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
|
@@ -38,10 +38,11 @@ import {
|
|
|
38
38
|
taskGuardFullScan,
|
|
39
39
|
taskQualityAudit,
|
|
40
40
|
} from './ChatAgentTasks.js';
|
|
41
|
-
import { ContextWindow, limitToolResult
|
|
41
|
+
import { ContextWindow, limitToolResult } from './ContextWindow.js';
|
|
42
42
|
import { ConversationStore } from './ConversationStore.js';
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
43
|
+
import { ExplorationTracker } from './ExplorationTracker.js';
|
|
44
|
+
import { MemoryCoordinator } from './memory/MemoryCoordinator.js';
|
|
45
|
+
import { ActiveContext } from './memory/ActiveContext.js';
|
|
45
46
|
import { TaskPipeline } from './TaskPipeline.js';
|
|
46
47
|
|
|
47
48
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -82,8 +83,6 @@ export class ChatAgent {
|
|
|
82
83
|
#pipelines = new Map();
|
|
83
84
|
/** @type {string} 缓存的项目概况(每次 execute 刷新一次) */
|
|
84
85
|
#projectBriefingCache = '';
|
|
85
|
-
/** @type {Memory|null} 跨对话轻量记忆 */
|
|
86
|
-
#memory = null;
|
|
87
86
|
/** @type {ConversationStore|null} 对话持久化 */
|
|
88
87
|
#conversations = null;
|
|
89
88
|
/** @type {string|null} 当前 execute 调用的 source — 'user' | 'system' */
|
|
@@ -102,6 +101,8 @@ export class ChatAgent {
|
|
|
102
101
|
#currentTokenUsage = { input: 0, output: 0 };
|
|
103
102
|
/** @type {import('./ProjectSemanticMemory.js').ProjectSemanticMemory|null} Tier 3 语义记忆 */
|
|
104
103
|
#semanticMemory = null;
|
|
104
|
+
/** @type {MemoryCoordinator|null} v5.0 统一记忆协调器 */
|
|
105
|
+
#memoryCoordinator = null;
|
|
105
106
|
|
|
106
107
|
/**
|
|
107
108
|
* @param {object} opts
|
|
@@ -127,18 +128,27 @@ export class ChatAgent {
|
|
|
127
128
|
/** AI Provider 引用(只读)— 用于外部模块直接调用 structuredOutput / extractJSON */
|
|
128
129
|
this.aiProvider = aiProvider || null;
|
|
129
130
|
|
|
130
|
-
//
|
|
131
|
+
// 初始化对话持久化
|
|
131
132
|
try {
|
|
132
133
|
const projectRoot = container?.singletons?._projectRoot || process.cwd();
|
|
133
|
-
this.#memory = new Memory(projectRoot);
|
|
134
134
|
this.#conversations = new ConversationStore(projectRoot);
|
|
135
135
|
} catch {
|
|
136
|
-
/*
|
|
136
|
+
/* ConversationStore init failed, degrade silently */
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// v4.1: 尝试初始化 ProjectSemanticMemory (Tier 3)
|
|
140
140
|
this.#initSemanticMemory(container);
|
|
141
141
|
|
|
142
|
+
// v5.0: 初始化 MemoryCoordinator (User Chat 模式)
|
|
143
|
+
try {
|
|
144
|
+
this.#memoryCoordinator = new MemoryCoordinator({
|
|
145
|
+
conversationLog: this.#conversations,
|
|
146
|
+
mode: 'user',
|
|
147
|
+
});
|
|
148
|
+
} catch {
|
|
149
|
+
/* MemoryCoordinator init failed, degrade silently */
|
|
150
|
+
}
|
|
151
|
+
|
|
142
152
|
// 注册内置 DAG 管线
|
|
143
153
|
this.#registerBuiltinPipelines();
|
|
144
154
|
|
|
@@ -168,6 +178,19 @@ export class ChatAgent {
|
|
|
168
178
|
*/
|
|
169
179
|
setSemanticMemory(sm) {
|
|
170
180
|
this.#semanticMemory = sm;
|
|
181
|
+
// v5.0: 同步更新 MemoryCoordinator 的 persistentMemory
|
|
182
|
+
if (this.#memoryCoordinator && sm) {
|
|
183
|
+
// 直接重建 coordinator 以包含语义记忆
|
|
184
|
+
try {
|
|
185
|
+
this.#memoryCoordinator = new MemoryCoordinator({
|
|
186
|
+
persistentMemory: sm,
|
|
187
|
+
conversationLog: this.#conversations,
|
|
188
|
+
mode: 'user',
|
|
189
|
+
});
|
|
190
|
+
} catch {
|
|
191
|
+
/* non-critical */
|
|
192
|
+
}
|
|
193
|
+
}
|
|
171
194
|
}
|
|
172
195
|
|
|
173
196
|
/**
|
|
@@ -220,14 +243,12 @@ export class ChatAgent {
|
|
|
220
243
|
// v3.0: Agent 分离选项
|
|
221
244
|
systemPromptOverride, // 覆盖默认 system prompt (Analyst/Producer 各自使用)
|
|
222
245
|
allowedTools, // 覆盖默认工具白名单 (string[])
|
|
223
|
-
|
|
246
|
+
strategy, // v4.2: 策略名称 ('bootstrap'|'analyst'|'producer')
|
|
224
247
|
temperature: temperatureOverride, // 覆盖默认温度
|
|
225
248
|
projectLanguage, // 项目主语言,注入到 submit tool 的 ctx._projectLanguage
|
|
226
249
|
lang, // UI 语言偏好 ('zh'|'en'),控制回复语言
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
episodicMemory, // EpisodicMemory 实例 (跨维度情景记忆)
|
|
230
|
-
toolResultCache, // ToolResultCache 实例 (跨维度工具结果缓存)
|
|
250
|
+
// v5.0: 统一记忆协调器
|
|
251
|
+
memoryCoordinator, // MemoryCoordinator 实例
|
|
231
252
|
// v5.1: SSE 流式进度回调
|
|
232
253
|
onProgress, // (event: {type, ...}) => void — 实时推送思考/工具/回答事件
|
|
233
254
|
} = {}
|
|
@@ -265,6 +286,11 @@ export class ChatAgent {
|
|
|
265
286
|
// 返回 { text, functionCalls: null },被 native 循环视为最终回答。
|
|
266
287
|
this.#logger.info(`[ChatAgent] ✨ using NATIVE tool calling mode (${this.#aiProvider.name})`);
|
|
267
288
|
let result;
|
|
289
|
+
|
|
290
|
+
// v5.0: 确定有效的 MemoryCoordinator
|
|
291
|
+
// 优先使用外部传入的 (bootstrap 模式),否则使用实例级的 (user chat 模式)
|
|
292
|
+
const effectiveCoordinator = memoryCoordinator || this.#memoryCoordinator || null;
|
|
293
|
+
|
|
268
294
|
result = await this.#executeWithNativeTools(prompt, {
|
|
269
295
|
effectiveHistory,
|
|
270
296
|
conversationId,
|
|
@@ -274,12 +300,10 @@ export class ChatAgent {
|
|
|
274
300
|
dimensionMeta,
|
|
275
301
|
systemPromptOverride,
|
|
276
302
|
allowedTools,
|
|
277
|
-
|
|
303
|
+
strategy,
|
|
278
304
|
temperatureOverride,
|
|
279
305
|
projectLanguage,
|
|
280
|
-
|
|
281
|
-
episodicMemory,
|
|
282
|
-
toolResultCache,
|
|
306
|
+
memoryCoordinator: effectiveCoordinator,
|
|
283
307
|
onProgress,
|
|
284
308
|
});
|
|
285
309
|
|
|
@@ -307,7 +331,10 @@ export class ChatAgent {
|
|
|
307
331
|
});
|
|
308
332
|
}
|
|
309
333
|
|
|
310
|
-
|
|
334
|
+
// v5.0: 通过 coordinator 提取记忆
|
|
335
|
+
if (effectiveCoordinator) {
|
|
336
|
+
effectiveCoordinator.extractFromConversation(prompt, result.reply, source);
|
|
337
|
+
}
|
|
311
338
|
|
|
312
339
|
// 附加 token 用量统计
|
|
313
340
|
result.tokenUsage = { ...this.#currentTokenUsage };
|
|
@@ -349,10 +376,10 @@ export class ChatAgent {
|
|
|
349
376
|
/**
|
|
350
377
|
* 原生结构化函数调用 ReAct 循环
|
|
351
378
|
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
379
|
+
* v4.2 重写: 用 ExplorationTracker 统一管理生命周期
|
|
380
|
+
* - ExplorationTracker: 信号收集 + 阶段路由 + Nudge 生成 + Graceful exit
|
|
381
|
+
* - ReasoningTrace: 纯数据收集(推理链记录)
|
|
382
|
+
* - ContextWindow: 消息管理 + 三级压缩
|
|
356
383
|
*
|
|
357
384
|
* @param {string} prompt
|
|
358
385
|
* @param {object} opts
|
|
@@ -367,27 +394,19 @@ export class ChatAgent {
|
|
|
367
394
|
execStartTime,
|
|
368
395
|
budget = DEFAULT_BUDGET,
|
|
369
396
|
dimensionMeta,
|
|
370
|
-
// v3.0: Agent 分离新增选项
|
|
371
397
|
systemPromptOverride,
|
|
372
398
|
allowedTools,
|
|
373
|
-
|
|
399
|
+
strategy,
|
|
374
400
|
temperatureOverride,
|
|
375
401
|
projectLanguage,
|
|
376
|
-
|
|
377
|
-
workingMemory,
|
|
378
|
-
episodicMemory,
|
|
379
|
-
toolResultCache,
|
|
380
|
-
// v5.1: SSE 流式进度回调
|
|
402
|
+
memoryCoordinator,
|
|
381
403
|
onProgress,
|
|
382
404
|
}
|
|
383
405
|
) {
|
|
384
406
|
const isSystem = source === 'system';
|
|
385
|
-
const isSkillOnly = dimensionMeta?.outputType === 'skill';
|
|
386
407
|
const temperature = temperatureOverride ?? (isSystem ? 0.3 : 0.7);
|
|
387
408
|
|
|
388
409
|
// ── Layer 1: ContextWindow ──
|
|
389
|
-
// messages[0] = prompt(不可压缩),历史消息在前面
|
|
390
|
-
// token 预算按模型动态适配:大窗口模型(Gemini/Claude)给更多预算,小窗口(Ollama)限制预算
|
|
391
410
|
const tokenBudget = ContextWindow.resolveTokenBudget(this.#aiProvider?.model, { isSystem });
|
|
392
411
|
const ctx = new ContextWindow(tokenBudget);
|
|
393
412
|
for (const h of effectiveHistory) {
|
|
@@ -397,41 +416,49 @@ export class ChatAgent {
|
|
|
397
416
|
ctx.appendUserMessage(h.content);
|
|
398
417
|
}
|
|
399
418
|
}
|
|
400
|
-
// prompt 作为最终 user message(Anthropic 最佳实践: 查询放在所有上下文之后)
|
|
401
419
|
ctx.appendUserMessage(prompt);
|
|
402
420
|
|
|
403
|
-
// ──
|
|
421
|
+
// ── Pre-check: 首条 prompt 过大时预警 ──
|
|
404
422
|
const initialUsage = ctx.getTokenUsageRatio();
|
|
405
423
|
if (initialUsage > 0.7) {
|
|
406
424
|
this.#logger.warn(
|
|
407
425
|
`[ChatAgent] ⚠ initial prompt already at ${(initialUsage * 100).toFixed(0)}% of token budget (${ctx.estimateTokens()}/${ctx.tokenBudget})`
|
|
408
426
|
);
|
|
409
|
-
if (initialUsage > 0.9 && isSystem) {
|
|
410
|
-
// 仅 1 条消息时 compactIfNeeded 无法压缩(需 >4 条),
|
|
411
|
-
// 依赖 P0/P1 信号限制来控制 prompt 大小
|
|
412
|
-
this.#logger.warn(
|
|
413
|
-
`[ChatAgent] ⚠ prompt exceeds 90% budget — P0/P1 signal limiting should have prevented this. Check PROMPT_LIMITS config.`
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
427
|
}
|
|
417
428
|
|
|
418
|
-
// ── Layer 2:
|
|
419
|
-
const
|
|
420
|
-
|
|
429
|
+
// ── Layer 2: ExplorationTracker (替代 PhaseRouter + 内联探索追踪 + ReasoningLayer 行为控制) ──
|
|
430
|
+
const tracker = ExplorationTracker.resolve(
|
|
431
|
+
{ source, strategy, dimensionMeta, allowedTools },
|
|
432
|
+
budget
|
|
433
|
+
);
|
|
421
434
|
|
|
422
|
-
// ──
|
|
435
|
+
// ── 并行安全: 从 dimensionMeta + strategy 推导确定性 scopeId ──
|
|
436
|
+
// orchestrator 约定 scopeId = `${dimId}:${strategy}` (e.g. "api-patterns:analyst")
|
|
437
|
+
const dimensionScopeId = (dimensionMeta?.id && strategy)
|
|
438
|
+
? `${dimensionMeta.id}:${strategy}`
|
|
439
|
+
: null;
|
|
440
|
+
|
|
441
|
+
// ── Layer 3: ActiveContext (合并 ReasoningTrace + WorkingMemory, Phase 3) ──
|
|
442
|
+
// 优先使用 coordinator 管理的 AC (由 orchestrator 通过 createDimensionScope 创建),
|
|
443
|
+
// 避免两个独立 AC 实例导致数据断裂。
|
|
444
|
+
// User Chat 模式: coordinator 无 AC → 创建 lightweight 本地实例。
|
|
445
|
+
const trace = memoryCoordinator?.getActiveContext(dimensionScopeId) || new ActiveContext({
|
|
446
|
+
lightweight: !isSystem, // User 模式: 轻量 (仅 RT 功能, D5)
|
|
447
|
+
maxRecentRounds: isSystem ? 3 : 2,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// ── 系统提示词 ──
|
|
423
451
|
let baseSystemPrompt =
|
|
424
452
|
systemPromptOverride ||
|
|
425
453
|
buildNativeToolSystemPrompt({
|
|
426
454
|
currentSource: this.#currentSource,
|
|
427
455
|
projectBriefingCache: this.#projectBriefingCache,
|
|
428
|
-
|
|
429
|
-
semanticMemory: this.#semanticMemory,
|
|
456
|
+
memoryCoordinator,
|
|
430
457
|
budget,
|
|
431
458
|
soulPath: SOUL_PATH,
|
|
432
459
|
});
|
|
433
460
|
|
|
434
|
-
// ──
|
|
461
|
+
// ── 语言指令 ──
|
|
435
462
|
const effectiveLang = this.#currentLang;
|
|
436
463
|
if (effectiveLang === 'en') {
|
|
437
464
|
baseSystemPrompt +=
|
|
@@ -441,14 +468,14 @@ export class ChatAgent {
|
|
|
441
468
|
'\n\n## 语言\n你必须使用中文回复。所有输出文本、分析、标题和描述都必须是中文。';
|
|
442
469
|
}
|
|
443
470
|
|
|
444
|
-
//
|
|
471
|
+
// 注入轮次预算
|
|
445
472
|
if (isSystem && !baseSystemPrompt.includes('轮次预算')) {
|
|
446
473
|
const exploreEnd = Math.floor(budget.maxIterations * 0.6);
|
|
447
474
|
const verifyEnd = Math.floor(budget.maxIterations * 0.8);
|
|
448
475
|
baseSystemPrompt += `\n\n## 轮次预算\n- 总轮次: **${budget.maxIterations} 轮**\n- 探索阶段: 第 1-${exploreEnd} 轮(搜索和结构化查询)\n- 验证阶段: 第 ${exploreEnd + 1}-${verifyEnd} 轮(读取关键文件确认细节)\n- 总结阶段: 第 ${verifyEnd + 1}-${budget.maxIterations} 轮(**停止工具调用,输出分析文本**)\n\n到达第 ${verifyEnd} 轮时你必须开始输出总结,不要继续搜索。`;
|
|
449
476
|
}
|
|
450
477
|
|
|
451
|
-
//
|
|
478
|
+
// ── 工具白名单 ──
|
|
452
479
|
const effectiveAllowedTools =
|
|
453
480
|
allowedTools ||
|
|
454
481
|
(isSystem
|
|
@@ -460,7 +487,6 @@ export class ChatAgent {
|
|
|
460
487
|
'list_project_structure',
|
|
461
488
|
'get_file_summary',
|
|
462
489
|
'semantic_search_code',
|
|
463
|
-
// AST 结构化分析工具
|
|
464
490
|
'get_project_overview',
|
|
465
491
|
'get_class_hierarchy',
|
|
466
492
|
'get_class_info',
|
|
@@ -468,7 +494,6 @@ export class ChatAgent {
|
|
|
468
494
|
'get_method_overrides',
|
|
469
495
|
'get_category_map',
|
|
470
496
|
'get_previous_analysis',
|
|
471
|
-
// Agent Memory 工具 (v4.0)
|
|
472
497
|
'note_finding',
|
|
473
498
|
'get_previous_evidence',
|
|
474
499
|
]
|
|
@@ -479,145 +504,49 @@ export class ChatAgent {
|
|
|
479
504
|
const maxIter = isSystem ? budget.maxIterations : MAX_ITERATIONS;
|
|
480
505
|
let consecutiveAiErrors = 0;
|
|
481
506
|
let consecutiveEmptyResponses = 0;
|
|
482
|
-
let iterationCount = 0; // v3: 独立迭代计数器
|
|
483
507
|
const submittedTitles = new Set(this.#globalSubmittedTitles);
|
|
484
|
-
const sharedState = {};
|
|
485
|
-
|
|
486
|
-
// ── 进度感知收敛 (Analyst 专用: disablePhaseRouter=true) ──
|
|
487
|
-
// 追踪搜索/读取新信息的效率,当探索饱和时提前引导 AI 收敛
|
|
488
|
-
const explorationMetrics = {
|
|
489
|
-
uniqueFiles: new Set(), // 已读取的唯一文件
|
|
490
|
-
uniquePatterns: new Set(), // 已搜索的唯一 pattern
|
|
491
|
-
uniqueQueries: new Set(), // 已查询的唯一类名/目录(AST + list_project_structure)
|
|
492
|
-
totalToolCalls: 0, // 总工具调用数
|
|
493
|
-
newInfoRounds: 0, // 最近 N 轮中获取到新信息的轮次
|
|
494
|
-
staleRounds: 0, // 连续未获取新信息的轮次
|
|
495
|
-
convergenceNudged: false, // 是否已注入收敛 nudge
|
|
496
|
-
};
|
|
497
|
-
const MIN_EXPLORE_ITERS = 16; // 最少探索轮次(冷启动质量保障)
|
|
498
|
-
const STALE_THRESHOLD = 3; // 连续无新信息轮次触发收敛
|
|
499
|
-
|
|
500
|
-
// ── Layer 4: ReasoningLayer — 推理链采集 + 结构化观察 + 反思 + 规划 ──
|
|
501
|
-
const reasoning = new ReasoningLayer({
|
|
502
|
-
enabled: true,
|
|
503
|
-
reflectionEnabled: isSystem, // 仅 system 模式启用反思
|
|
504
|
-
reflectionInterval: isSystem ? 5 : 0,
|
|
505
|
-
planningEnabled: isSystem, // 仅 system 模式启用规划
|
|
506
|
-
replanInterval: isSystem ? 8 : 0,
|
|
507
|
-
deviationThreshold: 0.6,
|
|
508
|
-
});
|
|
508
|
+
const sharedState = {};
|
|
509
509
|
|
|
510
510
|
// ── 主循环 ──
|
|
511
511
|
while (true) {
|
|
512
|
-
//
|
|
513
|
-
if (
|
|
514
|
-
|
|
515
|
-
if (
|
|
512
|
+
// ── ExplorationTracker: tick + 退出检查 ──
|
|
513
|
+
if (tracker) {
|
|
514
|
+
tracker.tick();
|
|
515
|
+
if (tracker.shouldExit()) {
|
|
516
516
|
this.#logger.info(
|
|
517
|
-
`[ChatAgent]
|
|
517
|
+
`[ChatAgent] tracker exit: phase=${tracker.phase}, iter=${tracker.iteration}, submits=${tracker.totalSubmits}`
|
|
518
518
|
);
|
|
519
519
|
break;
|
|
520
520
|
}
|
|
521
|
-
// PhaseRouter 因 maxIterations 强制转入 SUMMARIZE → 注入收尾 nudge
|
|
522
|
-
if (phaseRouter.consumeForcedSummarize()) {
|
|
523
|
-
const submitCount = toolCalls.filter(
|
|
524
|
-
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
525
|
-
).length;
|
|
526
|
-
ctx.appendUserNudge(
|
|
527
|
-
`⚠️ 轮次即将耗尽 (${phaseRouter.totalIterations}/${budget.maxIterations}),**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
|
|
528
|
-
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
|
|
529
|
-
);
|
|
530
|
-
this.#logger.info(
|
|
531
|
-
'[ChatAgent] 📝 injected forced SUMMARIZE nudge (maxIterations reached)'
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
} else if (
|
|
535
|
-
isSystem &&
|
|
536
|
-
!phaseRouter &&
|
|
537
|
-
!ctx.__gracefulExitInjected &&
|
|
538
|
-
!explorationMetrics.convergenceNudged &&
|
|
539
|
-
iterationCount >= MIN_EXPLORE_ITERS &&
|
|
540
|
-
explorationMetrics.staleRounds >= STALE_THRESHOLD
|
|
541
|
-
) {
|
|
542
|
-
// ── 进度感知收敛:探索已饱和,提前引导 AI 总结 ──
|
|
543
|
-
this.#logger.info(
|
|
544
|
-
`[ChatAgent] 📊 Exploration saturated at iter ${iterationCount}/${maxIter} — ` +
|
|
545
|
-
`files=${explorationMetrics.uniqueFiles.size}, patterns=${explorationMetrics.uniquePatterns.size}, ` +
|
|
546
|
-
`queries=${explorationMetrics.uniqueQueries.size}, staleRounds=${explorationMetrics.staleRounds} — nudging convergence`
|
|
547
|
-
);
|
|
548
|
-
ctx.appendUserNudge(
|
|
549
|
-
`你已经充分探索了项目代码(${explorationMetrics.uniqueFiles.size} 个文件,${explorationMetrics.uniquePatterns.size} 次不同搜索,${explorationMetrics.uniqueQueries.size} 次结构化查询)。` +
|
|
550
|
-
`最近 ${explorationMetrics.staleRounds} 轮没有发现新信息,建议开始撰写分析总结。\n` +
|
|
551
|
-
`如果你确信还有重要方面未覆盖,可以继续探索(剩余 ${maxIter - iterationCount} 轮);否则请直接输出你的分析发现。`
|
|
552
|
-
);
|
|
553
|
-
explorationMetrics.convergenceNudged = true;
|
|
554
|
-
// 不 break,不设 toolChoice=none — 这是软 nudge,AI 仍可继续探索
|
|
555
|
-
} else if (
|
|
556
|
-
isSystem &&
|
|
557
|
-
!phaseRouter &&
|
|
558
|
-
!ctx.__budgetWarningInjected &&
|
|
559
|
-
iterationCount >= Math.floor(maxIter * 0.75)
|
|
560
|
-
) {
|
|
561
|
-
// ── 预算感知提醒:75% 预算消耗时轻量提示 ──
|
|
562
|
-
ctx.appendUserNudge(
|
|
563
|
-
`📌 进度提醒:你已使用 ${iterationCount}/${maxIter} 轮次(${Math.round((iterationCount / maxIter) * 100)}%)。` +
|
|
564
|
-
`请确保核心方面已覆盖,开始准备总结。剩余 ${maxIter - iterationCount} 轮,优先填补最重要的分析空白。`
|
|
565
|
-
);
|
|
566
|
-
ctx.__budgetWarningInjected = true;
|
|
567
|
-
this.#logger.info(
|
|
568
|
-
`[ChatAgent] 📌 Budget warning at ${iterationCount}/${maxIter} (${Math.round((iterationCount / maxIter) * 100)}%)`
|
|
569
|
-
);
|
|
570
|
-
} else if (isSystem && iterationCount >= maxIter && !ctx.__gracefulExitInjected) {
|
|
571
|
-
// 达到上限 → 注入收尾消息让 AI 快速总结(而非硬中断)
|
|
572
|
-
this.#logger.info(
|
|
573
|
-
`[ChatAgent] Iteration cap reached (${iterationCount}/${maxIter}) — injecting graceful exit nudge`
|
|
574
|
-
);
|
|
575
|
-
const submitCount = toolCalls.filter(
|
|
576
|
-
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
577
|
-
).length;
|
|
578
|
-
ctx.appendUserNudge(
|
|
579
|
-
`⚠️ 你已使用 ${iterationCount}/${maxIter} 轮次,**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
|
|
580
|
-
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
|
|
581
|
-
);
|
|
582
|
-
ctx.__gracefulExitInjected = true;
|
|
583
|
-
// 不 break,给 AI 2 轮 grace 来产出总结
|
|
584
|
-
// 继续 while 循环
|
|
585
|
-
} else if (isSystem && iterationCount >= maxIter + 2) {
|
|
586
|
-
// 硬上限兜底:grace 轮次也耗尽
|
|
587
|
-
this.#logger.info(`[ChatAgent] Hard cap reached: ${iterationCount}/${maxIter + 2}`);
|
|
588
|
-
break;
|
|
589
521
|
} else if (!isSystem && ctx.length > maxIter * 2 + 2) {
|
|
590
|
-
//
|
|
522
|
+
// User 模式: 简单消息数限制
|
|
591
523
|
break;
|
|
592
524
|
}
|
|
593
|
-
iterationCount++;
|
|
594
525
|
|
|
526
|
+
const currentIter = tracker?.iteration || (ctx.length - 1);
|
|
595
527
|
const iterStartTime = Date.now();
|
|
596
|
-
const currentIter = phaseRouter?.totalIterations || iterationCount;
|
|
597
528
|
|
|
598
|
-
// ── 动态 toolChoice
|
|
529
|
+
// ── 动态 toolChoice ──
|
|
599
530
|
let currentChoice;
|
|
600
|
-
if (
|
|
601
|
-
|
|
602
|
-
currentChoice = 'none';
|
|
603
|
-
} else if (phaseRouter) {
|
|
604
|
-
currentChoice = phaseRouter.getToolChoice();
|
|
531
|
+
if (tracker) {
|
|
532
|
+
currentChoice = tracker.getToolChoice();
|
|
605
533
|
} else {
|
|
606
534
|
currentChoice = 'auto';
|
|
607
535
|
}
|
|
608
536
|
|
|
609
|
-
// ──
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
ctx.appendUserNudge(reflectionNudge);
|
|
617
|
-
this.#logger.info(`[ChatAgent] 💭 injected reflection at iteration ${iterationCount}`);
|
|
537
|
+
// ── Nudge 注入(每轮最多一条) ──
|
|
538
|
+
if (tracker) {
|
|
539
|
+
const nudge = tracker.getNudge(trace);
|
|
540
|
+
if (nudge) {
|
|
541
|
+
ctx.appendUserNudge(nudge.text);
|
|
542
|
+
this.#logger.info(`[ChatAgent] 💬 injected ${nudge.type} nudge at iter ${currentIter}`);
|
|
543
|
+
}
|
|
618
544
|
}
|
|
619
545
|
|
|
620
|
-
// ──
|
|
546
|
+
// ── ReasoningTrace: 开始新轮次 ──
|
|
547
|
+
trace.startRound(currentIter);
|
|
548
|
+
|
|
549
|
+
// ── 压缩检查 ──
|
|
621
550
|
const compactResult = ctx.compactIfNeeded();
|
|
622
551
|
if (compactResult.level > 0) {
|
|
623
552
|
this.#logger.info(
|
|
@@ -625,32 +554,23 @@ export class ChatAgent {
|
|
|
625
554
|
);
|
|
626
555
|
}
|
|
627
556
|
|
|
628
|
-
// ── 构建 systemPrompt (
|
|
557
|
+
// ── 构建 systemPrompt (含阶段上下文) ──
|
|
629
558
|
let systemPrompt = baseSystemPrompt;
|
|
630
|
-
if (
|
|
631
|
-
|
|
632
|
-
if (hint) {
|
|
633
|
-
systemPrompt += `\n\n## 当前状态\n${hint}`;
|
|
634
|
-
}
|
|
559
|
+
if (tracker) {
|
|
560
|
+
systemPrompt += tracker.getPhaseContext();
|
|
635
561
|
} else if (isSystem) {
|
|
636
|
-
// 非
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
let phaseLabel;
|
|
640
|
-
if (iterationCount <= Math.floor(maxIter * 0.6)) {
|
|
641
|
-
phaseLabel = '探索阶段';
|
|
642
|
-
} else if (iterationCount <= verifyStart) {
|
|
643
|
-
phaseLabel = '验证阶段';
|
|
644
|
-
} else {
|
|
645
|
-
phaseLabel = '⚠ 总结阶段 — 请停止工具调用,直接输出分析文本';
|
|
646
|
-
}
|
|
647
|
-
systemPrompt += `\n\n## 当前进度\n第 ${iterationCount}/${maxIter} 轮 | ${phaseLabel} | 剩余 ${remaining} 轮`;
|
|
562
|
+
// fallback: 非 tracker 路径注入进度
|
|
563
|
+
const remaining = maxIter - currentIter;
|
|
564
|
+
systemPrompt += `\n\n## 当前进度\n第 ${currentIter}/${maxIter} 轮 | 剩余 ${remaining} 轮`;
|
|
648
565
|
}
|
|
649
566
|
|
|
650
|
-
// ──
|
|
651
|
-
//
|
|
652
|
-
if (
|
|
653
|
-
const wmContext =
|
|
567
|
+
// ── WorkingMemory 上下文注入 ──
|
|
568
|
+
// v5.0: 通过 coordinator 获取动态记忆上下文
|
|
569
|
+
if (isSystem && memoryCoordinator) {
|
|
570
|
+
const wmContext = memoryCoordinator.buildDynamicMemoryPrompt({
|
|
571
|
+
mode: strategy || 'analyst',
|
|
572
|
+
scopeId: dimensionScopeId,
|
|
573
|
+
});
|
|
654
574
|
if (wmContext) {
|
|
655
575
|
systemPrompt += `\n\n${wmContext}`;
|
|
656
576
|
}
|
|
@@ -660,12 +580,11 @@ export class ChatAgent {
|
|
|
660
580
|
let aiResult;
|
|
661
581
|
try {
|
|
662
582
|
const messages = ctx.toMessages();
|
|
663
|
-
const currentPhase =
|
|
583
|
+
const currentPhase = tracker?.phase || 'user';
|
|
664
584
|
this.#logger.info(
|
|
665
585
|
`[ChatAgent] 🔄 iteration ${currentIter}/${maxIter} — phase=${currentPhase}, ${messages.length} msgs, toolChoice=${currentChoice}, tokens~${ctx.estimateTokens()}`
|
|
666
586
|
);
|
|
667
587
|
|
|
668
|
-
// SSE: 推送步骤开始
|
|
669
588
|
onProgress?.({
|
|
670
589
|
type: 'step:start',
|
|
671
590
|
step: currentIter,
|
|
@@ -684,7 +603,6 @@ export class ChatAgent {
|
|
|
684
603
|
|
|
685
604
|
const aiDuration = Date.now() - iterStartTime;
|
|
686
605
|
|
|
687
|
-
// 累计 token 用量
|
|
688
606
|
if (aiResult.usage) {
|
|
689
607
|
this.#currentTokenUsage.input += aiResult.usage.inputTokens || 0;
|
|
690
608
|
this.#currentTokenUsage.output += aiResult.usage.outputTokens || 0;
|
|
@@ -702,27 +620,32 @@ export class ChatAgent {
|
|
|
702
620
|
}
|
|
703
621
|
consecutiveAiErrors = 0;
|
|
704
622
|
|
|
705
|
-
// ──
|
|
706
|
-
|
|
623
|
+
// ── ReasoningTrace: 提取 Thought + Plan ──
|
|
624
|
+
if (aiResult.text) {
|
|
625
|
+
trace.setThought(aiResult.text);
|
|
626
|
+
trace.extractAndSetPlan(aiResult.text, currentIter);
|
|
627
|
+
}
|
|
707
628
|
} catch (aiErr) {
|
|
708
629
|
consecutiveAiErrors++;
|
|
709
630
|
this.#logger.warn(
|
|
710
631
|
`[ChatAgent] AI call failed (attempt ${consecutiveAiErrors}): ${aiErr.message}`
|
|
711
632
|
);
|
|
712
633
|
|
|
713
|
-
//
|
|
634
|
+
// 回退 tick(AI 失败不计入迭代)
|
|
635
|
+
tracker?.rollbackTick();
|
|
636
|
+
|
|
714
637
|
if (aiErr.code === 'CIRCUIT_OPEN') {
|
|
715
638
|
if (isSystem) {
|
|
716
639
|
this.#logger.warn(`[ChatAgent] 🛑 circuit breaker is OPEN — skipping to summary`);
|
|
717
640
|
break;
|
|
718
641
|
}
|
|
719
|
-
|
|
642
|
+
trace.endRound();
|
|
720
643
|
return {
|
|
721
644
|
reply: `抱歉,AI 服务暂时不可用(${aiErr.message})。请稍后重试,或检查 API 配置。`,
|
|
722
645
|
toolCalls,
|
|
723
646
|
hasContext: toolCalls.length > 0,
|
|
724
|
-
reasoningTrace:
|
|
725
|
-
reasoningQuality:
|
|
647
|
+
reasoningTrace: trace,
|
|
648
|
+
reasoningQuality: tracker?.getQualityMetrics(trace) || null,
|
|
726
649
|
};
|
|
727
650
|
}
|
|
728
651
|
|
|
@@ -734,14 +657,14 @@ export class ChatAgent {
|
|
|
734
657
|
ctx.resetToPromptOnly();
|
|
735
658
|
break;
|
|
736
659
|
}
|
|
737
|
-
|
|
660
|
+
trace.endRound();
|
|
738
661
|
onProgress?.({ type: 'step:end', step: currentIter });
|
|
739
662
|
return {
|
|
740
663
|
reply: `抱歉,AI 服务暂时不可用(${aiErr.message})。请稍后重试,或检查 API 配置。`,
|
|
741
664
|
toolCalls,
|
|
742
665
|
hasContext: toolCalls.length > 0,
|
|
743
|
-
reasoningTrace:
|
|
744
|
-
reasoningQuality:
|
|
666
|
+
reasoningTrace: trace,
|
|
667
|
+
reasoningQuality: tracker?.getQualityMetrics(trace) || null,
|
|
745
668
|
};
|
|
746
669
|
}
|
|
747
670
|
await new Promise((r) => setTimeout(r, 2000));
|
|
@@ -750,16 +673,15 @@ export class ChatAgent {
|
|
|
750
673
|
|
|
751
674
|
// ── 处理 functionCalls ──
|
|
752
675
|
if (aiResult.functionCalls && aiResult.functionCalls.length > 0) {
|
|
753
|
-
//
|
|
754
|
-
|
|
755
|
-
if (ctx.__gracefulExitInjected) {
|
|
676
|
+
// Graceful exit 保护: 忽略 toolChoice='none' 下的工具调用 (Gemini 偶发)
|
|
677
|
+
if (tracker?.isGracefulExit) {
|
|
756
678
|
this.#logger.warn(
|
|
757
679
|
`[ChatAgent] ⚠ AI returned ${aiResult.functionCalls.length} tool calls despite toolChoice=none (graceful exit) — ignoring tools, treating as text`
|
|
758
680
|
);
|
|
759
681
|
if (aiResult.text) {
|
|
760
682
|
const reply = cleanFinalAnswer(aiResult.text);
|
|
761
683
|
const totalDuration = Date.now() - execStartTime;
|
|
762
|
-
|
|
684
|
+
trace.endRound();
|
|
763
685
|
this.#logger.info(
|
|
764
686
|
`[ChatAgent] ✅ final answer (graceful exit, forced) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
765
687
|
);
|
|
@@ -767,29 +689,29 @@ export class ChatAgent {
|
|
|
767
689
|
reply,
|
|
768
690
|
toolCalls,
|
|
769
691
|
hasContext: toolCalls.length > 0,
|
|
770
|
-
reasoningTrace:
|
|
771
|
-
reasoningQuality:
|
|
692
|
+
reasoningTrace: trace,
|
|
693
|
+
reasoningQuality: tracker?.getQualityMetrics(trace) || null,
|
|
772
694
|
};
|
|
773
695
|
}
|
|
774
|
-
// 无文本时继续循环,下一轮硬上限兜底
|
|
775
696
|
continue;
|
|
776
697
|
}
|
|
777
698
|
|
|
778
|
-
//
|
|
699
|
+
// 限制单次工具调用数量
|
|
779
700
|
const MAX_TOOL_CALLS_PER_ITER = 8;
|
|
780
701
|
let activeCalls = aiResult.functionCalls;
|
|
781
702
|
if (activeCalls.length > MAX_TOOL_CALLS_PER_ITER) {
|
|
782
703
|
this.#logger.warn(
|
|
783
704
|
`[ChatAgent] ⚠ ${activeCalls.length} tool calls, capping to ${MAX_TOOL_CALLS_PER_ITER}`
|
|
784
705
|
);
|
|
706
|
+
tracker?.recordTruncatedCalls(activeCalls.length - MAX_TOOL_CALLS_PER_ITER);
|
|
785
707
|
activeCalls = activeCalls.slice(0, MAX_TOOL_CALLS_PER_ITER);
|
|
786
708
|
}
|
|
787
709
|
|
|
788
|
-
// ContextWindow: 原子追加 assistant + tool results
|
|
789
710
|
ctx.appendAssistantWithToolCalls(aiResult.text || null, activeCalls);
|
|
790
711
|
|
|
791
712
|
let roundSubmitCount = 0;
|
|
792
|
-
let roundHasNewInfo = false;
|
|
713
|
+
let roundHasNewInfo = false;
|
|
714
|
+
const roundToolNames = [];
|
|
793
715
|
|
|
794
716
|
for (const fc of activeCalls) {
|
|
795
717
|
const toolStartTime = Date.now();
|
|
@@ -797,7 +719,6 @@ export class ChatAgent {
|
|
|
797
719
|
`[ChatAgent] 🔧 ${fc.name}(${JSON.stringify(fc.args).substring(0, 100)})`
|
|
798
720
|
);
|
|
799
721
|
|
|
800
|
-
// SSE: 推送工具调用开始
|
|
801
722
|
onProgress?.({
|
|
802
723
|
type: 'tool:start',
|
|
803
724
|
id: `tc_${fc.name}_${Date.now()}`,
|
|
@@ -808,9 +729,9 @@ export class ChatAgent {
|
|
|
808
729
|
let toolResult;
|
|
809
730
|
let cacheHit = false;
|
|
810
731
|
|
|
811
|
-
//
|
|
812
|
-
if (
|
|
813
|
-
const cached =
|
|
732
|
+
// v5.0: 缓存检查通过 coordinator
|
|
733
|
+
if (memoryCoordinator) {
|
|
734
|
+
const cached = memoryCoordinator.getCachedResult(fc.name, fc.args);
|
|
814
735
|
if (cached !== null) {
|
|
815
736
|
toolResult = cached;
|
|
816
737
|
cacheHit = true;
|
|
@@ -830,11 +751,9 @@ export class ChatAgent {
|
|
|
830
751
|
_submittedPatterns: this.#globalSubmittedPatterns,
|
|
831
752
|
_sharedState: sharedState,
|
|
832
753
|
_projectLanguage: projectLanguage,
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
_toolResultCache: toolResultCache || null,
|
|
837
|
-
_currentRound: iterationCount,
|
|
754
|
+
_memoryCoordinator: memoryCoordinator || null,
|
|
755
|
+
_dimensionScopeId: dimensionScopeId,
|
|
756
|
+
_currentRound: currentIter,
|
|
838
757
|
})
|
|
839
758
|
);
|
|
840
759
|
const toolDuration = Date.now() - toolStartTime;
|
|
@@ -846,7 +765,6 @@ export class ChatAgent {
|
|
|
846
765
|
`[ChatAgent] 🔧 done: ${fc.name} → ${resultSize} chars in ${toolDuration}ms`
|
|
847
766
|
);
|
|
848
767
|
|
|
849
|
-
// SSE: 推送工具调用完成
|
|
850
768
|
onProgress?.({
|
|
851
769
|
type: 'tool:end',
|
|
852
770
|
tool: fc.name,
|
|
@@ -858,7 +776,6 @@ export class ChatAgent {
|
|
|
858
776
|
this.#logger.warn(`[ChatAgent] 🔧 FAILED: ${fc.name} — ${toolErr.message}`);
|
|
859
777
|
toolResult = { error: `tool "${fc.name}" failed: ${toolErr.message}` };
|
|
860
778
|
|
|
861
|
-
// SSE: 推送工具调用失败
|
|
862
779
|
onProgress?.({
|
|
863
780
|
type: 'tool:end',
|
|
864
781
|
tool: fc.name,
|
|
@@ -869,122 +786,29 @@ export class ChatAgent {
|
|
|
869
786
|
}
|
|
870
787
|
}
|
|
871
788
|
|
|
872
|
-
//
|
|
873
|
-
if (
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// v4.0: ToolResultCache — 缓存搜索/读文件结果 (仅非缓存命中时写入)
|
|
878
|
-
if (toolResultCache && !cacheHit) {
|
|
879
|
-
toolResultCache.set(fc.name, fc.args, toolResult);
|
|
789
|
+
// v5.0: 统一记录观察 (缓存写入)
|
|
790
|
+
if (memoryCoordinator) {
|
|
791
|
+
memoryCoordinator.recordObservation(fc.name, fc.args, toolResult, currentIter, cacheHit);
|
|
880
792
|
}
|
|
881
793
|
|
|
882
794
|
// 记录到全局 toolCalls
|
|
883
795
|
const summarized = this.#summarizeResult(toolResult);
|
|
884
796
|
toolCalls.push({ tool: fc.name, params: fc.args, result: summarized });
|
|
885
797
|
|
|
886
|
-
// ──
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
let foundNewInfo = false;
|
|
893
|
-
|
|
894
|
-
if (fc.name === 'search_project_code') {
|
|
895
|
-
const pattern = fc.args?.pattern || '';
|
|
896
|
-
const patterns = fc.args?.patterns || [];
|
|
897
|
-
// 单模式
|
|
898
|
-
if (pattern && !explorationMetrics.uniquePatterns.has(pattern)) {
|
|
899
|
-
explorationMetrics.uniquePatterns.add(pattern);
|
|
900
|
-
foundNewInfo = true;
|
|
901
|
-
}
|
|
902
|
-
// 批量模式
|
|
903
|
-
for (const p of patterns) {
|
|
904
|
-
if (!explorationMetrics.uniquePatterns.has(p)) {
|
|
905
|
-
explorationMetrics.uniquePatterns.add(p);
|
|
906
|
-
foundNewInfo = true;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
// 检查搜索结果是否有新文件
|
|
910
|
-
if (toolResult && typeof toolResult === 'object') {
|
|
911
|
-
const matches = toolResult.matches || [];
|
|
912
|
-
const batchResults = toolResult.batchResults || {};
|
|
913
|
-
for (const m of matches) {
|
|
914
|
-
if (m.file && !explorationMetrics.uniqueFiles.has(m.file)) {
|
|
915
|
-
explorationMetrics.uniqueFiles.add(m.file);
|
|
916
|
-
foundNewInfo = true;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
for (const sub of Object.values(batchResults)) {
|
|
920
|
-
for (const m of sub.matches || []) {
|
|
921
|
-
if (m.file && !explorationMetrics.uniqueFiles.has(m.file)) {
|
|
922
|
-
explorationMetrics.uniqueFiles.add(m.file);
|
|
923
|
-
foundNewInfo = true;
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
} else if (fc.name === 'read_project_file') {
|
|
929
|
-
const fp = fc.args?.filePath || '';
|
|
930
|
-
const fps = fc.args?.filePaths || [];
|
|
931
|
-
if (fp && !explorationMetrics.uniqueFiles.has(fp)) {
|
|
932
|
-
explorationMetrics.uniqueFiles.add(fp);
|
|
933
|
-
foundNewInfo = true;
|
|
934
|
-
}
|
|
935
|
-
for (const f of fps) {
|
|
936
|
-
if (!explorationMetrics.uniqueFiles.has(f)) {
|
|
937
|
-
explorationMetrics.uniqueFiles.add(f);
|
|
938
|
-
foundNewInfo = true;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
} else if (fc.name === 'list_project_structure') {
|
|
942
|
-
// 目录结构:同一目录只算一次新信息
|
|
943
|
-
const dir = fc.args?.directory || '/';
|
|
944
|
-
const qKey = `list:${dir}`;
|
|
945
|
-
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
946
|
-
explorationMetrics.uniqueQueries.add(qKey);
|
|
947
|
-
foundNewInfo = true;
|
|
948
|
-
}
|
|
949
|
-
} else if (
|
|
950
|
-
fc.name === 'get_class_info' ||
|
|
951
|
-
fc.name === 'get_class_hierarchy' ||
|
|
952
|
-
fc.name === 'get_protocol_info' ||
|
|
953
|
-
fc.name === 'get_method_overrides' ||
|
|
954
|
-
fc.name === 'get_category_map'
|
|
955
|
-
) {
|
|
956
|
-
// AST 结构化查询:同一类名/协议名只算一次新信息
|
|
957
|
-
const queryTarget =
|
|
958
|
-
fc.args?.className || fc.args?.protocolName || fc.args?.name || '';
|
|
959
|
-
const qKey = `${fc.name}:${queryTarget}`;
|
|
960
|
-
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
961
|
-
explorationMetrics.uniqueQueries.add(qKey);
|
|
962
|
-
foundNewInfo = true;
|
|
963
|
-
}
|
|
964
|
-
} else if (fc.name === 'get_project_overview') {
|
|
965
|
-
// 项目概览只需调一次
|
|
966
|
-
const qKey = 'overview';
|
|
967
|
-
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
968
|
-
explorationMetrics.uniqueQueries.add(qKey);
|
|
969
|
-
foundNewInfo = true;
|
|
970
|
-
}
|
|
971
|
-
} else if (fc.name !== 'submit_knowledge' && fc.name !== 'submit_with_check') {
|
|
972
|
-
// 其他未分类工具 — 首次算新信息,之后同工具名+同参数去重
|
|
973
|
-
const qKey = `${fc.name}:${JSON.stringify(fc.args || {}).substring(0, 80)}`;
|
|
974
|
-
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
975
|
-
explorationMetrics.uniqueQueries.add(qKey);
|
|
976
|
-
foundNewInfo = true;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
if (foundNewInfo) {
|
|
981
|
-
explorationMetrics.staleRounds = 0;
|
|
982
|
-
explorationMetrics.newInfoRounds++;
|
|
983
|
-
roundHasNewInfo = true;
|
|
984
|
-
}
|
|
798
|
+
// ── ExplorationTracker: 记录工具调用 (替代内联 ~120 行 if-else) ──
|
|
799
|
+
let isNew = false;
|
|
800
|
+
if (tracker) {
|
|
801
|
+
const trackResult = tracker.recordToolCall(fc.name, fc.args, toolResult);
|
|
802
|
+
isNew = trackResult.isNew;
|
|
803
|
+
if (isNew) roundHasNewInfo = true;
|
|
985
804
|
}
|
|
986
805
|
|
|
987
|
-
// ──
|
|
806
|
+
// ── ActiveContext: 统一记录 Action + Observation + WM 压缩 (Phase 3) ──
|
|
807
|
+
trace.recordToolCall(fc.name, fc.args, toolResult, isNew);
|
|
808
|
+
|
|
809
|
+
roundToolNames.push(fc.name);
|
|
810
|
+
|
|
811
|
+
// ── ToolResultLimiter: 动态配额压缩 ──
|
|
988
812
|
const quota = ctx.getToolResultQuota();
|
|
989
813
|
let resultStr = limitToolResult(fc.name, toolResult, quota);
|
|
990
814
|
|
|
@@ -999,7 +823,6 @@ export class ChatAgent {
|
|
|
999
823
|
if (isRejected) {
|
|
1000
824
|
this.#logger.info(`[ChatAgent] 🚫 off-topic rejected: "${title}"`);
|
|
1001
825
|
} else if (isError) {
|
|
1002
|
-
// 候选创建失败(如 validation error)— 不加入 submittedTitles,允许 AI 重试
|
|
1003
826
|
this.#logger.info(
|
|
1004
827
|
`[ChatAgent] ⚠ submit error: "${title}" — ${toolResult.error || 'unknown'}`
|
|
1005
828
|
);
|
|
@@ -1009,7 +832,6 @@ export class ChatAgent {
|
|
|
1009
832
|
} else {
|
|
1010
833
|
submittedTitles.add(title.toLowerCase().trim());
|
|
1011
834
|
this.#globalSubmittedTitles.add(title.toLowerCase().trim());
|
|
1012
|
-
// 记录代码模式指纹用于跨维度去重
|
|
1013
835
|
const pattern = fc.args?.content?.pattern || '';
|
|
1014
836
|
if (pattern.length >= 30) {
|
|
1015
837
|
const fp = pattern
|
|
@@ -1026,175 +848,102 @@ export class ChatAgent {
|
|
|
1026
848
|
}
|
|
1027
849
|
}
|
|
1028
850
|
|
|
1029
|
-
// ContextWindow: 追加 tool result(与 assistant 保持原子性)
|
|
1030
851
|
ctx.appendToolResult(fc.id, fc.name, resultStr);
|
|
1031
852
|
}
|
|
1032
853
|
|
|
1033
|
-
// ──
|
|
1034
|
-
if (
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
// ── ReasoningLayer Hook 4: afterRound — 关闭轮次 + 写入摘要 ──
|
|
1039
|
-
reasoning.afterRound({
|
|
1040
|
-
newInfoCount: roundHasNewInfo ? 1 : 0,
|
|
1041
|
-
totalCalls: activeCalls.length,
|
|
1042
|
-
submitCount: roundSubmitCount,
|
|
1043
|
-
explorationMetrics,
|
|
1044
|
-
});
|
|
1045
|
-
|
|
1046
|
-
// ── PhaseRouter 更新 ──
|
|
1047
|
-
if (phaseRouter) {
|
|
1048
|
-
const transition = phaseRouter.update({
|
|
1049
|
-
functionCalls: activeCalls,
|
|
854
|
+
// ── ExplorationTracker: endRound → 检查阶段转换 ──
|
|
855
|
+
if (tracker) {
|
|
856
|
+
tracker.updatePlanProgress(trace);
|
|
857
|
+
const transitionNudge = tracker.endRound({
|
|
858
|
+
hasNewInfo: roundHasNewInfo,
|
|
1050
859
|
submitCount: roundSubmitCount,
|
|
1051
|
-
|
|
860
|
+
toolNames: roundToolNames,
|
|
1052
861
|
});
|
|
1053
862
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
ctx.appendUserNudge(
|
|
1059
|
-
'你已充分探索了项目代码,现在请开始调用 submit_knowledge 工具来提交你发现的知识候选。不要再搜索,直接提交。'
|
|
1060
|
-
);
|
|
1061
|
-
this.#logger.info('[ChatAgent] 📝 injected PRODUCE transition nudge');
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
// ── EXPLORE→SUMMARIZE / PRODUCE→SUMMARIZE 阶段过渡: 注入 digest 引导 ──
|
|
1065
|
-
// skill-only 维度从 EXPLORE 直接进入 SUMMARIZE (跳过 PRODUCE),
|
|
1066
|
-
// 需要明确告知 AI 输出 dimensionDigest JSON
|
|
1067
|
-
if (transition.transitioned && transition.newPhase === 'SUMMARIZE') {
|
|
1068
|
-
const submitCount = toolCalls.filter(
|
|
1069
|
-
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
1070
|
-
).length;
|
|
1071
|
-
ctx.appendUserNudge(
|
|
1072
|
-
`你已完成分析探索。请在回复中直接输出 dimensionDigest JSON(用 \`\`\`json 包裹),包含以下字段:\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因(如:提交上限已达)","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。如果有未来得及处理的信号,请在此标记,系统会在下次运行时续传。`
|
|
863
|
+
if (transitionNudge) {
|
|
864
|
+
ctx.appendUserNudge(transitionNudge.text);
|
|
865
|
+
this.#logger.info(
|
|
866
|
+
`[ChatAgent] 📝 injected ${transitionNudge.type} nudge (${tracker.phase})`
|
|
1073
867
|
);
|
|
1074
|
-
this.#logger.info('[ChatAgent] 📝 injected SUMMARIZE transition nudge');
|
|
1075
868
|
}
|
|
1076
869
|
}
|
|
1077
870
|
|
|
1078
|
-
//
|
|
871
|
+
// ── ReasoningTrace: 关闭轮次 ──
|
|
872
|
+
trace.setRoundSummary({
|
|
873
|
+
newInfoCount: roundHasNewInfo ? 1 : 0,
|
|
874
|
+
totalCalls: activeCalls.length,
|
|
875
|
+
submits: roundSubmitCount,
|
|
876
|
+
cumulativeFiles: tracker?.getMetrics().uniqueFiles || 0,
|
|
877
|
+
cumulativePatterns: tracker?.getMetrics().uniquePatterns || 0,
|
|
878
|
+
});
|
|
879
|
+
trace.endRound();
|
|
880
|
+
|
|
1079
881
|
onProgress?.({ type: 'step:end', step: currentIter });
|
|
1080
882
|
continue;
|
|
1081
883
|
}
|
|
1082
884
|
|
|
1083
885
|
// ── 文字回答 ──
|
|
1084
|
-
// SSE: 文字回答意味着步骤结束
|
|
1085
886
|
onProgress?.({ type: 'step:end', step: currentIter });
|
|
1086
|
-
|
|
887
|
+
|
|
888
|
+
// 空响应重试 (Gemini 偶发)
|
|
1087
889
|
if (!aiResult.text && isSystem && consecutiveEmptyResponses < 2) {
|
|
1088
890
|
consecutiveEmptyResponses++;
|
|
1089
891
|
this.#logger.warn(
|
|
1090
892
|
`[ChatAgent] ⚠ empty response from system source — retrying (${consecutiveEmptyResponses}/2)`
|
|
1091
893
|
);
|
|
894
|
+
tracker?.rollbackTick();
|
|
1092
895
|
await new Promise((r) => setTimeout(r, 1500));
|
|
1093
896
|
continue;
|
|
1094
897
|
}
|
|
1095
|
-
// 收到非空响应时重置空响应计数器
|
|
1096
898
|
if (aiResult.text) {
|
|
1097
899
|
consecutiveEmptyResponses = 0;
|
|
1098
900
|
}
|
|
1099
901
|
|
|
1100
|
-
//
|
|
1101
|
-
if (
|
|
1102
|
-
const
|
|
1103
|
-
functionCalls: null,
|
|
1104
|
-
submitCount: 0,
|
|
1105
|
-
isTextOnly: true,
|
|
1106
|
-
});
|
|
902
|
+
// ── ExplorationTracker: 处理文本响应 ──
|
|
903
|
+
if (tracker) {
|
|
904
|
+
const textResult = tracker.onTextResponse();
|
|
1107
905
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
// 刚从 EXPLORE/PRODUCE 转入 SUMMARIZE 的文字回答可能不含 digest,
|
|
1111
|
-
// 注入 nudge 让 AI 再输出一次 digest JSON
|
|
1112
|
-
if (transition.transitioned) {
|
|
1113
|
-
ctx.appendAssistantText(aiResult.text || '');
|
|
1114
|
-
const submitCount = toolCalls.filter(
|
|
1115
|
-
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
1116
|
-
).length;
|
|
1117
|
-
ctx.appendUserNudge(
|
|
1118
|
-
`请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹):\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理的信号","reason":"原因","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 记录未来得及处理的信号。已全部覆盖则留空 \`[]\`。`
|
|
1119
|
-
);
|
|
1120
|
-
this.#logger.info(
|
|
1121
|
-
'[ChatAgent] 📝 injected SUMMARIZE nudge (text-triggered transition)'
|
|
1122
|
-
);
|
|
1123
|
-
continue;
|
|
1124
|
-
}
|
|
1125
|
-
// 已在 SUMMARIZE 阶段 — 这就是最终回答
|
|
906
|
+
if (textResult.isFinalAnswer) {
|
|
907
|
+
// 已在终结阶段且非刚转入 → 最终回答
|
|
1126
908
|
const reply = cleanFinalAnswer(aiResult.text || '');
|
|
1127
909
|
const totalDuration = Date.now() - execStartTime;
|
|
1128
|
-
|
|
910
|
+
trace.endRound();
|
|
1129
911
|
this.#logger.info(
|
|
1130
|
-
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${
|
|
912
|
+
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${tracker.iteration} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
1131
913
|
);
|
|
1132
914
|
return {
|
|
1133
915
|
reply,
|
|
1134
916
|
toolCalls,
|
|
1135
917
|
hasContext: toolCalls.length > 0,
|
|
1136
|
-
reasoningTrace:
|
|
1137
|
-
reasoningQuality:
|
|
918
|
+
reasoningTrace: trace,
|
|
919
|
+
reasoningQuality: tracker.getQualityMetrics(trace),
|
|
1138
920
|
};
|
|
1139
921
|
}
|
|
1140
922
|
|
|
1141
|
-
if (
|
|
1142
|
-
//
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
'你的分析很好。请继续调用 submit_knowledge 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。'
|
|
1149
|
-
);
|
|
1150
|
-
this.#logger.info(
|
|
1151
|
-
'[ChatAgent] 📝 injected submit nudge (text in PRODUCE, not transitioning)'
|
|
1152
|
-
);
|
|
1153
|
-
}
|
|
1154
|
-
continue;
|
|
1155
|
-
}
|
|
1156
|
-
// 非生产阶段的未转换文本 = 最终回答
|
|
1157
|
-
const reply = cleanFinalAnswer(aiResult.text || '');
|
|
1158
|
-
const totalDuration = Date.now() - execStartTime;
|
|
1159
|
-
reasoning.afterRound();
|
|
1160
|
-
this.#logger.info(
|
|
1161
|
-
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${phaseRouter.totalIterations} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
1162
|
-
);
|
|
1163
|
-
return {
|
|
1164
|
-
reply,
|
|
1165
|
-
toolCalls,
|
|
1166
|
-
hasContext: toolCalls.length > 0,
|
|
1167
|
-
reasoningTrace: reasoning.trace,
|
|
1168
|
-
reasoningQuality: reasoning.getQualityMetrics(),
|
|
1169
|
-
};
|
|
923
|
+
if (textResult.needsDigestNudge) {
|
|
924
|
+
// 刚转入终结阶段 → 注入 digest nudge,继续循环
|
|
925
|
+
ctx.appendAssistantText(aiResult.text || '');
|
|
926
|
+
ctx.appendUserNudge(textResult.nudge);
|
|
927
|
+
this.#logger.info('[ChatAgent] 📝 injected SUMMARIZE nudge (text-triggered transition)');
|
|
928
|
+
trace.endRound();
|
|
929
|
+
continue;
|
|
1170
930
|
}
|
|
1171
931
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
reasoning.afterRound();
|
|
1182
|
-
this.#logger.info(
|
|
1183
|
-
`[ChatAgent] ✅ final answer (${ctx.__gracefulExitInjected ? 'graceful exit' : 'user'}) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
1184
|
-
);
|
|
1185
|
-
return {
|
|
1186
|
-
reply,
|
|
1187
|
-
toolCalls,
|
|
1188
|
-
hasContext: toolCalls.length > 0,
|
|
1189
|
-
reasoningTrace: reasoning.trace,
|
|
1190
|
-
reasoningQuality: reasoning.getQualityMetrics(),
|
|
1191
|
-
};
|
|
932
|
+
if (textResult.shouldContinue) {
|
|
933
|
+
// 非终结阶段的中间文本 → 注入可选 nudge,继续循环
|
|
934
|
+
ctx.appendAssistantText(aiResult.text || '');
|
|
935
|
+
if (textResult.nudge) {
|
|
936
|
+
ctx.appendUserNudge(textResult.nudge);
|
|
937
|
+
}
|
|
938
|
+
trace.endRound();
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
1192
941
|
}
|
|
1193
942
|
|
|
1194
|
-
//
|
|
943
|
+
// User 模式 / 非 tracker: 文字回答即最终回答
|
|
1195
944
|
const reply = cleanFinalAnswer(aiResult.text || '');
|
|
1196
945
|
const totalDuration = Date.now() - execStartTime;
|
|
1197
|
-
|
|
946
|
+
trace.endRound();
|
|
1198
947
|
this.#logger.info(
|
|
1199
948
|
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
1200
949
|
);
|
|
@@ -1202,24 +951,24 @@ export class ChatAgent {
|
|
|
1202
951
|
reply,
|
|
1203
952
|
toolCalls,
|
|
1204
953
|
hasContext: toolCalls.length > 0,
|
|
1205
|
-
reasoningTrace:
|
|
1206
|
-
reasoningQuality:
|
|
954
|
+
reasoningTrace: trace,
|
|
955
|
+
reasoningQuality: tracker?.getQualityMetrics(trace) || null,
|
|
1207
956
|
};
|
|
1208
957
|
}
|
|
1209
958
|
|
|
1210
959
|
// ── 循环退出: 产出 dimensionDigest 总结 ──
|
|
1211
|
-
|
|
960
|
+
trace.endRound();
|
|
1212
961
|
const forcedResult = await this.#produceForcedSummary({
|
|
1213
962
|
source,
|
|
1214
963
|
toolCalls,
|
|
1215
964
|
toolSchemas,
|
|
1216
965
|
ctx,
|
|
1217
|
-
|
|
966
|
+
tracker,
|
|
1218
967
|
execStartTime,
|
|
1219
968
|
prompt,
|
|
1220
969
|
});
|
|
1221
|
-
forcedResult.reasoningTrace =
|
|
1222
|
-
forcedResult.reasoningQuality =
|
|
970
|
+
forcedResult.reasoningTrace = trace;
|
|
971
|
+
forcedResult.reasoningQuality = tracker?.getQualityMetrics(trace) || null;
|
|
1223
972
|
return forcedResult;
|
|
1224
973
|
}
|
|
1225
974
|
|
|
@@ -1232,11 +981,11 @@ export class ChatAgent {
|
|
|
1232
981
|
toolCalls,
|
|
1233
982
|
toolSchemas,
|
|
1234
983
|
ctx,
|
|
1235
|
-
|
|
984
|
+
tracker,
|
|
1236
985
|
execStartTime,
|
|
1237
986
|
prompt,
|
|
1238
987
|
}) {
|
|
1239
|
-
const iterations =
|
|
988
|
+
const iterations = tracker?.iteration || 0;
|
|
1240
989
|
const isSystem = source === 'system';
|
|
1241
990
|
this.#logger.info(
|
|
1242
991
|
`[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls, source=${source})`
|
|
@@ -1740,6 +1489,18 @@ ${toolContextSummary}
|
|
|
1740
1489
|
import('./ProjectSemanticMemory.js')
|
|
1741
1490
|
.then(({ ProjectSemanticMemory }) => {
|
|
1742
1491
|
this.#semanticMemory = new ProjectSemanticMemory(db, { logger: this.#logger });
|
|
1492
|
+
// v5.0: 更新 MemoryCoordinator 的 persistentMemory
|
|
1493
|
+
if (this.#memoryCoordinator) {
|
|
1494
|
+
try {
|
|
1495
|
+
this.#memoryCoordinator = new MemoryCoordinator({
|
|
1496
|
+
persistentMemory: this.#semanticMemory,
|
|
1497
|
+
conversationLog: this.#conversations,
|
|
1498
|
+
mode: 'user',
|
|
1499
|
+
});
|
|
1500
|
+
} catch {
|
|
1501
|
+
/* non-critical */
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1743
1504
|
})
|
|
1744
1505
|
.catch(() => {
|
|
1745
1506
|
/* Semantic Memory not available */
|
|
@@ -1750,85 +1511,6 @@ ${toolContextSummary}
|
|
|
1750
1511
|
}
|
|
1751
1512
|
}
|
|
1752
1513
|
|
|
1753
|
-
/**
|
|
1754
|
-
* 从对话中提取值得记忆的信息写入 Memory
|
|
1755
|
-
*
|
|
1756
|
-
* 双层策略:
|
|
1757
|
-
* 1. 规则快速匹配(零延迟,覆盖明确的中英文模式)
|
|
1758
|
-
* 2. AI 驱动提取(异步后台,从 reply 中提取 [MEMORY] 标签)
|
|
1759
|
-
*
|
|
1760
|
-
* source 隔离: 标记 memory 来源,避免系统分析污染用户记忆
|
|
1761
|
-
*/
|
|
1762
|
-
#extractMemory(prompt, reply) {
|
|
1763
|
-
if (!this.#memory && !this.#semanticMemory) {
|
|
1764
|
-
return;
|
|
1765
|
-
}
|
|
1766
|
-
const source = this.#currentSource || 'user';
|
|
1767
|
-
|
|
1768
|
-
try {
|
|
1769
|
-
// ── 层 1: 规则快速匹配(中文 + 英文) ──
|
|
1770
|
-
const prefPatterns = [
|
|
1771
|
-
/我们(项目|团队)?(不用|不使用|禁止|避免|偏好|习惯|规范是)/,
|
|
1772
|
-
/以后(都|请|要)/,
|
|
1773
|
-
/记住/,
|
|
1774
|
-
/we\s+(don'?t|never|always|prefer|avoid)\s+use/i,
|
|
1775
|
-
/remember\s+(to|that)/i,
|
|
1776
|
-
/our\s+(convention|standard|rule)\s+is/i,
|
|
1777
|
-
];
|
|
1778
|
-
if (prefPatterns.some((p) => p.test(prompt))) {
|
|
1779
|
-
const entry = {
|
|
1780
|
-
type: 'preference',
|
|
1781
|
-
content: prompt.substring(0, 200),
|
|
1782
|
-
source,
|
|
1783
|
-
ttl: 30,
|
|
1784
|
-
};
|
|
1785
|
-
this.#memory?.append(entry);
|
|
1786
|
-
this.#semanticMemory?.append({ ...entry, ttl: undefined });
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
const decisionPatterns = [
|
|
1790
|
-
/决定(了|用|采用|使用)/,
|
|
1791
|
-
/(确认|同意|通过)(了|这个方案|审核)/,
|
|
1792
|
-
/就(这样|这么)(做|定|办)/,
|
|
1793
|
-
/let'?s\s+(go\s+with|use|adopt)/i,
|
|
1794
|
-
/approved|confirmed|decided/i,
|
|
1795
|
-
];
|
|
1796
|
-
if (decisionPatterns.some((p) => p.test(prompt))) {
|
|
1797
|
-
const entry = {
|
|
1798
|
-
type: 'decision',
|
|
1799
|
-
content: prompt.substring(0, 200),
|
|
1800
|
-
source,
|
|
1801
|
-
ttl: 60,
|
|
1802
|
-
};
|
|
1803
|
-
this.#memory?.append(entry);
|
|
1804
|
-
this.#semanticMemory?.append({ ...entry, ttl: undefined });
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
// ── 层 2: 从 AI reply 中提取 [MEMORY] 标签 ──
|
|
1808
|
-
// AI 可在回复中嵌入: [MEMORY:preference] 内容 [/MEMORY]
|
|
1809
|
-
if (reply) {
|
|
1810
|
-
const memoryTagRegex = /\[MEMORY:(\w+)\]\s*([\s\S]*?)\s*\[\/MEMORY\]/g;
|
|
1811
|
-
let match;
|
|
1812
|
-
while ((match = memoryTagRegex.exec(reply)) !== null) {
|
|
1813
|
-
const type = match[1]; // preference | decision | context
|
|
1814
|
-
const content = match[2].trim();
|
|
1815
|
-
if (content && ['preference', 'decision', 'context'].includes(type)) {
|
|
1816
|
-
const entry = {
|
|
1817
|
-
type,
|
|
1818
|
-
content: content.substring(0, 200),
|
|
1819
|
-
source,
|
|
1820
|
-
ttl: type === 'context' ? 90 : type === 'decision' ? 60 : 30,
|
|
1821
|
-
};
|
|
1822
|
-
this.#memory?.append(entry);
|
|
1823
|
-
this.#semanticMemory?.append({ ...entry, ttl: undefined });
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
} catch {
|
|
1828
|
-
/* memory write failure is non-critical */
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
1514
|
/**
|
|
1833
1515
|
* 自动压缩过长的对话(异步后台执行)
|
|
1834
1516
|
* 当对话消息数超过 12 条时触发 AI 摘要压缩
|