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.
Files changed (64) hide show
  1. package/README.md +2 -4
  2. package/bin/cli.js +164 -145
  3. package/config/constitution.yaml +2 -0
  4. package/dashboard/dist/assets/{index-DNOHYBhy.css → index-BaGY7kJI.css} +1 -1
  5. package/dashboard/dist/assets/{index-6itPuGFl.js → index-DfHY_3ln.js} +25 -25
  6. package/dashboard/dist/index.html +2 -2
  7. package/lib/cli/CliLogger.js +78 -0
  8. package/lib/cli/SetupService.js +9 -718
  9. package/lib/cli/UpgradeService.js +23 -398
  10. package/lib/cli/deploy/FileDeployer.js +562 -0
  11. package/lib/cli/deploy/FileManifest.js +272 -0
  12. package/lib/external/mcp/McpServer.js +22 -26
  13. package/lib/external/mcp/autoApproveInjector.js +1 -0
  14. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +5 -5
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +25 -3
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +6 -6
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +4 -0
  18. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +5 -5
  19. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +89 -44
  20. package/lib/external/mcp/handlers/consolidated.js +8 -9
  21. package/lib/external/mcp/handlers/dimension-complete-external.js +4 -4
  22. package/lib/external/mcp/handlers/guard.js +283 -5
  23. package/lib/external/mcp/handlers/task.js +183 -9
  24. package/lib/external/mcp/tools.js +32 -81
  25. package/lib/http/routes/task.js +55 -0
  26. package/lib/service/chat/AnalystAgent.js +12 -12
  27. package/lib/service/chat/ChatAgent.js +227 -545
  28. package/lib/service/chat/ChatAgentPrompts.js +9 -11
  29. package/lib/service/chat/ContextWindow.js +2 -296
  30. package/lib/service/chat/EpisodicConsolidator.js +15 -15
  31. package/lib/service/chat/ExplorationTracker.js +1262 -0
  32. package/lib/service/chat/HandoffProtocol.js +8 -9
  33. package/lib/service/chat/Memory.js +4 -0
  34. package/lib/service/chat/ProducerAgent.js +9 -6
  35. package/lib/service/chat/ProjectSemanticMemory.js +4 -0
  36. package/lib/service/chat/ReasoningTrace.js +182 -0
  37. package/lib/service/chat/WorkingMemory.js +4 -0
  38. package/lib/service/chat/memory/ActiveContext.js +910 -0
  39. package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
  40. package/lib/service/chat/memory/PersistentMemory.js +450 -0
  41. package/lib/service/chat/memory/SessionStore.js +896 -0
  42. package/lib/service/chat/memory/index.js +13 -0
  43. package/lib/service/chat/tools/ast-graph.js +17 -16
  44. package/lib/service/cursor/AgentInstructionsGenerator.js +76 -47
  45. package/lib/service/cursor/FileProtection.js +4 -1
  46. package/lib/service/guard/GuardCheckEngine.js +10 -3
  47. package/lib/service/task/TaskGraphService.js +3 -3
  48. package/lib/shared/LanguageService.js +2 -1
  49. package/package.json +1 -1
  50. package/skills/autosnippet-intent/SKILL.md +1 -3
  51. package/skills/autosnippet-recipes/SKILL.md +1 -3
  52. package/templates/claude-code/commands/prime.md +19 -0
  53. package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
  54. package/templates/claude-code/settings.json +21 -0
  55. package/templates/copilot-instructions.md +64 -177
  56. package/templates/cursor-hooks/commands/prime.md +12 -0
  57. package/templates/cursor-hooks/hooks/session-start.sh +10 -0
  58. package/templates/cursor-hooks/hooks.json +11 -0
  59. package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
  60. package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
  61. package/lib/external/mcp/handlers/decide.js +0 -109
  62. package/lib/external/mcp/handlers/ready.js +0 -42
  63. package/lib/service/chat/ReasoningLayer.js +0 -888
  64. 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, PhaseRouter } from './ContextWindow.js';
41
+ import { ContextWindow, limitToolResult } from './ContextWindow.js';
42
42
  import { ConversationStore } from './ConversationStore.js';
43
- import { Memory } from './Memory.js';
44
- import { ReasoningLayer } from './ReasoningLayer.js';
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
- /* Memory/ConversationStore init failed, degrade silently */
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
- disablePhaseRouter = false, // 禁用 PhaseRouter (Analyst 不需要阶段控制)
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
- // v4.0: Agent Memory 集成
228
- workingMemory, // WorkingMemory 实例 (由 orchestrator 注入)
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
- disablePhaseRouter,
303
+ strategy,
278
304
  temperatureOverride,
279
305
  projectLanguage,
