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,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentState — 类型安全的 Agent 状态机
|
|
3
|
+
*
|
|
4
|
+
* 借鉴 LangGraph StateGraph + Anthropic Agentic Patterns:
|
|
5
|
+
* - 每个 Agent 拥有独立的 typed state
|
|
6
|
+
* - 状态转移通过声明式 transitions 定义
|
|
7
|
+
* - 支持 guard 条件(类似 XState)
|
|
8
|
+
* - 内置事件发射(状态变更通知)
|
|
9
|
+
*
|
|
10
|
+
* 设计原则:
|
|
11
|
+
* 1. 不可变更新 — 每次 transition 返回新 state snapshot
|
|
12
|
+
* 2. 可溯源 — 保留完整状态历史(可选)
|
|
13
|
+
* 3. 可序列化 — state 可 JSON 化,支持持久化/恢复
|
|
14
|
+
*
|
|
15
|
+
* @module AgentState
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { EventEmitter } from 'node:events';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Agent 执行阶段枚举
|
|
22
|
+
*/
|
|
23
|
+
export const AgentPhase = Object.freeze({
|
|
24
|
+
IDLE: 'idle',
|
|
25
|
+
PLANNING: 'planning',
|
|
26
|
+
EXECUTING: 'executing',
|
|
27
|
+
REFLECTING: 'reflecting',
|
|
28
|
+
WAITING_INPUT: 'waiting_input',
|
|
29
|
+
HANDOFF: 'handoff',
|
|
30
|
+
COMPLETED: 'completed',
|
|
31
|
+
FAILED: 'failed',
|
|
32
|
+
ABORTED: 'aborted',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 状态转移定义
|
|
37
|
+
* @typedef {Object} Transition
|
|
38
|
+
* @property {string} from — 起始阶段
|
|
39
|
+
* @property {string} to — 目标阶段
|
|
40
|
+
* @property {string} event — 触发事件名
|
|
41
|
+
* @property {Function} [guard] — 守卫条件 (state) => boolean
|
|
42
|
+
* @property {Function} [action] — 副作用 (state, payload) => void
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 默认状态转移图(适用于所有 Agent 模式,可在子类中扩展)
|
|
47
|
+
*/
|
|
48
|
+
const DEFAULT_TRANSITIONS = [
|
|
49
|
+
{ from: AgentPhase.IDLE, to: AgentPhase.PLANNING, event: 'start' },
|
|
50
|
+
{ from: AgentPhase.PLANNING, to: AgentPhase.EXECUTING, event: 'plan_ready' },
|
|
51
|
+
{ from: AgentPhase.EXECUTING, to: AgentPhase.REFLECTING, event: 'step_done' },
|
|
52
|
+
{ from: AgentPhase.REFLECTING, to: AgentPhase.EXECUTING, event: 'continue' },
|
|
53
|
+
{ from: AgentPhase.REFLECTING, to: AgentPhase.COMPLETED, event: 'finish' },
|
|
54
|
+
{ from: AgentPhase.EXECUTING, to: AgentPhase.COMPLETED, event: 'finish' },
|
|
55
|
+
{ from: AgentPhase.EXECUTING, to: AgentPhase.WAITING_INPUT, event: 'need_input' },
|
|
56
|
+
{ from: AgentPhase.WAITING_INPUT, to: AgentPhase.EXECUTING, event: 'input_received' },
|
|
57
|
+
{ from: AgentPhase.EXECUTING, to: AgentPhase.HANDOFF, event: 'handoff' },
|
|
58
|
+
{ from: AgentPhase.HANDOFF, to: AgentPhase.EXECUTING, event: 'handoff_done' },
|
|
59
|
+
// 任意阶段可中止/失败
|
|
60
|
+
{ from: '*', to: AgentPhase.ABORTED, event: 'abort' },
|
|
61
|
+
{ from: '*', to: AgentPhase.FAILED, event: 'error' },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export class AgentState extends EventEmitter {
|
|
65
|
+
/** @type {string} 当前阶段 */
|
|
66
|
+
#phase;
|
|
67
|
+
/** @type {Object} 用户自定义状态数据 */
|
|
68
|
+
#data;
|
|
69
|
+
/** @type {Transition[]} */
|
|
70
|
+
#transitions;
|
|
71
|
+
/** @type {Array<{phase: string, data: Object, timestamp: number}>} */
|
|
72
|
+
#history;
|
|
73
|
+
/** @type {boolean} 是否保留历史 */
|
|
74
|
+
#keepHistory;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {Object} [opts]
|
|
78
|
+
* @param {Object} [opts.initialData={}] — 初始状态数据
|
|
79
|
+
* @param {string} [opts.initialPhase='idle'] — 初始阶段
|
|
80
|
+
* @param {Transition[]} [opts.transitions] — 自定义转移定义(合并到默认转移上)
|
|
81
|
+
* @param {boolean} [opts.keepHistory=true] — 是否保留状态历史
|
|
82
|
+
*/
|
|
83
|
+
constructor({
|
|
84
|
+
initialData = {},
|
|
85
|
+
initialPhase = AgentPhase.IDLE,
|
|
86
|
+
transitions = [],
|
|
87
|
+
keepHistory = true,
|
|
88
|
+
} = {}) {
|
|
89
|
+
super();
|
|
90
|
+
this.#phase = initialPhase;
|
|
91
|
+
this.#data = { ...initialData };
|
|
92
|
+
this.#transitions = [...DEFAULT_TRANSITIONS, ...transitions];
|
|
93
|
+
this.#keepHistory = keepHistory;
|
|
94
|
+
this.#history = keepHistory ? [{ phase: initialPhase, data: { ...initialData }, timestamp: Date.now() }] : [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── 公共 API ────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/** 当前阶段 */
|
|
100
|
+
get phase() { return this.#phase; }
|
|
101
|
+
|
|
102
|
+
/** 当前状态数据(只读 copy) */
|
|
103
|
+
get data() { return { ...this.#data }; }
|
|
104
|
+
|
|
105
|
+
/** 状态历史 */
|
|
106
|
+
get history() { return [...this.#history]; }
|
|
107
|
+
|
|
108
|
+
/** Agent 是否处于终态 */
|
|
109
|
+
get isTerminal() {
|
|
110
|
+
return [AgentPhase.COMPLETED, AgentPhase.FAILED, AgentPhase.ABORTED].includes(this.#phase);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 触发事件,尝试状态转移
|
|
115
|
+
* @param {string} event — 事件名
|
|
116
|
+
* @param {Object} [payload={}] — 附加数据(合并到 state.data)
|
|
117
|
+
* @returns {boolean} 是否成功转移
|
|
118
|
+
*/
|
|
119
|
+
send(event, payload = {}) {
|
|
120
|
+
const transition = this.#findTransition(event);
|
|
121
|
+
if (!transition) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Guard 检查
|
|
126
|
+
if (transition.guard && !transition.guard(this.#data)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const prevPhase = this.#phase;
|
|
131
|
+
this.#phase = transition.to;
|
|
132
|
+
this.#data = { ...this.#data, ...payload };
|
|
133
|
+
|
|
134
|
+
// 执行副作用
|
|
135
|
+
if (transition.action) {
|
|
136
|
+
transition.action(this.#data, payload);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 记录历史
|
|
140
|
+
if (this.#keepHistory) {
|
|
141
|
+
this.#history.push({
|
|
142
|
+
phase: this.#phase,
|
|
143
|
+
data: { ...this.#data },
|
|
144
|
+
timestamp: Date.now(),
|
|
145
|
+
event,
|
|
146
|
+
from: prevPhase,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 发射事件
|
|
151
|
+
this.emit('transition', { from: prevPhase, to: this.#phase, event, payload });
|
|
152
|
+
this.emit(`phase:${this.#phase}`, { from: prevPhase, event, payload });
|
|
153
|
+
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 直接更新状态数据(不触发阶段转移)
|
|
159
|
+
* @param {Object} patch — 要合并的数据
|
|
160
|
+
*/
|
|
161
|
+
update(patch) {
|
|
162
|
+
this.#data = { ...this.#data, ...patch };
|
|
163
|
+
this.emit('update', { phase: this.#phase, patch });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 获取当前阶段可用的事件列表
|
|
168
|
+
* @returns {string[]}
|
|
169
|
+
*/
|
|
170
|
+
availableEvents() {
|
|
171
|
+
return this.#transitions
|
|
172
|
+
.filter(t => t.from === this.#phase || t.from === '*')
|
|
173
|
+
.map(t => t.event);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 导出为可序列化对象
|
|
178
|
+
* @returns {Object}
|
|
179
|
+
*/
|
|
180
|
+
toJSON() {
|
|
181
|
+
return {
|
|
182
|
+
phase: this.#phase,
|
|
183
|
+
data: this.#data,
|
|
184
|
+
history: this.#history,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 从序列化数据恢复
|
|
190
|
+
* @param {Object} snapshot
|
|
191
|
+
* @returns {AgentState}
|
|
192
|
+
*/
|
|
193
|
+
static fromJSON(snapshot, opts = {}) {
|
|
194
|
+
const state = new AgentState({
|
|
195
|
+
initialData: snapshot.data || {},
|
|
196
|
+
initialPhase: snapshot.phase || AgentPhase.IDLE,
|
|
197
|
+
transitions: opts.transitions || [],
|
|
198
|
+
keepHistory: opts.keepHistory ?? true,
|
|
199
|
+
});
|
|
200
|
+
if (snapshot.history) {
|
|
201
|
+
state.#history = snapshot.history;
|
|
202
|
+
}
|
|
203
|
+
return state;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─── 私有方法 ────────────────────────────────
|
|
207
|
+
|
|
208
|
+
#findTransition(event) {
|
|
209
|
+
// 精确匹配优先
|
|
210
|
+
const exact = this.#transitions.find(t => t.from === this.#phase && t.event === event);
|
|
211
|
+
if (exact) return exact;
|
|
212
|
+
// 通配符匹配
|
|
213
|
+
return this.#transitions.find(t => t.from === '*' && t.event === event);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export default AgentState;
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IntentClassifier — 自然语言意图分类器
|
|
3
|
+
*
|
|
4
|
+
* 核心问题: 用户通过飞书发了一句自然语言,应该交给谁处理?
|
|
5
|
+
*
|
|
6
|
+
* ┌──────────────────────────────────────────────────────────┐
|
|
7
|
+
* │ "帮我搜索一下项目里关于用户认证的知识" │
|
|
8
|
+
* │ → bot_agent (知识管理任务,服务端 AgentRuntime 处理) │
|
|
9
|
+
* │ │
|
|
10
|
+
* │ "把 src/auth.ts 里的 JWT 验证改成 OAuth2" │
|
|
11
|
+
* │ → ide_agent (编程任务,转发到 VSCode Copilot) │
|
|
12
|
+
* │ │
|
|
13
|
+
* │ "现在服务状态怎么样" │
|
|
14
|
+
* │ → system (系统状态查询,本地直接处理) │
|
|
15
|
+
* └──────────────────────────────────────────────────────────┘
|
|
16
|
+
*
|
|
17
|
+
* 三层分类策略 (延迟递增):
|
|
18
|
+
* 1. 规则匹配 — 零延迟关键词/模式 (~0ms)
|
|
19
|
+
* 2. 嵌入相似度 — 轻量向量匹配 (~50ms) [可选]
|
|
20
|
+
* 3. LLM 分类 — 精确但需 AI 调用 (~500ms)
|
|
21
|
+
*
|
|
22
|
+
* 设计原则:
|
|
23
|
+
* - 宁可多走 LLM 也不要误分类 — 用户体验优先
|
|
24
|
+
* - bot_agent 是默认,除非明确检测到编程意图
|
|
25
|
+
* - 系统查询是硬编码模式,不走 AI
|
|
26
|
+
*
|
|
27
|
+
* @module IntentClassifier
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 意图类型
|
|
34
|
+
*/
|
|
35
|
+
export const Intent = Object.freeze({
|
|
36
|
+
/** 知识管理任务 — 搜索/创建/分析知识,由服务端 AgentRuntime 处理 */
|
|
37
|
+
BOT_AGENT: 'bot_agent',
|
|
38
|
+
/** IDE 编程任务 — 代码编写/修改/调试/重构,转发到 VSCode Copilot */
|
|
39
|
+
IDE_AGENT: 'ide_agent',
|
|
40
|
+
/** 系统操作 — 状态查询/截图/连接管理,本地直接处理 */
|
|
41
|
+
SYSTEM: 'system',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 分类结果
|
|
46
|
+
* @typedef {Object} ClassificationResult
|
|
47
|
+
* @property {string} intent — Intent 枚举值
|
|
48
|
+
* @property {number} confidence — 0-1 置信度
|
|
49
|
+
* @property {string} reasoning — 分类理由 (用于日志/调试)
|
|
50
|
+
* @property {string} method — 'rule' | 'llm' 分类方法
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// ─── 规则匹配表 ─────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 系统操作规则 — 硬编码匹配,优先级最高
|
|
57
|
+
* 这些过去是 /command,现在用自然语言检测
|
|
58
|
+
*/
|
|
59
|
+
const SYSTEM_RULES = [
|
|
60
|
+
{ pattern: /状态|status|连接.*状态|诊断|服务.*状态|链路/i, action: 'status' },
|
|
61
|
+
{ pattern: /截图|screenshot|screen|截屏|画面/i, action: 'screen' },
|
|
62
|
+
{ pattern: /帮助|help|怎么用|使用说明/i, action: 'help' },
|
|
63
|
+
{ pattern: /队列|queue|排队|待执行/i, action: 'queue' },
|
|
64
|
+
{ pattern: /取消|cancel|撤销|不要了|别执行了/i, action: 'cancel' },
|
|
65
|
+
{ pattern: /清[理空]|clear|clean.*历史/i, action: 'clear' },
|
|
66
|
+
{ pattern: /^(ping|pong|测试连[通接])/i, action: 'ping' },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* IDE 编程意图 — 强信号关键词
|
|
71
|
+
* 如果匹配到这些,大概率是编程任务
|
|
72
|
+
*/
|
|
73
|
+
const IDE_STRONG_SIGNALS = [
|
|
74
|
+
// 直接编程动作
|
|
75
|
+
/修改|修复|改一下|改成|fix|refactor|重构|优化.*代码/i,
|
|
76
|
+
/写一个|实现|implement|创建.*文件|新建.*组件|添加.*功能/i,
|
|
77
|
+
/删除.*代码|移除|remove.*from|把.*去掉/i,
|
|
78
|
+
/调试|debug|排查.*bug|解决.*报错|修.*error/i,
|
|
79
|
+
// 文件/代码引用
|
|
80
|
+
/\.(?:ts|js|tsx|jsx|py|go|rs|java|swift|vue|css|html|json)\b/i,
|
|
81
|
+
/src\/|lib\/|components\/|pages\/|app\//i,
|
|
82
|
+
// 技术术语 + 动作
|
|
83
|
+
/(?:函数|方法|接口|类|组件|模块|hook).{0,10}(?:改|加|删|写|重构|优化)/i,
|
|
84
|
+
/(?:改|加|删|写|重构|优化).{0,10}(?:函数|方法|接口|类|组件|模块|hook)/i,
|
|
85
|
+
// 终端/构建命令
|
|
86
|
+
/运行|执行|run.*command|exec|npm|yarn|pnpm|cargo|go run|python/i,
|
|
87
|
+
/编译|build|compile|部署|deploy/i,
|
|
88
|
+
// Git 操作
|
|
89
|
+
/commit|push|pull|merge|branch|rebase|cherry.?pick|stash|git\s+(log|status|diff|show|add|reset|checkout|switch|tag)/i,
|
|
90
|
+
// 代码审查
|
|
91
|
+
/review.*代码|看看.*写得|检查.*实现/i,
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Bot Agent 意图 — 知识管理相关关键词
|
|
96
|
+
*/
|
|
97
|
+
const BOT_STRONG_SIGNALS = [
|
|
98
|
+
// 知识库操作
|
|
99
|
+
/知识库|knowledge.*base|搜索知识|查[找询].*知识/i,
|
|
100
|
+
/recipe|候选|candidate|snippet/i,
|
|
101
|
+
/冷启动|bootstrap|初始化.*项目/i,
|
|
102
|
+
// 分析/理解(非编程修改)
|
|
103
|
+
/解释|explain|帮我理解|什么意思|是什么/i,
|
|
104
|
+
/分析.*架构|项目.*结构|代码.*组织/i,
|
|
105
|
+
/总结|summarize|概括|提取.*要点/i,
|
|
106
|
+
// 翻译
|
|
107
|
+
/翻[译成]|translate/i,
|
|
108
|
+
// 知识管理对话
|
|
109
|
+
/聊聊|讨论|你觉得|建议|推荐/i,
|
|
110
|
+
/guard|规则|约束|violation|违规/i,
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
// ─── LLM 分类 Schema ────────────────────────────
|
|
114
|
+
|
|
115
|
+
const CLASSIFY_SCHEMA = {
|
|
116
|
+
name: 'classify_lark_intent',
|
|
117
|
+
description: 'Classify a Lark message to determine whether it should be handled by the Bot Agent (knowledge management on server) or forwarded to IDE Agent (coding in VSCode)',
|
|
118
|
+
parameters: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
intent: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
enum: ['bot_agent', 'ide_agent'],
|
|
124
|
+
description: 'bot_agent: knowledge search/management/analysis/conversation tasks handled on the server. ide_agent: code writing/editing/debugging/refactoring/terminal tasks forwarded to VSCode Copilot.',
|
|
125
|
+
},
|
|
126
|
+
confidence: {
|
|
127
|
+
type: 'number',
|
|
128
|
+
description: 'Confidence 0-1',
|
|
129
|
+
},
|
|
130
|
+
reasoning: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
description: 'Brief reasoning in Chinese',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
required: ['intent', 'confidence'],
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const CLASSIFY_SYSTEM_PROMPT = `你是一个意图分类器。用户通过飞书发送消息,你需要判断这条消息应该交给哪个 Agent 处理:
|
|
140
|
+
|
|
141
|
+
**bot_agent** — 知识管理 Bot (服务端处理):
|
|
142
|
+
- 搜索/查询/浏览项目知识库
|
|
143
|
+
- 创建/编辑/管理知识条目 (Recipe/Candidate)
|
|
144
|
+
- 项目架构分析、代码解释、翻译、总结
|
|
145
|
+
- 一般性对话、建议、推荐
|
|
146
|
+
- 知识库冷启动、Guard 规则管理
|
|
147
|
+
- 不需要直接修改源代码文件
|
|
148
|
+
|
|
149
|
+
**ide_agent** — IDE 编程 Agent (VSCode Copilot 处理):
|
|
150
|
+
- 创建/修改/删除源代码文件
|
|
151
|
+
- 调试、修复 bug、重构代码
|
|
152
|
+
- 运行终端命令 (npm/yarn/cargo/git 等)
|
|
153
|
+
- 代码审查、PR 相关操作
|
|
154
|
+
- 任何需要直接操作项目文件的任务
|
|
155
|
+
|
|
156
|
+
关键判断原则:
|
|
157
|
+
1. 如果任务涉及"修改源代码"→ ide_agent
|
|
158
|
+
2. 如果任务涉及"理解/搜索/管理知识"→ bot_agent
|
|
159
|
+
3. 模糊时倾向 bot_agent (成本更低,用户可重新触发)`;
|
|
160
|
+
|
|
161
|
+
// ─── IntentClassifier 实现 ──────────────────────
|
|
162
|
+
|
|
163
|
+
export class IntentClassifier {
|
|
164
|
+
/** @type {import('../../external/ai/AiProvider.js').AiProvider|null} */
|
|
165
|
+
#aiProvider;
|
|
166
|
+
#logger;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {Object} opts
|
|
170
|
+
* @param {import('../../external/ai/AiProvider.js').AiProvider} [opts.aiProvider]
|
|
171
|
+
*/
|
|
172
|
+
constructor({ aiProvider = null } = {}) {
|
|
173
|
+
this.#aiProvider = aiProvider;
|
|
174
|
+
this.#logger = Logger.getInstance();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 分类用户消息意图
|
|
179
|
+
*
|
|
180
|
+
* @param {string} text — 用户原始消息文本
|
|
181
|
+
* @param {Object} [context] — 额外上下文 (如对话历史、最近操作)
|
|
182
|
+
* @returns {Promise<ClassificationResult>}
|
|
183
|
+
*/
|
|
184
|
+
async classify(text, context = {}) {
|
|
185
|
+
if (!text?.trim()) {
|
|
186
|
+
return { intent: Intent.BOT_AGENT, confidence: 1, reasoning: '空消息', method: 'rule' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const trimmed = text.trim();
|
|
190
|
+
|
|
191
|
+
// ── Layer 1: 系统操作 (硬编码,零延迟) ──
|
|
192
|
+
const sysMatch = this.#matchSystem(trimmed);
|
|
193
|
+
if (sysMatch) return sysMatch;
|
|
194
|
+
|
|
195
|
+
// ── Layer 2: 强信号关键词匹配 ──
|
|
196
|
+
const ruleMatch = this.#matchRules(trimmed);
|
|
197
|
+
if (ruleMatch && ruleMatch.confidence >= 0.8) {
|
|
198
|
+
this.#logger.info(`[IntentClassifier] Rule match: ${ruleMatch.intent} (${ruleMatch.confidence})`);
|
|
199
|
+
return ruleMatch;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Layer 3: LLM 分类 (精确,需 AI) ──
|
|
203
|
+
if (this.#aiProvider && this.#aiProvider.name !== 'mock') {
|
|
204
|
+
const llmResult = await this.#classifyWithLLM(trimmed, context);
|
|
205
|
+
if (llmResult) {
|
|
206
|
+
this.#logger.info(`[IntentClassifier] LLM: ${llmResult.intent} (${llmResult.confidence}) — ${llmResult.reasoning}`);
|
|
207
|
+
return llmResult;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Fallback: 使用规则结果或默认 bot_agent ──
|
|
212
|
+
if (ruleMatch) return ruleMatch;
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
intent: Intent.BOT_AGENT,
|
|
216
|
+
confidence: 0.5,
|
|
217
|
+
reasoning: '无法确定意图,默认使用 Bot Agent',
|
|
218
|
+
method: 'fallback',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── 私有方法 ────────────────────────────────
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 系统操作匹配 (优先级最高)
|
|
226
|
+
*/
|
|
227
|
+
#matchSystem(text) {
|
|
228
|
+
for (const rule of SYSTEM_RULES) {
|
|
229
|
+
if (rule.pattern.test(text)) {
|
|
230
|
+
return {
|
|
231
|
+
intent: Intent.SYSTEM,
|
|
232
|
+
confidence: 1,
|
|
233
|
+
reasoning: `系统操作: ${rule.action}`,
|
|
234
|
+
method: 'rule',
|
|
235
|
+
action: rule.action,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 强信号关键词匹配
|
|
244
|
+
*/
|
|
245
|
+
#matchRules(text) {
|
|
246
|
+
let ideScore = 0;
|
|
247
|
+
let botScore = 0;
|
|
248
|
+
const ideMatches = [];
|
|
249
|
+
const botMatches = [];
|
|
250
|
+
|
|
251
|
+
for (const re of IDE_STRONG_SIGNALS) {
|
|
252
|
+
if (re.test(text)) {
|
|
253
|
+
ideScore++;
|
|
254
|
+
ideMatches.push(re.source.slice(0, 30));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const re of BOT_STRONG_SIGNALS) {
|
|
259
|
+
if (re.test(text)) {
|
|
260
|
+
botScore++;
|
|
261
|
+
botMatches.push(re.source.slice(0, 30));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 没有任何匹配 → null (交给 LLM)
|
|
266
|
+
if (ideScore === 0 && botScore === 0) return null;
|
|
267
|
+
|
|
268
|
+
// 明确的分差
|
|
269
|
+
if (ideScore > botScore && ideScore >= 2) {
|
|
270
|
+
return {
|
|
271
|
+
intent: Intent.IDE_AGENT,
|
|
272
|
+
confidence: Math.min(0.6 + ideScore * 0.1, 0.95),
|
|
273
|
+
reasoning: `IDE 信号: ${ideMatches.join(', ')}`,
|
|
274
|
+
method: 'rule',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (botScore > ideScore && botScore >= 2) {
|
|
279
|
+
return {
|
|
280
|
+
intent: Intent.BOT_AGENT,
|
|
281
|
+
confidence: Math.min(0.6 + botScore * 0.1, 0.95),
|
|
282
|
+
reasoning: `Bot 信号: ${botMatches.join(', ')}`,
|
|
283
|
+
method: 'rule',
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 分差不明确 → 返回低置信度结果 (让 LLM 决定)
|
|
288
|
+
const winner = ideScore > botScore ? Intent.IDE_AGENT : Intent.BOT_AGENT;
|
|
289
|
+
return {
|
|
290
|
+
intent: winner,
|
|
291
|
+
confidence: 0.5 + Math.abs(ideScore - botScore) * 0.1,
|
|
292
|
+
reasoning: `弱信号: IDE=${ideScore} Bot=${botScore}`,
|
|
293
|
+
method: 'rule',
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* LLM 分类
|
|
299
|
+
*/
|
|
300
|
+
async #classifyWithLLM(text, context = {}) {
|
|
301
|
+
try {
|
|
302
|
+
const userMsg = context.recentHistory
|
|
303
|
+
? `最近对话:\n${context.recentHistory}\n\n当前消息: "${text}"`
|
|
304
|
+
: `用户消息: "${text}"`;
|
|
305
|
+
|
|
306
|
+
const result = await this.#aiProvider.chatWithTools(userMsg, {
|
|
307
|
+
messages: [],
|
|
308
|
+
toolSchemas: [CLASSIFY_SCHEMA],
|
|
309
|
+
toolChoice: 'required',
|
|
310
|
+
systemPrompt: CLASSIFY_SYSTEM_PROMPT,
|
|
311
|
+
temperature: 0,
|
|
312
|
+
maxTokens: 200,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const call = result.functionCalls?.[0]?.args;
|
|
316
|
+
if (call?.intent) {
|
|
317
|
+
return {
|
|
318
|
+
intent: call.intent,
|
|
319
|
+
confidence: call.confidence ?? 0.8,
|
|
320
|
+
reasoning: call.reasoning || 'LLM 分类',
|
|
321
|
+
method: 'llm',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
} catch (err) {
|
|
325
|
+
this.#logger.warn(`[IntentClassifier] LLM error: ${err.message}`);
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export default IntentClassifier;
|