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,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
+ }