280
- workingMemory,
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
- this.#extractMemory(prompt, result.reply);
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
- * 1. ContextWindow 消息生命周期 + 三级递进压缩
354
- * 2. PhaseRouter — 阶段状态机 (EXPLORE→PRODUCE→SUMMARIZE)
355
- * 3. ToolResultLimiter 工具结果入口压缩 (动态配额)
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
- disablePhaseRouter = false,
399
+ strategy,
374
400
  temperatureOverride,
375
401
  projectLanguage,
376
- // v4.0: Agent Memory 集成
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
- // ── P5: Pre-check 首条 prompt 过大时预警 ──
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: PhaseRouter ( system 源且未禁用时使用) ──
419
- const phaseRouter =
420
- isSystem && !disablePhaseRouter ? new PhaseRouter(budget, isSkillOnly) : null;
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
- memory: this.#memory,
429
- semanticMemory: this.#semanticMemory,
456
+ memoryCoordinator,
430
457
  budget,
431
458
  soulPath: SOUL_PATH,
432
459
  });
433
460
 
434
- // ── 统一注入语言指令(对所有来源生效: user 对话 / system bootstrap / analyst / producer)──
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
- // 统一注入轮次预算,确保 AI 始终知道具体数字和节奏
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
- // Bootstrap 场景限制可用工具集 (支持外部覆盖)
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 = {}; // P2.2: 跨工具调用共享状态(搜索计数器等)
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
- // PhaseRouter tick + 退出检查
513
- if (phaseRouter) {
514
- phaseRouter.tick();
515
- if (phaseRouter.shouldExit()) {
512
+ // ── ExplorationTracker: tick + 退出检查 ──
513
+ if (tracker) {
514
+ tracker.tick();
515
+ if (tracker.shouldExit()) {
516
516
  this.#logger.info(
517
- `[ChatAgent] PhaseRouter exit: phase=${phaseRouter.phase}, iter=${phaseRouter.totalIterations}, submits=${phaseRouter.totalSubmits}`
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 (由 PhaseRouter 决定) ──
529
+ // ── 动态 toolChoice ──
599
530
  let currentChoice;
600
- if (ctx.__gracefulExitInjected) {
601
- // graceful exit: 禁止工具调用,强制 AI 输出文本总结
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
- // ── ReasoningLayer Hook 1: beforeAICall — 开始新轮次 + 反思检查 ──
610
- const reflectionNudge = reasoning.beforeAICall(iterationCount, {
611
- explorationMetrics,
612
- budget,
613
- phase: phaseRouter?.phase,
614
- });
615
- if (reflectionNudge) {
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
- // ── 压缩检查 (每次 AI 调用前) ──
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 (phaseRouter) {
631
- const hint = phaseRouter.getPhaseHint();
632
- if (hint) {
633
- systemPrompt += `\n\n## 当前状态\n${hint}`;
634
- }
559
+ if (tracker) {
560
+ systemPrompt += tracker.getPhaseContext();
635
561
  } else if (isSystem) {
636
- // 非 PhaseRouter 路径:注入实时轮次计数器,让 AI 始终感知进度
637
- const verifyStart = Math.floor(maxIter * 0.8);
638
- const remaining = maxIter - iterationCount;
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
- // ── v4.0: WorkingMemory 上下文注入 ──
651
- // 当有压缩后的旧观察或 scratchpad 发现时,注入到 system prompt
652
- if (workingMemory && isSystem) {
653
- const wmContext = workingMemory.buildContext();
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 = phaseRouter?.phase || 'user';
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
- // ── ReasoningLayer Hook 2: afterAICall — 提取 Thought ──
706
- reasoning.afterAICall(aiResult);
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
- reasoning.afterRound();
642
+ trace.endRound();
720
643
  return {
721
644
  reply: `抱歉,AI 服务暂时不可用(${aiErr.message})。请稍后重试,或检查 API 配置。`,
722
645
  toolCalls,
723
646
  hasContext: toolCalls.length > 0,
724
- reasoningTrace: reasoning.trace,
725
- reasoningQuality: reasoning.getQualityMetrics(),
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
- reasoning.afterRound();
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: reasoning.trace,
744
- reasoningQuality: reasoning.getQualityMetrics(),
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
- // ── Graceful exit 保护: Gemini 有时会无视 toolChoice='none' 继续返回工具调用
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
- reasoning.afterRound();
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: reasoning.trace,
771
- reasoningQuality: reasoning.getQualityMetrics(),
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
- // v4.0: ToolResultCache — 先检查缓存,命中则跳过实际执行
812
- if (toolResultCache) {
813
- const cached = toolResultCache.get(fc.name, fc.args);
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
- // v4.0: Agent Memory
834
- _workingMemory: workingMemory || null,
835
- _episodicMemory: episodicMemory || null,
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
- // v4.0: WorkingMemory — 记录工具观察
873
- if (workingMemory && fc.name !== 'note_finding') {
874
- workingMemory.observe(fc.name, toolResult, iterationCount);
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
- // ── ReasoningLayer Hook 3: afterToolExec 记录 Action + 构建 Observation ──
887
- reasoning.afterToolExec(fc.name, fc.args, toolResult, explorationMetrics);
888
-
889
- // ── 探索进度追踪 ( PhaseRouter 路径) ──
890
- if (!phaseRouter && isSystem) {
891
- explorationMetrics.totalToolCalls++;
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
- // ── Layer 3: ToolResultLimiter 动态配额压缩 ──
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
- // ── 探索饱和度更新 (非 PhaseRouter 路径) ──
1034
- if (!phaseRouter && isSystem && !roundHasNewInfo) {
1035
- explorationMetrics.staleRounds++;
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
- isTextOnly: false,
860
+ toolNames: roundToolNames,
1052
861
  });
1053
862
 
1054
- // ── EXPLORE→PRODUCE 阶段过渡: 注入提交引导消息 ──
1055
- // 原生工具调用模式下,仅靠 systemPrompt 附加 hint 不够显著,
1056
- // 需要一条 user 消息明确告知 AI 切换到提交模式
1057
- if (transition.transitioned && transition.newPhase === 'PRODUCE') {
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
- // SSE: 步骤结束
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
- // 空响应重试(Gemini 偶发)
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
- // PhaseRouter: 文字回答触发阶段转换
1101
- if (phaseRouter) {
1102
- const transition = phaseRouter.update({
1103
- functionCalls: null,
1104
- submitCount: 0,
1105
- isTextOnly: true,
1106
- });
902
+ // ── ExplorationTracker: 处理文本响应 ──
903
+ if (tracker) {
904
+ const textResult = tracker.onTextResponse();
1107
905
 
1108
- // SUMMARIZE 阶段的文字回答 = 最终回答
1109
- if (phaseRouter.phase === 'SUMMARIZE') {
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
- reasoning.afterRound();
910
+ trace.endRound();
1129
911
  this.#logger.info(
1130
- `[ChatAgent] ✅ final answer — ${reply.length} chars, ${phaseRouter.totalIterations} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`
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: reasoning.trace,
1137
- reasoningQuality: reasoning.getQualityMetrics(),
918
+ reasoningTrace: trace,
919
+ reasoningQuality: tracker.getQualityMetrics(trace),
1138
920
  };
1139
921
  }
1140
922
 
1141
- if (!transition.transitioned) {
1142
- // EXPLORE/PRODUCE 阶段收到文本但未转阶段 可能是 AI 的中间分析
1143
- // 注入提交引导并继续循环,而非立即返回
1144
- if (phaseRouter.phase === 'EXPLORE' || phaseRouter.phase === 'PRODUCE') {
1145
- ctx.appendAssistantText(aiResult.text || '');
1146
- if (phaseRouter.phase === 'PRODUCE') {
1147
- ctx.appendUserNudge(
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
- // 其他阶段的文字回答 → 继续循环(PhaseRouter 已自动转换阶段)
1173
- ctx.appendAssistantText(aiResult.text || '');
1174
- continue;
1175
- }
1176
-
1177
- // 用户对话 / graceful exit: 文字回答即最终回答
1178
- if (ctx.__gracefulExitInjected || !isSystem) {
1179
- const reply = cleanFinalAnswer(aiResult.text || '');
1180
- const totalDuration = Date.now() - execStartTime;
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
- // system 模式非 graceful exit 的文字回答(理论上不应到这里)
943
+ // User 模式 / tracker: 文字回答即最终回答
1195
944
  const reply = cleanFinalAnswer(aiResult.text || '');
1196
945
  const totalDuration = Date.now() - execStartTime;
1197
- reasoning.afterRound();
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: reasoning.trace,
1206
- reasoningQuality: reasoning.getQualityMetrics(),
954
+ reasoningTrace: trace,
955
+ reasoningQuality: tracker?.getQualityMetrics(trace) || null,
1207
956
  };
1208
957
  }
1209
958
 
1210
959
  // ── 循环退出: 产出 dimensionDigest 总结 ──
1211
- reasoning.afterRound();
960
+ trace.endRound();
1212
961
  const forcedResult = await this.#produceForcedSummary({
1213
962
  source,
1214
963
  toolCalls,
1215
964
  toolSchemas,
1216
965
  ctx,
1217
- phaseRouter,
966
+ tracker,
1218
967
  execStartTime,
1219
968
  prompt,
1220
969
  });
1221
- forcedResult.reasoningTrace = reasoning.trace;
1222
- forcedResult.reasoningQuality = reasoning.getQualityMetrics();
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
- phaseRouter,
984
+ tracker,
1236
985
  execStartTime,
1237
986
  prompt,
1238
987
  }) {
1239
- const iterations = phaseRouter?.totalIterations || 0;
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 摘要压缩