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,240 @@
1
+ /**
2
+ * AgentMessage — 统一消息信封
3
+ *
4
+ * 核心抽象: Agent 永远不需要知道消息来自哪个渠道。
5
+ * Transport 适配器负责将渠道特定格式转换为 AgentMessage,
6
+ * Agent 只处理 AgentMessage, 通过 replyFn 返回结果。
7
+ *
8
+ * 这是将"飞书聊天"和"前端聊天"统一为同一概念的关键:
9
+ * - HTTP/SSE (Dashboard) → AgentMessage { channel: 'http', ... }
10
+ * - WebSocket (Lark/飞书) → AgentMessage { channel: 'lark', ... }
11
+ * - CLI (终端) → AgentMessage { channel: 'cli', ... }
12
+ * - MCP (IDE 扩展) → AgentMessage { channel: 'mcp', ... }
13
+ *
14
+ * @module AgentMessage
15
+ */
16
+
17
+ import { randomUUID } from 'node:crypto';
18
+
19
+ /**
20
+ * 通信渠道枚举
21
+ */
22
+ export const Channel = Object.freeze({
23
+ HTTP: 'http',
24
+ LARK: 'lark',
25
+ CLI: 'cli',
26
+ MCP: 'mcp',
27
+ INTERNAL: 'internal', // Agent 间通信
28
+ });
29
+
30
+ /**
31
+ * @typedef {Object} Sender
32
+ * @property {string} id — 发送者唯一标识
33
+ * @property {string} [name] — 显示名
34
+ * @property {'user'|'system'|'agent'} type — 发送者类型
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} Session
39
+ * @property {string} id — 会话 ID
40
+ * @property {Array<{role: string, content: string}>} [history] — 对话历史
41
+ */
42
+
43
+ export class AgentMessage {
44
+ /** @type {string} 消息唯一 ID */
45
+ id;
46
+ /** @type {string} 用户输入内容 */
47
+ content;
48
+ /** @type {string} 通信渠道 */
49
+ channel;
50
+ /** @type {Session} 会话信息 */
51
+ session;
52
+ /** @type {Sender} 发送者 */
53
+ sender;
54
+ /** @type {Record<string, any>} 渠道特定元数据 */
55
+ metadata;
56
+ /** @type {Function|null} 回复函数 (text: string) => Promise<void> */
57
+ replyFn;
58
+ /** @type {number} 时间戳 */
59
+ timestamp;
60
+
61
+ /**
62
+ * @param {Object} opts
63
+ * @param {string} opts.content — 用户输入
64
+ * @param {string} [opts.channel='http'] — 渠道
65
+ * @param {Session} [opts.session] — 会话
66
+ * @param {Sender} [opts.sender] — 发送者
67
+ * @param {Record<string, any>} [opts.metadata] — 元数据
68
+ * @param {Function} [opts.replyFn] — 回复函数
69
+ */
70
+ constructor({ content, channel = Channel.HTTP, session, sender, metadata, replyFn } = {}) {
71
+ this.id = randomUUID();
72
+ this.content = content || '';
73
+ this.channel = channel;
74
+ this.session = session || { id: randomUUID(), history: [] };
75
+ this.sender = sender || { id: 'anonymous', type: 'user' };
76
+ this.metadata = metadata || {};
77
+ this.replyFn = replyFn || null;
78
+ this.timestamp = Date.now();
79
+ }
80
+
81
+ /** 对话历史 (快捷访问) */
82
+ get history() {
83
+ return this.session.history || [];
84
+ }
85
+
86
+ /**
87
+ * 向发送方回复
88
+ * @param {string} text
89
+ */
90
+ async reply(text) {
91
+ if (this.replyFn) await this.replyFn(text);
92
+ }
93
+
94
+ // ─── Transport 工厂方法 ─────────────────────
95
+
96
+ /**
97
+ * 从 HTTP 请求构建
98
+ * @param {Object} req — Express request
99
+ * @param {Function} [replyFn] — SSE 或 JSON 回复
100
+ */
101
+ static fromHttp(req, replyFn) {
102
+ const body = req.body || {};
103
+ return new AgentMessage({
104
+ content: body.prompt || body.message || body.content || '',
105
+ channel: Channel.HTTP,
106
+ session: {
107
+ id: body.conversationId || body.sessionId || randomUUID(),
108
+ history: body.history || [],
109
+ },
110
+ sender: {
111
+ id: body.userId || req.ip || 'http-user',
112
+ name: body.userName,
113
+ type: 'user',
114
+ },
115
+ metadata: {
116
+ lang: body.lang,
117
+ mode: body.mode, // 手动指定 preset
118
+ context: body.context, // 额外上下文
119
+ stream: body.stream ?? true,
120
+ },
121
+ replyFn,
122
+ });
123
+ }
124
+
125
+ /**
126
+ * 从飞书消息构建
127
+ * @param {Object} larkMsg — 飞书消息对象
128
+ * @param {Function} replyFn — 飞书回复函数
129
+ */
130
+ static fromLark(larkMsg, replyFn) {
131
+ return new AgentMessage({
132
+ content: larkMsg.text || larkMsg.content || '',
133
+ channel: Channel.LARK,
134
+ session: {
135
+ id: larkMsg.chatId || randomUUID(),
136
+ history: [],
137
+ },
138
+ sender: {
139
+ id: larkMsg.senderId || larkMsg.userId || 'lark-user',
140
+ name: larkMsg.senderName,
141
+ type: 'user',
142
+ },
143
+ metadata: {
144
+ messageId: larkMsg.messageId,
145
+ chatId: larkMsg.chatId,
146
+ messageType: larkMsg.messageType,
147
+ // 飞书特有字段透传
148
+ raw: larkMsg,
149
+ },
150
+ replyFn,
151
+ });
152
+ }
153
+
154
+ /**
155
+ * 从 CLI 输入构建
156
+ * @param {string} input — 命令行输入
157
+ * @param {Object} [opts]
158
+ */
159
+ static fromCli(input, opts = {}) {
160
+ return new AgentMessage({
161
+ content: input,
162
+ channel: Channel.CLI,
163
+ session: { id: opts.sessionId || 'cli-session', history: opts.history || [] },
164
+ sender: { id: 'cli-user', type: 'user' },
165
+ metadata: {
166
+ cwd: opts.cwd || process.cwd(),
167
+ mode: opts.mode,
168
+ ...opts.metadata,
169
+ },
170
+ });
171
+ }
172
+
173
+ /**
174
+ * Agent 间内部消息
175
+ * @param {string} content — 消息内容
176
+ * @param {Object} [opts]
177
+ */
178
+ static internal(content, opts = {}) {
179
+ return new AgentMessage({
180
+ content,
181
+ channel: Channel.INTERNAL,
182
+ session: opts.session || { id: opts.sessionId || randomUUID(), history: opts.history || [] },
183
+ sender: {
184
+ id: opts.sourceAgentId || 'system',
185
+ type: 'agent',
186
+ },
187
+ metadata: {
188
+ parentAgentId: opts.parentAgentId,
189
+ dimension: opts.dimension,
190
+ phase: opts.phase,
191
+ ...opts.metadata,
192
+ },
193
+ });
194
+ }
195
+
196
+ /**
197
+ * 从 MCP 请求构建
198
+ * @param {Object} mcpReq — MCP tool call request
199
+ * @param {Function} [replyFn] — 回复函数
200
+ */
201
+ static fromMcp(mcpReq, replyFn) {
202
+ return new AgentMessage({
203
+ content: mcpReq.prompt || mcpReq.content || mcpReq.arguments?.prompt || '',
204
+ channel: Channel.MCP,
205
+ session: {
206
+ id: mcpReq.sessionId || randomUUID(),
207
+ history: mcpReq.history || [],
208
+ },
209
+ sender: {
210
+ id: mcpReq.clientId || 'mcp-client',
211
+ name: mcpReq.clientName,
212
+ type: 'user',
213
+ },
214
+ metadata: {
215
+ toolName: mcpReq.toolName,
216
+ arguments: mcpReq.arguments,
217
+ mode: mcpReq.mode,
218
+ ...mcpReq.metadata,
219
+ },
220
+ replyFn,
221
+ });
222
+ }
223
+
224
+ /**
225
+ * 序列化
226
+ */
227
+ toJSON() {
228
+ return {
229
+ id: this.id,
230
+ content: this.content,
231
+ channel: this.channel,
232
+ session: { id: this.session.id, historyLength: this.history.length },
233
+ sender: this.sender,
234
+ metadata: this.metadata,
235
+ timestamp: this.timestamp,
236
+ };
237
+ }
238
+ }
239
+
240
+ export default AgentMessage;
@@ -0,0 +1,228 @@
1
+ /**
2
+ * AgentRouter — Intent → Preset 解析器
3
+ *
4
+ * 在统一架构下,Router 的职责简化为:
5
+ * - 理解用户意图 (intent classification)
6
+ * - 映射到正确的 Preset (配置组合)
7
+ * - 创建 AgentRuntime 并执行
8
+ *
9
+ * 路由策略 (优先级递减):
10
+ * 1. 手动指定 (API `preset` 参数)
11
+ * 2. 渠道特征 (如飞书终端命令以 > 开头 → remote-exec)
12
+ * 3. 关键词匹配 (零延迟正则)
13
+ * 4. LLM 意图分类 (精确但需 AI 调用)
14
+ * 5. 默认 → chat
15
+ *
16
+ * 关键区别 (vs 旧 AgentRouter):
17
+ * - 旧版路由到 AgentMode (chat/bootstrap/scan/lark_bridge) → 4 种 Agent 类型
18
+ * - 新版路由到 Preset (chat/insight/remote-exec) → 同一 Runtime 的不同配置
19
+ * - 不再存在 "LarkBridge" 模式 — 飞书和前端共享同一 Preset
20
+ *
21
+ * @module AgentRouter
22
+ */
23
+
24
+ import Logger from '../../infrastructure/logging/Logger.js';
25
+ import { AgentEventBus, AgentEvents } from './AgentEventBus.js';
26
+ import { Channel } from './AgentMessage.js';
27
+
28
+ /**
29
+ * Preset 名称枚举
30
+ */
31
+ export const PresetName = Object.freeze({
32
+ CHAT: 'chat',
33
+ INSIGHT: 'insight',
34
+ LARK: 'lark',
35
+ REMOTE_EXEC: 'remote-exec',
36
+ });
37
+
38
+ /**
39
+ * 关键词 → Preset 映射 (零延迟快速路径)
40
+ */
41
+ const KEYWORD_ROUTES = [
42
+ {
43
+ preset: PresetName.INSIGHT,
44
+ keywords: [
45
+ /冷启动|cold[\s-]?start|初始化知识库|bootstrap/i,
46
+ /重建知识库|rebuild.*knowledge/i,
47
+ /全项目.*分析|analyze.*entire.*project/i,
48
+ /扫描|scan|分析.*目录|分析.*文件夹|analyze.*folder/i,
49
+ /检查.*target|审计.*模块|audit.*module/i,
50
+ /深度分析.*路径|知识提取/i,
51
+ ],
52
+ },
53
+ {
54
+ preset: PresetName.REMOTE_EXEC,
55
+ keywords: [
56
+ /^[>$]\s*/, // 以 > 或 $ 开头 = 终端命令
57
+ /运行命令|执行命令|run.*command|exec.*command/i,
58
+ ],
59
+ },
60
+ ];
61
+
62
+ /**
63
+ * LLM 路由分类 Schema
64
+ */
65
+ const ROUTE_CLASSIFICATION_SCHEMA = {
66
+ name: 'classify_intent',
67
+ description: 'Classify user intent to determine which agent preset to use',
68
+ parameters: {
69
+ type: 'object',
70
+ properties: {
71
+ preset: {
72
+ type: 'string',
73
+ enum: ['chat', 'insight', 'lark', 'remote-exec'],
74
+ description: 'The preset to route to. chat=conversation, insight=code analysis and knowledge extraction, lark=Lark knowledge management, remote-exec=terminal commands',
75
+ },
76
+ confidence: {
77
+ type: 'number',
78
+ description: 'Confidence score 0-1',
79
+ },
80
+ reasoning: {
81
+ type: 'string',
82
+ description: 'Brief reasoning for the classification',
83
+ },
84
+ },
85
+ required: ['preset', 'confidence'],
86
+ },
87
+ };
88
+
89
+ export class AgentRouter {
90
+ #logger;
91
+ #bus;
92
+ /** @type {import('../../external/ai/AiProvider.js').AiProvider|null} */
93
+ #aiProvider = null;
94
+ /** @type {Function|null} — (presetName, message, opts) => Promise<AgentResult> */
95
+ #executor = null;
96
+
97
+ constructor() {
98
+ this.#logger = Logger.getInstance();
99
+ this.#bus = AgentEventBus.getInstance();
100
+ }
101
+
102
+ /**
103
+ * 设置 AI Provider (用于 LLM 路由)
104
+ * @param {import('../../external/ai/AiProvider.js').AiProvider} provider
105
+ */
106
+ setAiProvider(provider) {
107
+ this.#aiProvider = provider;
108
+ }
109
+
110
+ /**
111
+ * 设置执行器 — Factory 提供的 (presetName, message, opts) => AgentResult
112
+ * @param {Function} executor
113
+ */
114
+ setExecutor(executor) {
115
+ this.#executor = executor;
116
+ }
117
+
118
+ /**
119
+ * 路由并执行
120
+ *
121
+ * @param {import('./AgentMessage.js').AgentMessage} message — 统一消息
122
+ * @param {Object} [opts]
123
+ * @param {string} [opts.preset] — 手动指定 Preset (跳过路由)
124
+ * @param {Object} [opts.strategyOpts] — 策略特定选项 (如 FanOut 的 items)
125
+ * @returns {Promise<{preset: string, result: Object}>}
126
+ */
127
+ async route(message, opts = {}) {
128
+ // 1. 手动指定
129
+ let preset = opts.preset || message.metadata?.mode;
130
+
131
+ // 2. 渠道特征路由
132
+ if (!preset) {
133
+ preset = this.#matchChannel(message);
134
+ if (preset) this.#logger.info(`[AgentRouter] Channel match → ${preset}`);
135
+ }
136
+
137
+ // 3. 关键词匹配
138
+ if (!preset) {
139
+ preset = this.#matchKeyword(message.content);
140
+ if (preset) this.#logger.info(`[AgentRouter] Keyword match → ${preset}`);
141
+ }
142
+
143
+ // 4. LLM 分类
144
+ if (!preset && this.#aiProvider) {
145
+ preset = await this.#classifyWithLLM(message.content);
146
+ if (preset) this.#logger.info(`[AgentRouter] LLM classification → ${preset}`);
147
+ }
148
+
149
+ // 5. 默认 → chat
150
+ if (!preset) preset = PresetName.CHAT;
151
+
152
+ // 执行
153
+ if (!this.#executor) {
154
+ throw new Error('[AgentRouter] No executor set. Call setExecutor() first.');
155
+ }
156
+
157
+ this.#logger.info(`[AgentRouter] Dispatching → preset="${preset}" channel=${message.channel}`);
158
+
159
+ const result = await this.#executor(preset, message, opts);
160
+ return { preset, result };
161
+ }
162
+
163
+ /**
164
+ * 仅分类意图,不执行
165
+ * @param {import('./AgentMessage.js').AgentMessage} message
166
+ * @returns {Promise<string>} preset name
167
+ */
168
+ async classify(message) {
169
+ let preset = message.metadata?.mode;
170
+ if (!preset) preset = this.#matchChannel(message);
171
+ if (!preset) preset = this.#matchKeyword(message.content);
172
+ if (!preset && this.#aiProvider) preset = await this.#classifyWithLLM(message.content);
173
+ return preset || PresetName.CHAT;
174
+ }
175
+
176
+ // ─── 私有方法 ────────────────────────────────
177
+
178
+ /**
179
+ * 渠道特征路由
180
+ * 飞书消息默认 → lark preset (知识管理对话)
181
+ * 飞书终端命令 (> 开头) → remote-exec
182
+ */
183
+ #matchChannel(message) {
184
+ if (message.channel === Channel.LARK) {
185
+ const text = message.content.trim();
186
+ // 飞书消息以 > 或 $ 开头 → 终端命令
187
+ if (/^[>$]\s*/.test(text)) return PresetName.REMOTE_EXEC;
188
+ // 默认走 lark preset (知识管理对话)
189
+ return PresetName.LARK;
190
+ }
191
+ return null;
192
+ }
193
+
194
+ #matchKeyword(prompt) {
195
+ for (const route of KEYWORD_ROUTES) {
196
+ for (const re of route.keywords) {
197
+ if (re.test(prompt)) return route.preset;
198
+ }
199
+ }
200
+ return null;
201
+ }
202
+
203
+ async #classifyWithLLM(prompt) {
204
+ try {
205
+ const result = await this.#aiProvider.chatWithTools(
206
+ `Classify this user message into the correct preset: "${prompt.slice(0, 300)}"`,
207
+ {
208
+ messages: [],
209
+ toolSchemas: [ROUTE_CLASSIFICATION_SCHEMA],
210
+ toolChoice: 'required',
211
+ systemPrompt: 'You classify user intents for an AI coding assistant. Respond by calling the classify_intent function.',
212
+ temperature: 0,
213
+ maxTokens: 200,
214
+ }
215
+ );
216
+
217
+ if (result.functionCalls?.[0]?.args?.preset) {
218
+ const classified = result.functionCalls[0].args;
219
+ if (classified.confidence > 0.6) return classified.preset;
220
+ }
221
+ } catch (err) {
222
+ this.#logger.warn(`[AgentRouter] LLM classification failed: ${err.message}`);
223
+ }
224
+ return null;
225
+ }
226
+ }
227
+
228
+ export default AgentRouter;