autosnippet 3.2.4 → 3.2.6
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/README.md +2 -4
- package/bin/cli.js +164 -145
- package/config/constitution.yaml +2 -0
- package/dashboard/dist/assets/{index-DNOHYBhy.css → index-BaGY7kJI.css} +1 -1
- package/dashboard/dist/assets/{index-6itPuGFl.js → index-DfHY_3ln.js} +25 -25
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/CliLogger.js +78 -0
- package/lib/cli/SetupService.js +9 -718
- package/lib/cli/UpgradeService.js +23 -398
- package/lib/cli/deploy/FileDeployer.js +562 -0
- package/lib/cli/deploy/FileManifest.js +272 -0
- package/lib/external/mcp/McpServer.js +22 -26
- package/lib/external/mcp/autoApproveInjector.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +5 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +25 -3
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +6 -6
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +4 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +5 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +89 -44
- package/lib/external/mcp/handlers/consolidated.js +8 -9
- package/lib/external/mcp/handlers/dimension-complete-external.js +4 -4
- package/lib/external/mcp/handlers/guard.js +283 -5
- package/lib/external/mcp/handlers/task.js +183 -9
- package/lib/external/mcp/tools.js +32 -81
- package/lib/http/routes/task.js +55 -0
- package/lib/service/chat/AnalystAgent.js +12 -12
- package/lib/service/chat/ChatAgent.js +227 -545
- package/lib/service/chat/ChatAgentPrompts.js +9 -11
- package/lib/service/chat/ContextWindow.js +2 -296
- package/lib/service/chat/EpisodicConsolidator.js +15 -15
- package/lib/service/chat/ExplorationTracker.js +1262 -0
- package/lib/service/chat/HandoffProtocol.js +8 -9
- package/lib/service/chat/Memory.js +4 -0
- package/lib/service/chat/ProducerAgent.js +9 -6
- package/lib/service/chat/ProjectSemanticMemory.js +4 -0
- package/lib/service/chat/ReasoningTrace.js +182 -0
- package/lib/service/chat/WorkingMemory.js +4 -0
- package/lib/service/chat/memory/ActiveContext.js +910 -0
- package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
- package/lib/service/chat/memory/PersistentMemory.js +450 -0
- package/lib/service/chat/memory/SessionStore.js +896 -0
- package/lib/service/chat/memory/index.js +13 -0
- package/lib/service/chat/tools/ast-graph.js +17 -16
- package/lib/service/cursor/AgentInstructionsGenerator.js +76 -47
- package/lib/service/cursor/FileProtection.js +4 -1
- package/lib/service/guard/GuardCheckEngine.js +10 -3
- package/lib/service/task/TaskGraphService.js +3 -3
- package/lib/shared/LanguageService.js +2 -1
- package/package.json +1 -1
- package/skills/autosnippet-intent/SKILL.md +1 -3
- package/skills/autosnippet-recipes/SKILL.md +1 -3
- package/templates/claude-code/commands/prime.md +19 -0
- package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
- package/templates/claude-code/settings.json +21 -0
- package/templates/copilot-instructions.md +64 -177
- package/templates/cursor-hooks/commands/prime.md +12 -0
- package/templates/cursor-hooks/hooks/session-start.sh +10 -0
- package/templates/cursor-hooks/hooks.json +11 -0
- package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
- package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
- package/lib/external/mcp/handlers/decide.js +0 -109
- package/lib/external/mcp/handlers/ready.js +0 -42
- package/lib/service/chat/ReasoningLayer.js +0 -888
- package/templates/claude-hooks.yaml +0 -19
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActiveContext — 合并 WorkingMemory + ReasoningTrace 为统一的会话工作记忆
|
|
3
|
+
*
|
|
4
|
+
* 设计来源: docs/copilot/memory-system-redesign.md §4.3, §6.2
|
|
5
|
+
*
|
|
6
|
+
* 三个内部子区:
|
|
7
|
+
* 1. Scratchpad — Agent 通过 note_finding 主动标记的发现 (不可压缩)
|
|
8
|
+
* 2. ObservationLog — 每轮 ReAct 记录 (合并原 RT.rounds + WM.observations,滑动窗口压缩)
|
|
9
|
+
* 3. Plan — 从 ReasoningTrace 继承的规划追踪
|
|
10
|
+
*
|
|
11
|
+
* 替代关系:
|
|
12
|
+
* WorkingMemory.js → Scratchpad + 工具压缩策略 + buildContext + distill
|
|
13
|
+
* ReasoningTrace.js → rounds + plan + thoughts + extractAndSetPlan + observations
|
|
14
|
+
*
|
|
15
|
+
* 兼容性:
|
|
16
|
+
* - 提供所有 ReasoningTrace 和 WorkingMemory 的公共方法
|
|
17
|
+
* - ExplorationTracker 可直接使用 ActiveContext 作为 trace 参数 (L5 缓解)
|
|
18
|
+
* - MemoryCoordinator 通过 createDimensionScope 创建实例
|
|
19
|
+
*
|
|
20
|
+
* 生命周期: 单次 execute() 调用 (由 MemoryCoordinator 管理创建/蒸馏/销毁)
|
|
21
|
+
*
|
|
22
|
+
* @module ActiveContext
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import Logger from '../../../infrastructure/logging/Logger.js';
|
|
26
|
+
|
|
27
|
+
// ═══════════════════════════════════════════════════════════
|
|
28
|
+
// §1: 工具压缩策略 (从 WorkingMemory 迁入)
|
|
29
|
+
// ═══════════════════════════════════════════════════════════
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 工具特化压缩策略 — 不同工具返回不同结构,压缩时保留最有价值的部分
|
|
33
|
+
*/
|
|
34
|
+
const TOOL_COMPRESS_STRATEGIES = {
|
|
35
|
+
search_project_code(result) {
|
|
36
|
+
if (typeof result !== 'object') {
|
|
37
|
+
return String(result).substring(0, 600);
|
|
38
|
+
}
|
|
39
|
+
const matches = result.matches || [];
|
|
40
|
+
const batchResults = result.batchResults || {};
|
|
41
|
+
const lines = [];
|
|
42
|
+
if (matches.length > 0) {
|
|
43
|
+
lines.push(`搜索到 ${matches.length} 个匹配`);
|
|
44
|
+
const fileGroups = {};
|
|
45
|
+
for (const m of matches) {
|
|
46
|
+
if (!fileGroups[m.file]) fileGroups[m.file] = [];
|
|
47
|
+
fileGroups[m.file].push(m.line);
|
|
48
|
+
}
|
|
49
|
+
for (const [file, lineNums] of Object.entries(fileGroups).slice(0, 8)) {
|
|
50
|
+
lines.push(` ${file}: L${lineNums.slice(0, 3).join(',')}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const [pattern, sub] of Object.entries(batchResults).slice(0, 5)) {
|
|
54
|
+
const subMatches = sub.matches || [];
|
|
55
|
+
lines.push(` [${pattern}] ${subMatches.length} 个匹配`);
|
|
56
|
+
for (const m of subMatches.slice(0, 3)) {
|
|
57
|
+
lines.push(` ${m.file}:${m.line}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return lines.join('\n');
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
read_project_file(result) {
|
|
64
|
+
if (typeof result !== 'object') {
|
|
65
|
+
return String(result).substring(0, 600);
|
|
66
|
+
}
|
|
67
|
+
if (result.files) {
|
|
68
|
+
const lines = [`读取 ${result.files.length} 个文件`];
|
|
69
|
+
for (const f of result.files.slice(0, 5)) {
|
|
70
|
+
const totalLines = (f.content || '').split('\n').length;
|
|
71
|
+
lines.push(` ${f.path} (${totalLines} 行)`);
|
|
72
|
+
}
|
|
73
|
+
return lines.join('\n');
|
|
74
|
+
}
|
|
75
|
+
const content = result.content || String(result);
|
|
76
|
+
const totalLines = content.split('\n').length;
|
|
77
|
+
return `文件 ${result.path || '?'} (${totalLines} 行)`;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
get_class_info(result) {
|
|
81
|
+
if (typeof result !== 'object') {
|
|
82
|
+
return String(result).substring(0, 600);
|
|
83
|
+
}
|
|
84
|
+
const lines = [`类 ${result.className || '?'}`];
|
|
85
|
+
if (result.superClass) lines.push(` 继承: ${result.superClass}`);
|
|
86
|
+
if (result.protocols?.length) lines.push(` 协议: ${result.protocols.join(', ')}`);
|
|
87
|
+
if (result.methods?.length) lines.push(` 方法数: ${result.methods.length}`);
|
|
88
|
+
if (result.properties?.length) lines.push(` 属性数: ${result.properties.length}`);
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
get_class_hierarchy(result) {
|
|
93
|
+
if (typeof result !== 'object') {
|
|
94
|
+
return String(result).substring(0, 600);
|
|
95
|
+
}
|
|
96
|
+
const classes = result.classes || result.hierarchy || [];
|
|
97
|
+
return `类层级: ${Array.isArray(classes) ? classes.length : 0} 个类`;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
get_project_overview(result) {
|
|
101
|
+
if (typeof result !== 'object') {
|
|
102
|
+
return String(result).substring(0, 800);
|
|
103
|
+
}
|
|
104
|
+
return JSON.stringify(result).substring(0, 800);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
list_project_structure(result) {
|
|
108
|
+
if (typeof result !== 'object') {
|
|
109
|
+
return String(result).substring(0, 600);
|
|
110
|
+
}
|
|
111
|
+
const entries = result.entries || result.children || [];
|
|
112
|
+
return `目录结构: ${Array.isArray(entries) ? entries.length : 0} 个条目`;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 默认压缩 — 截断到 maxChars
|
|
118
|
+
*/
|
|
119
|
+
function defaultCompress(result, maxChars = 600) {
|
|
120
|
+
const str = typeof result === 'string' ? result : JSON.stringify(result);
|
|
121
|
+
if (str.length <= maxChars) return str;
|
|
122
|
+
return `${str.substring(0, maxChars)}…(truncated)`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ═══════════════════════════════════════════════════════════
|
|
126
|
+
// §2: ActiveContext 类
|
|
127
|
+
// ═══════════════════════════════════════════════════════════
|
|
128
|
+
|
|
129
|
+
export class ActiveContext {
|
|
130
|
+
// ── 子区 1: Scratchpad (从 WorkingMemory 继承, 不可压缩) ──
|
|
131
|
+
/** @type {Array<{finding: string, evidence: string, importance: number, round: number}>} */
|
|
132
|
+
#scratchpad = [];
|
|
133
|
+
|
|
134
|
+
// ── 子区 2: ObservationLog (合并 RT.rounds + WM.observations) ──
|
|
135
|
+
/** @type {Array<Round>} */
|
|
136
|
+
#rounds = [];
|
|
137
|
+
/** @type {Round|null} */
|
|
138
|
+
#currentRound = null;
|
|
139
|
+
|
|
140
|
+
// ── WM 滑动窗口 (保留最近 N 轮原始结果,旧的压缩) ──
|
|
141
|
+
/** @type {Array<{toolName: string, result: any, round: number, timestamp: number}>} */
|
|
142
|
+
#recentObservations = [];
|
|
143
|
+
/** @type {Array<{toolName: string, round: number, summary: string}>} */
|
|
144
|
+
#compressedObservations = [];
|
|
145
|
+
|
|
146
|
+
// ── 子区 3: Plan (从 ReasoningTrace 继承) ──
|
|
147
|
+
/** @type {Plan|null} */
|
|
148
|
+
#plan = null;
|
|
149
|
+
/** @type {Array<Plan>} */
|
|
150
|
+
#planHistory = [];
|
|
151
|
+
|
|
152
|
+
// ── 配置 ──
|
|
153
|
+
/** @type {number} 保留最近 N 轮原始观察 */
|
|
154
|
+
#maxRecentRounds;
|
|
155
|
+
/** @type {boolean} 轻量模式 (User Chat: 仅 RT 功能,禁用 WM 压缩/Scratchpad) */
|
|
156
|
+
#lightweight;
|
|
157
|
+
/** @type {number} 总观察计数 */
|
|
158
|
+
#totalObservations = 0;
|
|
159
|
+
|
|
160
|
+
/** @type {import('../../../infrastructure/logging/Logger.js').default} */
|
|
161
|
+
#logger;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {object} [options]
|
|
165
|
+
* @param {number} [options.maxRecentRounds=3] - 保留最近 N 轮原始结果 (WM 滑动窗口)
|
|
166
|
+
* @param {boolean} [options.lightweight=false] - 轻量模式: 跳过 WM 的压缩/Scratchpad 逻辑 (D5)
|
|
167
|
+
*/
|
|
168
|
+
constructor(options = {}) {
|
|
169
|
+
this.#maxRecentRounds = options.maxRecentRounds ?? 3;
|
|
170
|
+
this.#lightweight = options.lightweight ?? false;
|
|
171
|
+
this.#logger = Logger.getInstance();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ═══════════════════════════════════════════════════════
|
|
175
|
+
// §2.1: 轮次管理 (合并 RT.startRound/endRound)
|
|
176
|
+
// ═══════════════════════════════════════════════════════
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 开始新一轮推理
|
|
180
|
+
* @param {number} iteration — 轮次编号
|
|
181
|
+
*/
|
|
182
|
+
startRound(iteration) {
|
|
183
|
+
if (this.#currentRound) {
|
|
184
|
+
this.endRound(); // 安全关闭上一轮
|
|
185
|
+
}
|
|
186
|
+
this.#currentRound = {
|
|
187
|
+
iteration,
|
|
188
|
+
thought: null,
|
|
189
|
+
actions: [],
|
|
190
|
+
observations: [],
|
|
191
|
+
reflection: null,
|
|
192
|
+
roundSummary: null,
|
|
193
|
+
startTime: Date.now(),
|
|
194
|
+
endTime: null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 结束当前轮次
|
|
200
|
+
*/
|
|
201
|
+
endRound() {
|
|
202
|
+
if (this.#currentRound) {
|
|
203
|
+
this.#currentRound.endTime = Date.now();
|
|
204
|
+
this.#rounds.push(this.#currentRound);
|
|
205
|
+
this.#currentRound = null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ═══════════════════════════════════════════════════════
|
|
210
|
+
// §2.2: 记录 (合并 WM.observe + RT.addAction/addObservation)
|
|
211
|
+
// ═══════════════════════════════════════════════════════
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 记录 AI 的推理文本(从 aiResult.text 提取)
|
|
215
|
+
* @param {string} text
|
|
216
|
+
*/
|
|
217
|
+
setThought(text) {
|
|
218
|
+
if (this.#currentRound && text) {
|
|
219
|
+
this.#currentRound.thought = text;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 统一记录一次工具调用 — 合并原 WM.observe() + RT.addAction() + RT.addObservation()
|
|
225
|
+
*
|
|
226
|
+
* @param {string} toolName - 工具名称
|
|
227
|
+
* @param {object} args - 工具参数
|
|
228
|
+
* @param {*} result - 工具返回的原始结果
|
|
229
|
+
* @param {boolean} isNew - 是否发现新信息 (由 ExplorationTracker.recordToolCall 提供)
|
|
230
|
+
*/
|
|
231
|
+
recordToolCall(toolName, args, result, isNew) {
|
|
232
|
+
const round = this.#currentRound?.iteration || 0;
|
|
233
|
+
|
|
234
|
+
// ── RT 部分: Action + Observation ──
|
|
235
|
+
this.#currentRound?.actions.push({ tool: toolName, params: args });
|
|
236
|
+
const observationMeta = ActiveContext.buildObservationMeta(toolName, args, result, isNew);
|
|
237
|
+
this.#currentRound?.observations.push({ tool: toolName, ...observationMeta });
|
|
238
|
+
|
|
239
|
+
// ── WM 部分: 滑动窗口压缩 (非轻量模式) ──
|
|
240
|
+
if (!this.#lightweight) {
|
|
241
|
+
this.#totalObservations++;
|
|
242
|
+
this.#recentObservations.push({
|
|
243
|
+
toolName,
|
|
244
|
+
result,
|
|
245
|
+
round,
|
|
246
|
+
timestamp: Date.now(),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
while (this.#recentObservations.length > this.#maxRecentRounds) {
|
|
250
|
+
const oldest = this.#recentObservations.shift();
|
|
251
|
+
const summary = this.#compressObservation(oldest);
|
|
252
|
+
this.#compressedObservations.push(summary);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 兼容旧 RT API: 记录一次工具调用 (Action only)
|
|
259
|
+
* @param {string} toolName
|
|
260
|
+
* @param {object} params
|
|
261
|
+
*/
|
|
262
|
+
addAction(toolName, params) {
|
|
263
|
+
this.#currentRound?.actions.push({ tool: toolName, params });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 兼容旧 RT API: 记录一次工具结果的结构化观察
|
|
268
|
+
* @param {string} toolName
|
|
269
|
+
* @param {object} meta
|
|
270
|
+
*/
|
|
271
|
+
addObservation(toolName, meta) {
|
|
272
|
+
this.#currentRound?.observations.push({ tool: toolName, ...meta });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 兼容旧 WM API: 记录工具调用结果 (Observe, 仅 WM 滑动窗口)
|
|
277
|
+
* @param {string} toolName
|
|
278
|
+
* @param {*} result
|
|
279
|
+
* @param {number} round
|
|
280
|
+
*/
|
|
281
|
+
observe(toolName, result, round) {
|
|
282
|
+
if (this.#lightweight) return;
|
|
283
|
+
this.#totalObservations++;
|
|
284
|
+
this.#recentObservations.push({ toolName, result, round, timestamp: Date.now() });
|
|
285
|
+
while (this.#recentObservations.length > this.#maxRecentRounds) {
|
|
286
|
+
const oldest = this.#recentObservations.shift();
|
|
287
|
+
const summary = this.#compressObservation(oldest);
|
|
288
|
+
this.#compressedObservations.push(summary);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 记录反思内容 (ExplorationTracker 使用, L5 修复)
|
|
294
|
+
* @param {string} text
|
|
295
|
+
*/
|
|
296
|
+
setReflection(text) {
|
|
297
|
+
if (this.#currentRound && text) {
|
|
298
|
+
this.#currentRound.reflection = text;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 记录轮次摘要
|
|
304
|
+
* @param {object} summary — { newInfoCount, totalCalls, submits, cumulativeFiles, cumulativePatterns }
|
|
305
|
+
*/
|
|
306
|
+
setRoundSummary(summary) {
|
|
307
|
+
if (this.#currentRound) {
|
|
308
|
+
this.#currentRound.roundSummary = summary;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ═══════════════════════════════════════════════════════
|
|
313
|
+
// §2.3: Scratchpad (从 WorkingMemory 继承)
|
|
314
|
+
// ═══════════════════════════════════════════════════════
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Agent 主动记录关键发现 (note_finding 工具入口)
|
|
318
|
+
*
|
|
319
|
+
* @param {string} finding - 关键发现描述
|
|
320
|
+
* @param {string} [evidence] - 证据 (文件路径:行号)
|
|
321
|
+
* @param {number} [importance=5] - 重要性 1-10
|
|
322
|
+
* @param {number} [round=0] - 当前轮次
|
|
323
|
+
*/
|
|
324
|
+
noteKeyFinding(finding, evidence = '', importance = 5, round = 0) {
|
|
325
|
+
this.#scratchpad.push({
|
|
326
|
+
finding,
|
|
327
|
+
evidence,
|
|
328
|
+
importance: Math.min(10, Math.max(1, importance)),
|
|
329
|
+
round,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
this.#logger.debug(
|
|
333
|
+
`[ActiveContext] 📌 noted finding (${importance}/10): ${finding.substring(0, 80)}`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ═══════════════════════════════════════════════════════
|
|
338
|
+
// §2.4: Plan (从 ReasoningTrace 继承)
|
|
339
|
+
// ═══════════════════════════════════════════════════════
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 从 AI 响应文本中提取计划,自动调用 setPlan/updatePlan
|
|
343
|
+
* @param {string} text — AI 完整响应文本
|
|
344
|
+
* @param {number} iteration — 当前轮次
|
|
345
|
+
* @returns {boolean} — 是否成功提取到计划
|
|
346
|
+
*/
|
|
347
|
+
extractAndSetPlan(text, iteration) {
|
|
348
|
+
const planText = this.#extractPlanFromText(text);
|
|
349
|
+
if (!planText) return false;
|
|
350
|
+
|
|
351
|
+
if (this.#plan) {
|
|
352
|
+
this.#updatePlan(planText, iteration);
|
|
353
|
+
} else {
|
|
354
|
+
this.#setPlan(planText, iteration);
|
|
355
|
+
}
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 直接设置计划 (公开接口,供 ExplorationTracker 和测试使用)
|
|
361
|
+
* @param {string} planText
|
|
362
|
+
* @param {number} iteration
|
|
363
|
+
*/
|
|
364
|
+
setPlan(planText, iteration) {
|
|
365
|
+
this.#setPlan(planText, iteration);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 更新计划 (保留旧 plan 到 history)
|
|
370
|
+
* @param {string} replanText
|
|
371
|
+
* @param {number} iteration
|
|
372
|
+
*/
|
|
373
|
+
updatePlan(replanText, iteration) {
|
|
374
|
+
this.#updatePlan(replanText, iteration);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 获取当前计划 (只读副本)
|
|
379
|
+
* @returns {Plan|null}
|
|
380
|
+
*/
|
|
381
|
+
getPlan() {
|
|
382
|
+
if (!this.#plan) return null;
|
|
383
|
+
return {
|
|
384
|
+
...this.#plan,
|
|
385
|
+
steps: this.#plan.steps.map((s) => ({ ...s })),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 获取计划步骤的可变引用 (ExplorationTracker.updatePlanProgress 使用)
|
|
391
|
+
* @returns {Array<PlanStep>}
|
|
392
|
+
*/
|
|
393
|
+
getPlanStepsMutable() {
|
|
394
|
+
return this.#plan?.steps || [];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 获取计划历史 (F7)
|
|
399
|
+
* @returns {Array<Plan>}
|
|
400
|
+
*/
|
|
401
|
+
getPlanHistory() {
|
|
402
|
+
return this.#planHistory.map((p) => ({ ...p, steps: p.steps.map((s) => ({ ...s })) }));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 获取当前轮次的 actions (ExplorationTracker.updatePlanProgress 使用, L5 修复)
|
|
407
|
+
* @returns {Array<{tool: string, params: object}>}
|
|
408
|
+
*/
|
|
409
|
+
getCurrentRoundActions() {
|
|
410
|
+
return this.#currentRound?.actions || [];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 获取当前轮次的 iteration 编号 (F8)
|
|
415
|
+
* @returns {number|null}
|
|
416
|
+
*/
|
|
417
|
+
getCurrentIteration() {
|
|
418
|
+
return this.#currentRound?.iteration || null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ═══════════════════════════════════════════════════════
|
|
422
|
+
// §2.5: 上下文构建 (合并 WM.buildContext, 增加预算控制)
|
|
423
|
+
// ═══════════════════════════════════════════════════════
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 构建当前工作记忆的上下文快照
|
|
427
|
+
* 用于注入到 system prompt 或 user nudge 中
|
|
428
|
+
*
|
|
429
|
+
* @param {number} [tokenBudget=Infinity] - token 预算 (新增: 预算控制)
|
|
430
|
+
* @returns {string} Markdown 格式的上下文块,空字符串表示无内容
|
|
431
|
+
*/
|
|
432
|
+
buildContext(tokenBudget = Infinity) {
|
|
433
|
+
if (this.#lightweight) return '';
|
|
434
|
+
|
|
435
|
+
const parts = [];
|
|
436
|
+
let remaining = tokenBudget;
|
|
437
|
+
|
|
438
|
+
// §1: Scratchpad (最高优先级 — 不会被压缩)
|
|
439
|
+
if (this.#scratchpad.length > 0) {
|
|
440
|
+
const sorted = [...this.#scratchpad].sort((a, b) => b.importance - a.importance);
|
|
441
|
+
const scratchLines = ['## 📌 已确认的关键发现'];
|
|
442
|
+
for (const f of sorted) {
|
|
443
|
+
const badge = f.importance >= 8 ? '⚠️' : f.importance >= 5 ? '📋' : '💡';
|
|
444
|
+
let line = `- ${badge} [${f.importance}/10] ${f.finding}`;
|
|
445
|
+
if (f.evidence) line += ` (${f.evidence})`;
|
|
446
|
+
scratchLines.push(line);
|
|
447
|
+
}
|
|
448
|
+
const scratchSection = scratchLines.join('\n');
|
|
449
|
+
const scratchTokens = this.#estimateTokens(scratchSection);
|
|
450
|
+
if (scratchTokens <= remaining) {
|
|
451
|
+
parts.push(scratchSection);
|
|
452
|
+
remaining -= scratchTokens;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// §2: 压缩后的旧观察摘要 (中等优先级)
|
|
457
|
+
if (this.#compressedObservations.length > 0 && remaining > 100) {
|
|
458
|
+
const obsLines = ['## 📂 之前的探索摘要'];
|
|
459
|
+
const maxItems = Math.min(15, this.#compressedObservations.length);
|
|
460
|
+
const recent = this.#compressedObservations.slice(-maxItems);
|
|
461
|
+
|
|
462
|
+
for (const s of recent) {
|
|
463
|
+
const line = `- [R${s.round}|${s.toolName}] ${s.summary.substring(0, 200)}`;
|
|
464
|
+
const lineTokens = this.#estimateTokens(line);
|
|
465
|
+
if (lineTokens > remaining) break;
|
|
466
|
+
obsLines.push(line);
|
|
467
|
+
remaining -= lineTokens;
|
|
468
|
+
}
|
|
469
|
+
if (this.#compressedObservations.length > maxItems) {
|
|
470
|
+
obsLines.push(` …(还有 ${this.#compressedObservations.length - maxItems} 条更早的观察)`);
|
|
471
|
+
}
|
|
472
|
+
if (obsLines.length > 1) {
|
|
473
|
+
parts.push(obsLines.join('\n'));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return parts.join('\n');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ═══════════════════════════════════════════════════════
|
|
481
|
+
// §2.6: 蒸馏 (合并 WM.distill, 增强版 — 含 plan + stats)
|
|
482
|
+
// ═══════════════════════════════════════════════════════
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* 蒸馏 ActiveContext 为结构化报告
|
|
486
|
+
* 在 Agent execute 结束时调用,结果写入 SessionStore
|
|
487
|
+
*
|
|
488
|
+
* @returns {DistilledContext}
|
|
489
|
+
*/
|
|
490
|
+
distill() {
|
|
491
|
+
return {
|
|
492
|
+
keyFindings: this.#scratchpad.map((f) => ({
|
|
493
|
+
finding: f.finding,
|
|
494
|
+
evidence: f.evidence,
|
|
495
|
+
importance: f.importance,
|
|
496
|
+
})),
|
|
497
|
+
toolCallSummary: this.#compressedObservations.map(
|
|
498
|
+
(s) => `[${s.toolName}] ${s.summary.substring(0, 150)}`
|
|
499
|
+
),
|
|
500
|
+
stats: this.getStats(),
|
|
501
|
+
plan: this.getPlan(),
|
|
502
|
+
totalObservations: this.#totalObservations,
|
|
503
|
+
compressedCount: this.#compressedObservations.length,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ═══════════════════════════════════════════════════════
|
|
508
|
+
// §2.7: 分析方法 (从 ReasoningTrace 继承)
|
|
509
|
+
// ═══════════════════════════════════════════════════════
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* 获取所有有 Thought 的轮次
|
|
513
|
+
* @returns {Array<{iteration: number, thought: string}>}
|
|
514
|
+
*/
|
|
515
|
+
getThoughts() {
|
|
516
|
+
return this.#rounds
|
|
517
|
+
.filter((r) => r.thought)
|
|
518
|
+
.map((r) => ({ iteration: r.iteration, thought: r.thought }));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* 获取最近 N 轮的紧凑摘要 (ExplorationTracker.#checkReflection 使用)
|
|
523
|
+
* @param {number} [n=3] — 回看轮数
|
|
524
|
+
* @returns {object|null}
|
|
525
|
+
*/
|
|
526
|
+
getRecentSummary(n = 3) {
|
|
527
|
+
const recent = this.#rounds.slice(-n);
|
|
528
|
+
if (recent.length === 0) return null;
|
|
529
|
+
|
|
530
|
+
const thoughts = recent
|
|
531
|
+
.filter((r) => r.thought)
|
|
532
|
+
.map((r) => (r.thought.length > 100 ? `${r.thought.substring(0, 100)}…` : r.thought));
|
|
533
|
+
|
|
534
|
+
const tools = recent.flatMap((r) => r.actions.map((a) => a.tool));
|
|
535
|
+
|
|
536
|
+
const newInfoCount = recent.reduce(
|
|
537
|
+
(c, r) => c + r.observations.filter((o) => o.gotNewInfo).length,
|
|
538
|
+
0
|
|
539
|
+
);
|
|
540
|
+
const totalObs = recent.reduce((c, r) => c + r.observations.length, 0);
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
roundCount: recent.length,
|
|
544
|
+
thoughts,
|
|
545
|
+
toolCalls: tools,
|
|
546
|
+
newInfoRatio: totalObs > 0 ? newInfoCount / totalObs : 0,
|
|
547
|
+
lastIteration: recent[recent.length - 1].iteration,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* 统计指标 (ExplorationTracker.getQualityMetrics 使用)
|
|
553
|
+
* @returns {object}
|
|
554
|
+
*/
|
|
555
|
+
getStats() {
|
|
556
|
+
return {
|
|
557
|
+
totalRounds: this.#rounds.length,
|
|
558
|
+
thoughtCount: this.#rounds.filter((r) => r.thought).length,
|
|
559
|
+
totalActions: this.#rounds.reduce((c, r) => c + r.actions.length, 0),
|
|
560
|
+
totalObservations: this.#rounds.reduce((c, r) => c + r.observations.length, 0),
|
|
561
|
+
reflectionCount: this.#rounds.filter((r) => r.reflection).length,
|
|
562
|
+
totalDurationMs: this.#rounds.reduce(
|
|
563
|
+
(d, r) => d + ((r.endTime || Date.now()) - r.startTime),
|
|
564
|
+
0
|
|
565
|
+
),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// ═══════════════════════════════════════════════════════
|
|
570
|
+
// §2.8: Scratchpad 查询 (从 WorkingMemory 继承)
|
|
571
|
+
// ═══════════════════════════════════════════════════════
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* 获取 scratchpad 中的关键发现数量
|
|
575
|
+
* @returns {number}
|
|
576
|
+
*/
|
|
577
|
+
get scratchpadSize() {
|
|
578
|
+
return this.#scratchpad.length;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* 获取总观察数
|
|
583
|
+
* @returns {number}
|
|
584
|
+
*/
|
|
585
|
+
get totalObservations() {
|
|
586
|
+
return this.#totalObservations;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* 获取 scratchpad 中的高重要性发现
|
|
591
|
+
* @param {number} [minImportance=7]
|
|
592
|
+
* @returns {Array<{finding: string, evidence: string, importance: number}>}
|
|
593
|
+
*/
|
|
594
|
+
getHighPriorityFindings(minImportance = 7) {
|
|
595
|
+
return this.#scratchpad
|
|
596
|
+
.filter((f) => f.importance >= minImportance)
|
|
597
|
+
.sort((a, b) => b.importance - a.importance);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ═══════════════════════════════════════════════════════
|
|
601
|
+
// §2.9: 序列化 (从 ReasoningTrace 继承)
|
|
602
|
+
// ═══════════════════════════════════════════════════════
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* 可序列化输出
|
|
606
|
+
* @returns {object}
|
|
607
|
+
*/
|
|
608
|
+
toJSON() {
|
|
609
|
+
return {
|
|
610
|
+
rounds: this.#rounds.map((r) => ({ ...r })),
|
|
611
|
+
stats: this.getStats(),
|
|
612
|
+
scratchpad: this.#scratchpad.map((f) => ({ ...f })),
|
|
613
|
+
compressedObservations: this.#compressedObservations.length,
|
|
614
|
+
totalObservations: this.#totalObservations,
|
|
615
|
+
...(this.#plan
|
|
616
|
+
? {
|
|
617
|
+
plan: {
|
|
618
|
+
text: this.#plan.text,
|
|
619
|
+
steps: this.#plan.steps.map((s) => ({ ...s })),
|
|
620
|
+
createdAtIteration: this.#plan.createdAtIteration,
|
|
621
|
+
lastUpdatedAtIteration: this.#plan.lastUpdatedAtIteration,
|
|
622
|
+
},
|
|
623
|
+
planHistory: this.#planHistory.length,
|
|
624
|
+
}
|
|
625
|
+
: {}),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* 从 JSON 恢复 ActiveContext (断点续传)
|
|
631
|
+
* @param {object} json - toJSON() 的输出
|
|
632
|
+
* @returns {ActiveContext}
|
|
633
|
+
*/
|
|
634
|
+
static fromJSON(json) {
|
|
635
|
+
const ctx = new ActiveContext();
|
|
636
|
+
if (json.rounds) {
|
|
637
|
+
ctx.#rounds = json.rounds.map((r) => ({ ...r }));
|
|
638
|
+
}
|
|
639
|
+
if (json.scratchpad) {
|
|
640
|
+
ctx.#scratchpad = json.scratchpad.map((f) => ({ ...f }));
|
|
641
|
+
}
|
|
642
|
+
if (json.totalObservations) {
|
|
643
|
+
ctx.#totalObservations = json.totalObservations;
|
|
644
|
+
}
|
|
645
|
+
if (json.plan) {
|
|
646
|
+
ctx.#plan = {
|
|
647
|
+
text: json.plan.text,
|
|
648
|
+
steps: json.plan.steps.map((s) => ({ ...s })),
|
|
649
|
+
createdAtIteration: json.plan.createdAtIteration,
|
|
650
|
+
lastUpdatedAtIteration: json.plan.lastUpdatedAtIteration,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
return ctx;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* 清空 ActiveContext — 释放内存
|
|
658
|
+
*/
|
|
659
|
+
clear() {
|
|
660
|
+
this.#scratchpad.length = 0;
|
|
661
|
+
this.#rounds.length = 0;
|
|
662
|
+
this.#currentRound = null;
|
|
663
|
+
this.#recentObservations.length = 0;
|
|
664
|
+
this.#compressedObservations.length = 0;
|
|
665
|
+
this.#plan = null;
|
|
666
|
+
this.#planHistory.length = 0;
|
|
667
|
+
this.#totalObservations = 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ═══════════════════════════════════════════════════════
|
|
671
|
+
// §2.10: 静态工具 (从 ReasoningTrace 迁入)
|
|
672
|
+
// ═══════════════════════════════════════════════════════
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* 从工具执行结果构建结构化观察元数据
|
|
676
|
+
* 不改变工具结果传给 AI 的内容,只影响推理链记录
|
|
677
|
+
*
|
|
678
|
+
* @param {string} toolName
|
|
679
|
+
* @param {object} args
|
|
680
|
+
* @param {*} result
|
|
681
|
+
* @param {boolean} isNew — 由 ExplorationTracker.recordToolCall 提供
|
|
682
|
+
* @returns {{ gotNewInfo: boolean, resultType: string, keyFacts: string[], resultSize: number }}
|
|
683
|
+
*/
|
|
684
|
+
static buildObservationMeta(toolName, args, result, isNew) {
|
|
685
|
+
const meta = {
|
|
686
|
+
gotNewInfo: isNew,
|
|
687
|
+
resultType: 'unknown',
|
|
688
|
+
keyFacts: [],
|
|
689
|
+
resultSize: 0,
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result || '');
|
|
693
|
+
meta.resultSize = resultStr.length;
|
|
694
|
+
|
|
695
|
+
switch (toolName) {
|
|
696
|
+
case 'search_project_code': {
|
|
697
|
+
meta.resultType = 'search';
|
|
698
|
+
const matches = result?.matches || [];
|
|
699
|
+
const batchResults = result?.batchResults;
|
|
700
|
+
const totalMatches = batchResults
|
|
701
|
+
? Object.values(batchResults).reduce((s, br) => s + (br.matches?.length || 0), 0)
|
|
702
|
+
: matches.length;
|
|
703
|
+
meta.keyFacts.push(`${totalMatches} matches found`);
|
|
704
|
+
if (isNew) meta.keyFacts.push('new files discovered');
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
case 'read_project_file': {
|
|
708
|
+
meta.resultType = 'file_content';
|
|
709
|
+
const fp = args?.filePath || '';
|
|
710
|
+
const fps = args?.filePaths || [];
|
|
711
|
+
const allPaths = fp ? [fp, ...fps] : fps;
|
|
712
|
+
meta.keyFacts.push(`read ${allPaths.length} file(s)`);
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
case 'submit_knowledge':
|
|
716
|
+
case 'submit_with_check': {
|
|
717
|
+
meta.resultType = 'submit';
|
|
718
|
+
meta.gotNewInfo = true;
|
|
719
|
+
const status = typeof result === 'object' ? result?.status || 'ok' : 'ok';
|
|
720
|
+
const title = args?.title || '(untitled)';
|
|
721
|
+
meta.keyFacts.push(`submit "${title}": ${status}`);
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
case 'list_project_structure': {
|
|
725
|
+
meta.resultType = 'structure';
|
|
726
|
+
meta.keyFacts.push(`list ${args?.directory || '/'}`);
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
case 'get_class_info':
|
|
730
|
+
case 'get_class_hierarchy':
|
|
731
|
+
case 'get_protocol_info':
|
|
732
|
+
case 'get_method_overrides':
|
|
733
|
+
case 'get_category_map': {
|
|
734
|
+
meta.resultType = 'ast_query';
|
|
735
|
+
const target = args?.className || args?.protocolName || args?.name || '';
|
|
736
|
+
meta.keyFacts.push(`${toolName}(${target})`);
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
case 'get_project_overview': {
|
|
740
|
+
meta.resultType = 'overview';
|
|
741
|
+
meta.keyFacts.push('project overview');
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
case 'semantic_search_code':
|
|
745
|
+
case 'get_file_summary':
|
|
746
|
+
case 'get_previous_analysis': {
|
|
747
|
+
meta.resultType = 'query';
|
|
748
|
+
meta.keyFacts.push(toolName);
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
default: {
|
|
752
|
+
meta.resultType = 'other';
|
|
753
|
+
meta.keyFacts.push(toolName);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return meta;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// ═══════════════════════════════════════════════════════
|
|
761
|
+
// §3: 私有方法
|
|
762
|
+
// ═══════════════════════════════════════════════════════
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* 工具结果压缩 — 使用特化策略 (从 WorkingMemory 迁入)
|
|
766
|
+
* @param {{toolName: string, result: any, round: number}} observation
|
|
767
|
+
* @returns {{toolName: string, round: number, summary: string}}
|
|
768
|
+
*/
|
|
769
|
+
#compressObservation(observation) {
|
|
770
|
+
const strategy = TOOL_COMPRESS_STRATEGIES[observation.toolName];
|
|
771
|
+
let summary;
|
|
772
|
+
try {
|
|
773
|
+
summary = strategy ? strategy(observation.result) : defaultCompress(observation.result);
|
|
774
|
+
} catch {
|
|
775
|
+
summary = defaultCompress(observation.result);
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
toolName: observation.toolName,
|
|
779
|
+
round: observation.round,
|
|
780
|
+
summary,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* 粗糙 token 估算 (1 token ≈ 4 chars)
|
|
786
|
+
* @param {string} text
|
|
787
|
+
* @returns {number}
|
|
788
|
+
*/
|
|
789
|
+
#estimateTokens(text) {
|
|
790
|
+
return Math.ceil((text || '').length / 4);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// ── Plan 内部方法 (从 ReasoningTrace 迁入) ──
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* @param {string} planText
|
|
797
|
+
* @param {number} iteration
|
|
798
|
+
*/
|
|
799
|
+
#setPlan(planText, iteration) {
|
|
800
|
+
this.#plan = {
|
|
801
|
+
text: planText,
|
|
802
|
+
steps: this.#parsePlanSteps(planText),
|
|
803
|
+
createdAtIteration: iteration,
|
|
804
|
+
lastUpdatedAtIteration: iteration,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* @param {string} replanText
|
|
810
|
+
* @param {number} iteration
|
|
811
|
+
*/
|
|
812
|
+
#updatePlan(replanText, iteration) {
|
|
813
|
+
if (!this.#plan) {
|
|
814
|
+
this.#setPlan(replanText, iteration);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
this.#planHistory.push({ ...this.#plan, steps: this.#plan.steps.map((s) => ({ ...s })) });
|
|
818
|
+
this.#plan.text = replanText;
|
|
819
|
+
this.#plan.steps = this.#parsePlanSteps(replanText);
|
|
820
|
+
this.#plan.lastUpdatedAtIteration = iteration;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* 从 AI 文本中解析计划步骤
|
|
825
|
+
* @param {string} text
|
|
826
|
+
* @returns {Array<PlanStep>}
|
|
827
|
+
*/
|
|
828
|
+
#parsePlanSteps(text) {
|
|
829
|
+
if (!text) return [];
|
|
830
|
+
const lines = text.split('\n');
|
|
831
|
+
const steps = [];
|
|
832
|
+
for (const line of lines) {
|
|
833
|
+
const m = line.match(/^\s*(?:\d+[.)]\s*|[-*]\s+)(.+)/);
|
|
834
|
+
if (m && m[1].trim().length > 5) {
|
|
835
|
+
steps.push({
|
|
836
|
+
description: m[1].trim(),
|
|
837
|
+
status: 'pending',
|
|
838
|
+
keywords: this.#extractKeywords(m[1]),
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return steps;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* 从步骤描述中提取关键词
|
|
847
|
+
* @param {string} text
|
|
848
|
+
* @returns {string[]}
|
|
849
|
+
*/
|
|
850
|
+
#extractKeywords(text) {
|
|
851
|
+
const quoted = [...text.matchAll(/[`"']([A-Za-z_]\w{2,})[`"']/g)].map((m) => m[1]);
|
|
852
|
+
const camelCase = [...text.matchAll(/\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/g)].map((m) => m[0]);
|
|
853
|
+
const acronyms = [...text.matchAll(/\b([A-Z]{2,}[a-z]\w+)\b/g)].map((m) => m[0]);
|
|
854
|
+
return [...new Set([...quoted, ...camelCase, ...acronyms])];
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* 从 AI 响应文本中提取"计划"部分
|
|
859
|
+
* @param {string} text
|
|
860
|
+
* @returns {string|null}
|
|
861
|
+
*/
|
|
862
|
+
#extractPlanFromText(text) {
|
|
863
|
+
if (!text || text.length < 30) return null;
|
|
864
|
+
|
|
865
|
+
const searchArea = text.substring(0, 2000);
|
|
866
|
+
|
|
867
|
+
const planMarkers = [
|
|
868
|
+
/(?:探索|分析)?计划[::\s]/i,
|
|
869
|
+
/(?:my\s+)?plan[::\s]/i,
|
|
870
|
+
/步骤[::\s]/i,
|
|
871
|
+
/以下是.*(?:计划|步骤)/i,
|
|
872
|
+
];
|
|
873
|
+
|
|
874
|
+
let planStart = -1;
|
|
875
|
+
for (const marker of planMarkers) {
|
|
876
|
+
const match = searchArea.match(marker);
|
|
877
|
+
if (match) {
|
|
878
|
+
planStart = match.index + match[0].length;
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (planStart === -1) {
|
|
884
|
+
const listMatch = searchArea.match(/\n\s*1[.)]\s+/);
|
|
885
|
+
if (listMatch) planStart = listMatch.index;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (planStart === -1) return null;
|
|
889
|
+
|
|
890
|
+
const remaining = searchArea.substring(planStart);
|
|
891
|
+
const lines = remaining.split('\n');
|
|
892
|
+
const planLines = [];
|
|
893
|
+
let inList = false;
|
|
894
|
+
|
|
895
|
+
for (const line of lines) {
|
|
896
|
+
if (/^\s*(?:\d+[.)]\s+|[-*]\s+)/.test(line)) {
|
|
897
|
+
inList = true;
|
|
898
|
+
planLines.push(line);
|
|
899
|
+
} else if (inList && line.trim() === '') {
|
|
900
|
+
break;
|
|
901
|
+
} else if (inList) {
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
return planLines.length >= 2 ? planLines.join('\n').trim() : null;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
export default ActiveContext;
|