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,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessageAdapter — 统一消息操作接口
|
|
3
|
+
*
|
|
4
|
+
* 消除 reactLoop 内的 useCtxWin 双模式分支:
|
|
5
|
+
* - ContextWindowAdapter: 委托给 ContextWindow 实例 (bootstrap/system 场景)
|
|
6
|
+
* - SimpleArrayAdapter: 裸数组模式 (对话场景)
|
|
7
|
+
*
|
|
8
|
+
* 两个实现对外暴露完全相同的 API,
|
|
9
|
+
* 使得 reactLoop 及其提取方法无需关心底层消息存储方式。
|
|
10
|
+
*
|
|
11
|
+
* @module core/MessageAdapter
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { limitToolResult } from '../context/ContextWindow.js';
|
|
15
|
+
|
|
16
|
+
// ─────────────────────────────────────────────
|
|
17
|
+
// Base class (接口定义 + JSDoc)
|
|
18
|
+
// ─────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @abstract
|
|
22
|
+
*/
|
|
23
|
+
export class MessageAdapter {
|
|
24
|
+
/**
|
|
25
|
+
* 追加用户消息
|
|
26
|
+
* @param {string} text
|
|
27
|
+
*/
|
|
28
|
+
appendUserMessage(_text) { throw new Error('not implemented'); }
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 追加助手纯文本回复
|
|
32
|
+
* @param {string} text
|
|
33
|
+
*/
|
|
34
|
+
appendAssistantText(_text) { throw new Error('not implemented'); }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 追加助手带工具调用的回复
|
|
38
|
+
* @param {string|null} text
|
|
39
|
+
* @param {Array} calls — functionCalls 数组
|
|
40
|
+
*/
|
|
41
|
+
appendAssistantWithToolCalls(_text, _calls) { throw new Error('not implemented'); }
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 追加工具执行结果
|
|
45
|
+
* @param {string} callId
|
|
46
|
+
* @param {string} name
|
|
47
|
+
* @param {string} content
|
|
48
|
+
*/
|
|
49
|
+
appendToolResult(_callId, _name, _content) { throw new Error('not implemented'); }
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 追加系统/用户 nudge 消息
|
|
53
|
+
* @param {string} text
|
|
54
|
+
*/
|
|
55
|
+
appendUserNudge(_text) { throw new Error('not implemented'); }
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 导出当前消息列表 (供 LLM 调用)
|
|
59
|
+
* @returns {Array<{role: string, content: string}>}
|
|
60
|
+
*/
|
|
61
|
+
toMessages() { throw new Error('not implemented'); }
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 重置到仅保留初始 prompt (错误恢复)
|
|
65
|
+
*/
|
|
66
|
+
resetToPromptOnly() { throw new Error('not implemented'); }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 获取工具结果限额 (字符数)
|
|
70
|
+
* @returns {number}
|
|
71
|
+
*/
|
|
72
|
+
getToolResultQuota() { throw new Error('not implemented'); }
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 压缩检查 — 如果消息过多则自动压缩
|
|
76
|
+
* @returns {{ level: number, removed: number }}
|
|
77
|
+
*/
|
|
78
|
+
compactIfNeeded() { throw new Error('not implemented'); }
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 格式化工具结果字符串 (统一 limitToolResult 调用)
|
|
82
|
+
* @param {string} toolName
|
|
83
|
+
* @param {*} rawResult — 工具原始返回值
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
formatToolResult(toolName, rawResult) {
|
|
87
|
+
const quota = this.getToolResultQuota();
|
|
88
|
+
return limitToolResult(toolName, rawResult, quota);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─────────────────────────────────────────────
|
|
93
|
+
// ContextWindowAdapter — 委托给 ContextWindow
|
|
94
|
+
// ─────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 委托所有消息操作给 ContextWindow 实例。
|
|
98
|
+
*
|
|
99
|
+
* 用于 bootstrap / system 场景,
|
|
100
|
+
* ContextWindow 提供三级递进压缩 + 动态 token 预算。
|
|
101
|
+
*/
|
|
102
|
+
export class ContextWindowAdapter extends MessageAdapter {
|
|
103
|
+
/** @type {import('../context/ContextWindow.js').ContextWindow} */
|
|
104
|
+
#ctxWin;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {import('../context/ContextWindow.js').ContextWindow} ctxWin
|
|
108
|
+
*/
|
|
109
|
+
constructor(ctxWin) {
|
|
110
|
+
super();
|
|
111
|
+
this.#ctxWin = ctxWin;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 获取底层 ContextWindow 实例 (供 forced-summary 等外部逻辑使用) */
|
|
115
|
+
get contextWindow() {
|
|
116
|
+
return this.#ctxWin;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
appendUserMessage(text) {
|
|
120
|
+
this.#ctxWin.appendUserMessage(text);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
appendAssistantText(text) {
|
|
124
|
+
this.#ctxWin.appendAssistantText(text);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
appendAssistantWithToolCalls(text, calls) {
|
|
128
|
+
this.#ctxWin.appendAssistantWithToolCalls(text, calls);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
appendToolResult(callId, name, content) {
|
|
132
|
+
this.#ctxWin.appendToolResult(callId, name, content);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
appendUserNudge(text) {
|
|
136
|
+
this.#ctxWin.appendUserNudge(text);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
toMessages() {
|
|
140
|
+
return this.#ctxWin.toMessages();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
resetToPromptOnly() {
|
|
144
|
+
this.#ctxWin.resetToPromptOnly();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getToolResultQuota() {
|
|
148
|
+
return this.#ctxWin.getToolResultQuota();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
compactIfNeeded() {
|
|
152
|
+
return this.#ctxWin.compactIfNeeded();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─────────────────────────────────────────────
|
|
157
|
+
// SimpleArrayAdapter — 裸数组模式
|
|
158
|
+
// ─────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 简单数组消息管理 — 对话场景。
|
|
162
|
+
*
|
|
163
|
+
* 不做任何压缩,getToolResultQuota 返回固定 8000。
|
|
164
|
+
* compactIfNeeded 始终返回 no-op。
|
|
165
|
+
*/
|
|
166
|
+
export class SimpleArrayAdapter extends MessageAdapter {
|
|
167
|
+
/** @type {Array<Object>} */
|
|
168
|
+
#messages = [];
|
|
169
|
+
|
|
170
|
+
appendUserMessage(text) {
|
|
171
|
+
this.#messages.push({ role: 'user', content: text });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
appendAssistantText(text) {
|
|
175
|
+
this.#messages.push({ role: 'assistant', content: text });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
appendAssistantWithToolCalls(text, calls) {
|
|
179
|
+
this.#messages.push({ role: 'assistant', content: text, toolCalls: calls });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
appendToolResult(callId, name, content) {
|
|
183
|
+
this.#messages.push({ role: 'tool', toolCallId: callId, name, content });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
appendUserNudge(text) {
|
|
187
|
+
this.#messages.push({ role: 'user', content: text });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
toMessages() {
|
|
191
|
+
return [...this.#messages];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
resetToPromptOnly() {
|
|
195
|
+
const first = this.#messages[0];
|
|
196
|
+
this.#messages.length = 0;
|
|
197
|
+
if (first) this.#messages.push(first);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
getToolResultQuota() {
|
|
201
|
+
return { maxChars: 8000, maxMatches: 20 };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
compactIfNeeded() {
|
|
205
|
+
return { level: 0, removed: 0 };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─────────────────────────────────────────────
|
|
210
|
+
// Factory helper
|
|
211
|
+
// ─────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 根据是否提供 contextWindow 创建对应适配器
|
|
215
|
+
* @param {import('../context/ContextWindow.js').ContextWindow|null|undefined} contextWindow
|
|
216
|
+
* @returns {MessageAdapter}
|
|
217
|
+
*/
|
|
218
|
+
export function createMessageAdapter(contextWindow) {
|
|
219
|
+
if (contextWindow) {
|
|
220
|
+
return new ContextWindowAdapter(contextWindow);
|
|
221
|
+
}
|
|
222
|
+
return new SimpleArrayAdapter();
|
|
223
|
+
}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolExecutionPipeline — 工具执行的中间件管道
|
|
3
|
+
*
|
|
4
|
+
* 将 reactLoop 中 ~120 行的工具执行逻辑拆分为独立中间件:
|
|
5
|
+
* before → execute → after
|
|
6
|
+
*
|
|
7
|
+
* 每个中间件负责一个横切关注点:
|
|
8
|
+
* 1. EventBusPublisher — 事件发布
|
|
9
|
+
* 2. ProgressEmitter — 进度回调
|
|
10
|
+
* 3. SafetyGate — SafetyPolicy 安全拦截
|
|
11
|
+
* 4. CacheCheck — MemoryCoordinator 缓存命中
|
|
12
|
+
* 5. ObservationRecord — 记忆记录
|
|
13
|
+
* 6. TrackerSignal — ExplorationTracker 信号收集
|
|
14
|
+
* 7. TraceRecord — ActiveContext 推理链记录
|
|
15
|
+
* 8. SubmitDedup — 提交去重
|
|
16
|
+
*
|
|
17
|
+
* @module core/ToolExecutionPipeline
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { SafetyPolicy } from '../policies.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} ToolCall
|
|
24
|
+
* @property {string} name — 工具名称
|
|
25
|
+
* @property {Object} args — 工具参数
|
|
26
|
+
* @property {string} id — 调用 ID
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} ToolExecContext
|
|
31
|
+
* @property {import('../AgentRuntime.js').AgentRuntime} runtime — 运行时实例
|
|
32
|
+
* @property {import('./LoopContext.js').LoopContext} loopCtx — 循环上下文
|
|
33
|
+
* @property {number} iteration — 当前迭代次数
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} ToolMetadata
|
|
38
|
+
* @property {boolean} cacheHit — 是否缓存命中
|
|
39
|
+
* @property {boolean} blocked — 是否被安全策略拦截
|
|
40
|
+
* @property {boolean} isNew — 是否为新信息 (ExplorationTracker)
|
|
41
|
+
* @property {number} durationMs — 执行耗时
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} ToolMiddleware
|
|
46
|
+
* @property {string} name — 中间件名称
|
|
47
|
+
* @property {Function} [before] — 前置钩子: (call, ctx, metadata) => { blocked?, result? } | void
|
|
48
|
+
* @property {Function} [after] — 后置钩子: (call, result, ctx, metadata) => void
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
export class ToolExecutionPipeline {
|
|
52
|
+
/** @type {ToolMiddleware[]} */
|
|
53
|
+
#middlewares = [];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 注册中间件
|
|
57
|
+
* @param {ToolMiddleware} middleware
|
|
58
|
+
* @returns {this}
|
|
59
|
+
*/
|
|
60
|
+
use(middleware) {
|
|
61
|
+
this.#middlewares.push(middleware);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 执行单个工具调用
|
|
67
|
+
*
|
|
68
|
+
* 执行流:
|
|
69
|
+
* 1. 依次调用 before 钩子 — 任一返回 blocked/result 则短路
|
|
70
|
+
* 2. 实际执行工具 (toolRegistry.execute)
|
|
71
|
+
* 3. 依次调用 after 钩子
|
|
72
|
+
*
|
|
73
|
+
* @param {ToolCall} call — { name, args, id }
|
|
74
|
+
* @param {ToolExecContext} context — { runtime, loopCtx, iteration }
|
|
75
|
+
* @returns {Promise<{ result: *, metadata: ToolMetadata }>}
|
|
76
|
+
*/
|
|
77
|
+
async execute(call, context) {
|
|
78
|
+
let toolResult = null;
|
|
79
|
+
const metadata = { cacheHit: false, blocked: false, isNew: false, durationMs: 0 };
|
|
80
|
+
|
|
81
|
+
// ── before 阶段 ──
|
|
82
|
+
for (const mw of this.#middlewares) {
|
|
83
|
+
if (mw.before) {
|
|
84
|
+
const verdict = await mw.before(call, context, metadata);
|
|
85
|
+
if (verdict?.blocked) {
|
|
86
|
+
toolResult = verdict.result;
|
|
87
|
+
metadata.blocked = true;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
if (verdict?.result !== undefined) {
|
|
91
|
+
toolResult = verdict.result;
|
|
92
|
+
metadata.cacheHit = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── execute 阶段 ──
|
|
99
|
+
if (toolResult === null) {
|
|
100
|
+
const t0 = Date.now();
|
|
101
|
+
try {
|
|
102
|
+
const { runtime, loopCtx } = context;
|
|
103
|
+
const safetyPolicy = runtime.policies.get?.(SafetyPolicy) || null;
|
|
104
|
+
toolResult = await runtime.toolRegistry.execute(call.name, call.args, {
|
|
105
|
+
agentId: runtime.id,
|
|
106
|
+
source: loopCtx.source || runtime.presetName,
|
|
107
|
+
container: runtime.container,
|
|
108
|
+
safetyPolicy,
|
|
109
|
+
projectRoot: runtime.projectRoot,
|
|
110
|
+
fileCache: runtime.fileCache,
|
|
111
|
+
lang: runtime.lang,
|
|
112
|
+
logger: runtime.logger || null,
|
|
113
|
+
aiProvider: runtime.aiProvider || null,
|
|
114
|
+
// ── bootstrap 维度上下文 (从 sharedState 透传) ──
|
|
115
|
+
_submittedTitles: loopCtx.sharedState?.submittedTitles || null,
|
|
116
|
+
_submittedPatterns: loopCtx.sharedState?.submittedPatterns || null,
|
|
117
|
+
_sharedState: loopCtx.sharedState || null,
|
|
118
|
+
_dimensionMeta: loopCtx.sharedState?._dimensionMeta || null,
|
|
119
|
+
_projectLanguage: loopCtx.sharedState?._projectLanguage || null,
|
|
120
|
+
_memoryCoordinator: loopCtx.memoryCoordinator || null,
|
|
121
|
+
_dimensionScopeId: loopCtx.sharedState?._dimensionScopeId || null,
|
|
122
|
+
_currentRound: loopCtx.iteration || 0,
|
|
123
|
+
});
|
|
124
|
+
} catch (err) {
|
|
125
|
+
toolResult = { error: err.message };
|
|
126
|
+
}
|
|
127
|
+
metadata.durationMs = Date.now() - t0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── after 阶段 ──
|
|
131
|
+
for (const mw of this.#middlewares) {
|
|
132
|
+
if (mw.after) {
|
|
133
|
+
await mw.after(call, toolResult, context, metadata);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { result: toolResult, metadata };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─────────────────────────────────────────────
|
|
142
|
+
// 预置中间件
|
|
143
|
+
// ─────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* AllowlistGate — 工具白名单守卫
|
|
147
|
+
*
|
|
148
|
+
* 防止 LLM hallucinate 不在当前 capability 允许列表中的工具调用。
|
|
149
|
+
* 从 LoopContext.toolSchemas 中提取允许的工具名列表,
|
|
150
|
+
* 拒绝不在列表中的调用(返回 error 提示)。
|
|
151
|
+
*
|
|
152
|
+
* before: 如果工具不在白名单中则短路返回 error
|
|
153
|
+
*/
|
|
154
|
+
export const allowlistGate = {
|
|
155
|
+
name: 'allowlistGate',
|
|
156
|
+
before(call, ctx) {
|
|
157
|
+
const schemas = ctx.loopCtx?.toolSchemas;
|
|
158
|
+
// 如果没有 schema 列表(全工具模式),跳过检查
|
|
159
|
+
if (!schemas || schemas.length === 0) return;
|
|
160
|
+
|
|
161
|
+
const allowedNames = new Set(schemas.map(s => s.name || s.function?.name));
|
|
162
|
+
if (!allowedNames.has(call.name)) {
|
|
163
|
+
ctx.runtime.logger.warn(
|
|
164
|
+
`[ToolPipeline] ⛔ Tool "${call.name}" not in allowlist — blocked (hallucinated call)`
|
|
165
|
+
);
|
|
166
|
+
return {
|
|
167
|
+
blocked: true,
|
|
168
|
+
result: {
|
|
169
|
+
error: `工具 "${call.name}" 不可用。当前可用工具: ${[...allowedNames].slice(0, 5).join(', ')}${allowedNames.size > 5 ? '...' : ''}`,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* SafetyGate — SafetyPolicy 安全拦截
|
|
178
|
+
*
|
|
179
|
+
* before: 如果策略拒绝则短路返回 error
|
|
180
|
+
*/
|
|
181
|
+
export const safetyGate = {
|
|
182
|
+
name: 'safetyGate',
|
|
183
|
+
before(call, ctx) {
|
|
184
|
+
const check = ctx.runtime.policies.validateToolCall(call.name, call.args);
|
|
185
|
+
if (!check.ok) {
|
|
186
|
+
ctx.runtime.logger.warn(
|
|
187
|
+
`[ToolPipeline] Tool blocked by Policy: ${call.name} — ${check.reason}`
|
|
188
|
+
);
|
|
189
|
+
return { blocked: true, result: { error: check.reason } };
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* CacheCheck — MemoryCoordinator 缓存命中
|
|
196
|
+
*
|
|
197
|
+
* before: 如果缓存命中则短路返回缓存值
|
|
198
|
+
*/
|
|
199
|
+
export const cacheCheck = {
|
|
200
|
+
name: 'cacheCheck',
|
|
201
|
+
before(call, ctx) {
|
|
202
|
+
const mc = ctx.loopCtx.memoryCoordinator;
|
|
203
|
+
if (!mc) return;
|
|
204
|
+
const cached = mc.getCachedResult?.(call.name, call.args);
|
|
205
|
+
if (cached !== null && cached !== undefined) {
|
|
206
|
+
ctx.runtime.logger.info(
|
|
207
|
+
`[ToolPipeline] 🔧 CACHE HIT: ${call.name} → skipped execution`
|
|
208
|
+
);
|
|
209
|
+
return { result: cached };
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* ObservationRecord — MemoryCoordinator 观察记录
|
|
216
|
+
*
|
|
217
|
+
* after: 记录工具执行观察
|
|
218
|
+
*/
|
|
219
|
+
export const observationRecord = {
|
|
220
|
+
name: 'observationRecord',
|
|
221
|
+
after(call, result, ctx, meta) {
|
|
222
|
+
ctx.loopCtx.memoryCoordinator?.recordObservation?.(
|
|
223
|
+
call.name, call.args, result, ctx.iteration, meta.cacheHit
|
|
224
|
+
);
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* TrackerSignal — ExplorationTracker 信号收集
|
|
230
|
+
*
|
|
231
|
+
* after: 记录工具调用信号,更新 isNew 标记
|
|
232
|
+
*/
|
|
233
|
+
export const trackerSignal = {
|
|
234
|
+
name: 'trackerSignal',
|
|
235
|
+
after(call, result, ctx, meta) {
|
|
236
|
+
if (ctx.loopCtx.tracker) {
|
|
237
|
+
const r = ctx.loopCtx.tracker.recordToolCall(call.name, call.args, result);
|
|
238
|
+
meta.isNew = r.isNew;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* TraceRecord — ActiveContext 推理链记录
|
|
245
|
+
*
|
|
246
|
+
* after: 记录 Action + Observation 到推理链
|
|
247
|
+
*/
|
|
248
|
+
export const traceRecord = {
|
|
249
|
+
name: 'traceRecord',
|
|
250
|
+
after(call, result, ctx, meta) {
|
|
251
|
+
ctx.loopCtx.trace?.recordToolCall(call.name, call.args, result, meta.isNew);
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* SubmitDedup — 提交去重
|
|
257
|
+
*
|
|
258
|
+
* after: 检查并标记重复提交 (修改 metadata)
|
|
259
|
+
*/
|
|
260
|
+
export const submitDedup = {
|
|
261
|
+
name: 'submitDedup',
|
|
262
|
+
after(call, result, ctx, meta) {
|
|
263
|
+
const { sharedState } = ctx.loopCtx;
|
|
264
|
+
if (!sharedState) return;
|
|
265
|
+
if (call.name !== 'submit_knowledge' && call.name !== 'submit_with_check') return;
|
|
266
|
+
|
|
267
|
+
const title = call.args?.title || call.args?.category || '';
|
|
268
|
+
const isRejected = typeof result === 'object' && result?.status === 'rejected';
|
|
269
|
+
const isError = typeof result === 'object' && (result?.error || result?.status === 'error');
|
|
270
|
+
|
|
271
|
+
if (!isRejected && !isError && sharedState.submittedTitles) {
|
|
272
|
+
const normalizedTitle = title.toLowerCase().trim();
|
|
273
|
+
if (sharedState.submittedTitles.has(normalizedTitle)) {
|
|
274
|
+
meta.dedupMessage = `⚠ 重复提交: "${title}" 已存在。`;
|
|
275
|
+
ctx.runtime.logger.info(`[ToolPipeline] 🔁 duplicate: "${title}"`);
|
|
276
|
+
} else {
|
|
277
|
+
sharedState.submittedTitles.add(normalizedTitle);
|
|
278
|
+
// 模式指纹去重
|
|
279
|
+
const pattern = call.args?.content?.pattern || '';
|
|
280
|
+
if (pattern.length >= 30 && sharedState.submittedPatterns) {
|
|
281
|
+
const fp = pattern
|
|
282
|
+
.replace(/\/\/[^\n]*/g, '')
|
|
283
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
284
|
+
.replace(/[\s]+/g, '')
|
|
285
|
+
.toLowerCase()
|
|
286
|
+
.slice(0, 200);
|
|
287
|
+
if (fp.length >= 20) {
|
|
288
|
+
sharedState.submittedPatterns.add(fp);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
meta.isSubmit = true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* ProgressEmitter — 进度回调 (可选,需 runtime.emitProgress 为 public)
|
|
299
|
+
*
|
|
300
|
+
* NOTE: 默认管道不包含此中间件,因为 tool_end 事件需要 resultStr.length,
|
|
301
|
+
* 而 resultStr 在管道外部计算。由 #processToolCalls 直接处理。
|
|
302
|
+
*/
|
|
303
|
+
export const progressEmitter = {
|
|
304
|
+
name: 'progressEmitter',
|
|
305
|
+
before(call, ctx) {
|
|
306
|
+
ctx.runtime.emitProgress?.('tool_call', { tool: call.name, args: call.args });
|
|
307
|
+
},
|
|
308
|
+
after(call, result, ctx, meta) {
|
|
309
|
+
ctx.runtime.emitProgress?.('tool_end', {
|
|
310
|
+
tool: call.name,
|
|
311
|
+
duration: meta.durationMs,
|
|
312
|
+
status: result?.error ? 'error' : 'ok',
|
|
313
|
+
error: result?.error || undefined,
|
|
314
|
+
});
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* EventBusPublisher — EventBus 事件发布 (可选)
|
|
320
|
+
*
|
|
321
|
+
* NOTE: 默认管道不包含此中间件。由 #processToolCalls 直接处理,
|
|
322
|
+
* 与原始 reactLoop 保持完全一致的事件顺序。
|
|
323
|
+
*/
|
|
324
|
+
export const eventBusPublisher = {
|
|
325
|
+
name: 'eventBusPublisher',
|
|
326
|
+
before(call, ctx) {
|
|
327
|
+
if (ctx.runtime.bus?.publish) {
|
|
328
|
+
ctx.runtime.bus.publish('tool:call:start', {
|
|
329
|
+
agentId: ctx.runtime.id,
|
|
330
|
+
tool: call.name,
|
|
331
|
+
}, { source: ctx.runtime.id });
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
after(call, result, ctx, meta) {
|
|
335
|
+
if (ctx.runtime.bus?.publish) {
|
|
336
|
+
ctx.runtime.bus.publish('tool:call:end', {
|
|
337
|
+
agentId: ctx.runtime.id,
|
|
338
|
+
tool: call.name,
|
|
339
|
+
durationMs: meta.durationMs,
|
|
340
|
+
success: !result?.error,
|
|
341
|
+
}, { source: ctx.runtime.id });
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// ─────────────────────────────────────────────
|
|
347
|
+
// Factory helper
|
|
348
|
+
// ─────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 创建预配置的工具执行管道
|
|
352
|
+
*
|
|
353
|
+
* 中间件顺序:
|
|
354
|
+
* 1. safetyGate (安全拦截 — 可短路)
|
|
355
|
+
* 2. cacheCheck (缓存检查 — 可短路)
|
|
356
|
+
* 3. observationRecord (记忆记录)
|
|
357
|
+
* 4. trackerSignal (信号收集)
|
|
358
|
+
* 5. traceRecord (推理链)
|
|
359
|
+
* 6. submitDedup (提交去重)
|
|
360
|
+
*
|
|
361
|
+
* NOTE: eventBusPublisher 和 progressEmitter 不在默认管道中,
|
|
362
|
+
* 由 #processToolCalls 直接处理,以保持与原始 reactLoop 完全一致的事件顺序
|
|
363
|
+
* (progress_end 需要 resultStr.length,在管道外计算)。
|
|
364
|
+
*
|
|
365
|
+
* @returns {ToolExecutionPipeline}
|
|
366
|
+
*/
|
|
367
|
+
export function createToolPipeline() {
|
|
368
|
+
return new ToolExecutionPipeline()
|
|
369
|
+
.use(allowlistGate)
|
|
370
|
+
.use(safetyGate)
|
|
371
|
+
.use(cacheCheck)
|
|
372
|
+
.use(observationRecord)
|
|
373
|
+
.use(trackerSignal)
|
|
374
|
+
.use(traceRecord)
|
|
375
|
+
.use(submitDedup);
|
|
376
|
+
}
|