autosnippet 3.2.8 → 3.2.10
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 +6 -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 +23 -26
- package/lib/cli/SetupService.js +1 -1
- package/lib/cli/deploy/FileManifest.js +1 -1
- package/lib/core/AstAnalyzer.js +1 -1
- package/lib/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +29 -5
- 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 +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -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 +291 -204
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +7 -6
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-internal.js +2 -2
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- 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 +5 -5
- package/lib/http/routes/remote.js +134 -255
- 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 +64 -17
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/routes/spm.js +2 -2
- package/lib/service/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +535 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1056 -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 +409 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +112 -33
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +5 -3
- 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 +15 -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} +85 -135
- package/lib/service/agent/domain/insight-producer.js +270 -0
- package/lib/service/agent/domain/scan-prompts.js +444 -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 +29 -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 +1 -1
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +305 -0
- package/lib/service/agent/strategies.js +756 -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/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +27 -21
- package/lib/service/{chat → agent}/tools/infrastructure.js +1 -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/module/ModuleService.js +40 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/shared/FieldSpec.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-D5jiDBQG.css +0 -1
- package/dashboard/dist/assets/index-e5OKj-Ni.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -750
- 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 -359
- 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/ast-graph.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,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;
|