autosnippet 3.2.8 → 3.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/bin/cli.js +6 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +23 -26
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/cli/deploy/FileManifest.js +1 -1
  8. package/lib/core/AstAnalyzer.js +1 -1
  9. package/lib/core/discovery/index.js +2 -2
  10. package/lib/external/ai/AiProvider.js +66 -172
  11. package/lib/external/ai/providers/GoogleGeminiProvider.js +29 -5
  12. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  13. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  14. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +1 -1
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  18. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +291 -204
  19. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +7 -6
  20. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  21. package/lib/external/mcp/handlers/bootstrap-internal.js +2 -2
  22. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  23. package/lib/http/HttpServer.js +1 -1
  24. package/lib/http/middleware/requestLogger.js +1 -0
  25. package/lib/http/routes/ai.js +240 -35
  26. package/lib/http/routes/candidates.js +2 -3
  27. package/lib/http/routes/extract.js +13 -11
  28. package/lib/http/routes/modules.js +2 -2
  29. package/lib/http/routes/recipes.js +5 -5
  30. package/lib/http/routes/remote.js +134 -255
  31. package/lib/http/routes/violations.js +0 -54
  32. package/lib/http/utils/sse-sessions.js +1 -1
  33. package/lib/infrastructure/logging/Logger.js +5 -4
  34. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  35. package/lib/injection/ServiceContainer.js +64 -17
  36. package/lib/platform/ScreenCaptureService.js +177 -0
  37. package/lib/platform/ios/routes/spm.js +2 -2
  38. package/lib/service/agent/AgentEventBus.js +207 -0
  39. package/lib/service/agent/AgentFactory.js +535 -0
  40. package/lib/service/agent/AgentMessage.js +240 -0
  41. package/lib/service/agent/AgentRouter.js +228 -0
  42. package/lib/service/agent/AgentRuntime.js +1056 -0
  43. package/lib/service/agent/AgentState.js +217 -0
  44. package/lib/service/agent/IntentClassifier.js +331 -0
  45. package/lib/service/agent/LarkTransport.js +389 -0
  46. package/lib/service/agent/capabilities.js +409 -0
  47. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  48. package/lib/service/{chat → agent/context}/ExplorationTracker.js +112 -33
  49. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +5 -3
  50. package/lib/service/agent/core/LoopContext.js +170 -0
  51. package/lib/service/agent/core/MessageAdapter.js +223 -0
  52. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  53. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +15 -98
  54. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  55. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  56. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  57. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +85 -135
  58. package/lib/service/agent/domain/insight-producer.js +270 -0
  59. package/lib/service/agent/domain/scan-prompts.js +444 -0
  60. package/lib/service/agent/forced-summary.js +266 -0
  61. package/lib/service/agent/index.js +91 -0
  62. package/lib/service/{chat → agent}/memory/ActiveContext.js +29 -1
  63. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  64. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  65. package/lib/service/{chat → agent}/memory/SessionStore.js +1 -1
  66. package/lib/service/{chat → agent}/memory/index.js +1 -1
  67. package/lib/service/agent/policies.js +442 -0
  68. package/lib/service/agent/presets.js +305 -0
  69. package/lib/service/agent/strategies.js +756 -0
  70. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  71. package/lib/service/agent/tools/ai-analysis.js +75 -0
  72. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  73. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  74. package/lib/service/{chat → agent}/tools/index.js +27 -21
  75. package/lib/service/{chat → agent}/tools/infrastructure.js +1 -1
  76. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  77. package/lib/service/agent/tools/scan-recipe.js +189 -0
  78. package/lib/service/agent/tools/system-interaction.js +476 -0
  79. package/lib/service/automation/DirectiveDetector.js +0 -1
  80. package/lib/service/automation/FileWatcher.js +0 -8
  81. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  82. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  83. package/lib/service/module/ModuleService.js +40 -73
  84. package/lib/service/skills/SignalCollector.js +26 -19
  85. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  86. package/lib/shared/FieldSpec.js +1 -1
  87. package/lib/shared/StyleGuide.js +1 -1
  88. package/package.json +4 -1
  89. package/resources/native-ui/screenshot.swift +228 -0
  90. package/dashboard/dist/assets/index-D5jiDBQG.css +0 -1
  91. package/dashboard/dist/assets/index-e5OKj-Ni.js +0 -128
  92. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  93. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -750
  94. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  95. package/lib/http/routes/spm.js +0 -5
  96. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  97. package/lib/service/chat/ChatAgent.js +0 -1602
  98. package/lib/service/chat/Memory.js +0 -161
  99. package/lib/service/chat/ProducerAgent.js +0 -431
  100. package/lib/service/chat/ReasoningTrace.js +0 -523
  101. package/lib/service/chat/TaskPipeline.js +0 -357
  102. package/lib/service/chat/WorkingMemory.js +0 -359
  103. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  104. package/lib/service/chat/tools/ai-analysis.js +0 -267
  105. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  106. package/lib/service/chat/tools.js +0 -18
  107. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  108. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  109. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  110. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  111. /package/lib/service/{chat → agent}/tools/ast-graph.js +0 -0
  112. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  113. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  114. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 合并了三个原本各自为政的系统:
5
5
  * 1. PhaseRouter (ContextWindow.js) — 阶段状态机
6
- * 2. 探索进度追踪 (ChatAgent.js 内联) — 信息增量检测
6
+ * 2. 探索进度追踪 (原内联逻辑) — 信息增量检测
7
7
  * 3. ReasoningLayer 行为控制部分 — 反思/规划/停滞 nudge
8
8
  *
9
9
  * 职责:
@@ -21,7 +21,7 @@
21
21
  * @module ExplorationTracker
22
22
  */
23
23
 
24
- import Logger from '../../infrastructure/logging/Logger.js';
24
+ import Logger from '../../../infrastructure/logging/Logger.js';
25
25
 
26
26
  // ─── 常量 ──────────────────────────────────────────────
27
27
 
@@ -39,11 +39,13 @@ const DEFAULT_DEVIATION_THRESHOLD = 0.6;
39
39
  const DEFAULT_MIN_EXPLORE_ITERS = 10;
40
40
  /** 默认停滞收敛阈值 */
41
41
  const DEFAULT_CONVERGENCE_STALE_THRESHOLD = 3;
42
+ /** 最少经过 N 轮后才允许再次触发 replan(防止 replan 风暴) */
43
+ const MIN_REPLAN_GAP = 3;
42
44
 
43
45
  // ─── 内置策略 ────────────────────────────────────────────
44
46
 
45
47
  /**
46
- * Bootstrap 策略(原始 ChatAgent,有 submit 阶段)
48
+ * Bootstrap 策略(有 submit 阶段)
47
49
  * @param {boolean} isSkillOnly — skill-only 维度跳过 PRODUCE 阶段
48
50
  * @returns {object} 策略配置
49
51
  */
@@ -227,6 +229,8 @@ export class ExplorationTracker {
227
229
 
228
230
  /** @type {boolean} tick 是否已调用(用于 rollback) */
229
231
  #ticked = false;
232
+ /** @type {string} 提交工具名(用于 nudge 文本生成) */
233
+ #submitToolName = 'submit_knowledge';
230
234
 
231
235
  /**
232
236
  * @param {object} strategy — 策略配置对象
@@ -234,7 +238,20 @@ export class ExplorationTracker {
234
238
  */
235
239
  constructor(strategy, budget) {
236
240
  this.#strategy = strategy;
237
- this.#budget = budget;
241
+ // 合并默认值: PipelineStrategy 创建 per-stage tracker 时,
242
+ // budget 可能仅含 maxIterations/temperature (来自 stage.budget),
243
+ // 需要补全 tracker 所需的 searchBudget、maxSubmits 等字段。
244
+ this.#budget = {
245
+ maxIterations: 24,
246
+ searchBudget: 18,
247
+ searchBudgetGrace: 10,
248
+ maxSubmits: 10,
249
+ softSubmitLimit: 8,
250
+ idleRoundsToExit: 3,
251
+ ...budget,
252
+ };
253
+ /** @type {string} 提交工具名 — bootstrap 用 submit_knowledge,scan 用 collect_scan_recipe */
254
+ this.#submitToolName = budget.submitToolName || 'submit_knowledge';
238
255
  this.#phase = strategy.phases[0];
239
256
  this.#logger = Logger.getInstance();
240
257
  }
@@ -243,7 +260,7 @@ export class ExplorationTracker {
243
260
 
244
261
  /**
245
262
  * 根据调用参数解析应使用的策略
246
- * @param {object} opts — ChatAgent execute 的选项
263
+ * @param {object} opts — AgentRuntime execute 的选项
247
264
  * @param {object} budget — 预算配置
248
265
  * @returns {ExplorationTracker|null} — User 模式返回 null
249
266
  */
@@ -295,11 +312,20 @@ export class ExplorationTracker {
295
312
  }
296
313
  }
297
314
 
315
+ /** 提交工具名 (供外部判断是否为 scan / bootstrap pipeline) */
316
+ get submitToolName() {
317
+ return this.#submitToolName;
318
+ }
319
+
298
320
  /**
299
321
  * 是否应退出主循环
300
322
  * @returns {boolean}
301
323
  */
302
324
  shouldExit() {
325
+ // Scan pipeline: SUMMARIZE 无消费方,recipes 已通过工具调用收集,直接退出
326
+ if (this.#isTerminalPhase() && this.#submitToolName === 'collect_scan_recipe') {
327
+ return true;
328
+ }
303
329
  // 终结阶段 + 已给了 2 轮 grace → 退出
304
330
  if (this.#isTerminalPhase() && this.#metrics.phaseRounds >= 2) {
305
331
  return true;
@@ -336,7 +362,7 @@ export class ExplorationTracker {
336
362
  * 4. reflection — 周期反思 / 停滞反思
337
363
  * 5. planning — 首轮规划 / 偏差重规划
338
364
  *
339
- * @param {import('./memory/ActiveContext.js').ActiveContext} trace — 推理链(供反思用)
365
+ * @param {import('../memory/ActiveContext.js').ActiveContext} trace — 推理链(供反思用)
340
366
  * @returns {{ type: string, text: string }|null}
341
367
  */
342
368
  getNudge(trace) {
@@ -346,7 +372,7 @@ export class ExplorationTracker {
346
372
  // 1. 强制退出
347
373
  if (this.#gracefulExitRound != null && m.iteration === this.#gracefulExitRound) {
348
374
  const submitCount = m.submitCount;
349
- // v5.1: Analyst 策略使用纯文本输出,其他策略使用 dimensionDigest JSON
375
+ // v5.1: Analyst 策略使用纯文本输出
350
376
  if (this.#strategy.name === 'analyst') {
351
377
  return {
352
378
  type: 'force_exit',
@@ -360,6 +386,17 @@ export class ExplorationTracker {
360
386
  `⛔ 严禁在回复中复制或引用本条指令的任何文字,只输出你自己的分析。`,
361
387
  };
362
388
  }
389
+ // Scan pipeline (collect_scan_recipe): 使用纯文本总结,不需要 dimensionDigest
390
+ if (this.#submitToolName === 'collect_scan_recipe') {
391
+ return {
392
+ type: 'force_exit',
393
+ text: `⚠️ **轮次耗尽** (${m.iteration}/${b.maxIterations})。你必须**立即停止工具调用**。\n\n` +
394
+ `已通过 collect_scan_recipe 提交了 ${submitCount} 个知识候选。` +
395
+ `请直接输出你的分析总结(Markdown 格式),列出已发现和未覆盖的关键模式。\n` +
396
+ `⛔ 不要再调用任何工具,直接输出文本。`,
397
+ };
398
+ }
399
+ // Bootstrap 策略: 使用 dimensionDigest JSON (供维度编排消费)
363
400
  return {
364
401
  type: 'force_exit',
365
402
  text: `⚠️ 你已使用 ${m.iteration}/${b.maxIterations} 轮次,**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
@@ -459,7 +496,7 @@ export class ExplorationTracker {
459
496
 
460
497
  /**
461
498
  * 记录一次工具调用结果,更新内部指标
462
- * 替代 ChatAgent 中内联的 ~120 行 if-else 逻辑
499
+ * 替代原内联的 ~120 行 if-else 逻辑
463
500
  *
464
501
  * @param {string} toolName
465
502
  * @param {object} args
@@ -470,8 +507,8 @@ export class ExplorationTracker {
470
507
  this.#metrics.totalToolCalls++;
471
508
  const isNew = this.#detectNewInfo(toolName, args, result);
472
509
 
473
- // Submit 追踪(只记成功提交)
474
- if (toolName === 'submit_knowledge' || toolName === 'submit_with_check') {
510
+ // Submit 追踪(只记成功提交)— 含 scan 阶段的 collect_scan_recipe
511
+ if (toolName === 'submit_knowledge' || toolName === 'submit_with_check' || toolName === 'collect_scan_recipe') {
475
512
  const status = typeof result === 'object' ? result?.status : 'ok';
476
513
  const isRejected = status === 'rejected';
477
514
  const isError = status === 'error';
@@ -526,6 +563,13 @@ export class ExplorationTracker {
526
563
  // 4. 如果发生了转换,生成 nudge 立即返回给主循环注入
527
564
  if (this.#justTransitioned) {
528
565
  this.#justTransitioned = false;
566
+ // Scan pipeline: 不注入转换 nudge,shouldExit 会在下一轮直接退出
567
+ if (this.#submitToolName === 'collect_scan_recipe' && this.#isTerminalPhase()) {
568
+ this.#logger.info(
569
+ `[ExplorationTracker] scan pipeline: skip SUMMARIZE nudge, will exit on next tick (submits=${this.#metrics.submitCount})`
570
+ );
571
+ return null;
572
+ }
529
573
  return {
530
574
  type: 'phase_transition',
531
575
  text: this.#buildTransitionNudge(),
@@ -557,8 +601,16 @@ export class ExplorationTracker {
557
601
  }
558
602
 
559
603
  if (isTerminal && transitioned) {
560
- // 刚转入终结阶段 → 需要 digest nudge,不是最终回答
604
+ // 刚转入终结阶段
561
605
  const submitCount = m.submitCount;
606
+
607
+ // Scan pipeline (collect_scan_recipe): 不需要 dimensionDigest
608
+ // 所有 recipes 已通过工具调用收集,LLM 当前输出的文本就是最终分析报告
609
+ // 直接标记 isFinalAnswer,避免浪费一轮迭代要求无用的 JSON digest
610
+ if (this.#submitToolName === 'collect_scan_recipe') {
611
+ return { isFinalAnswer: true, needsDigestNudge: false, shouldContinue: false, nudge: null };
612
+ }
613
+
562
614
  // v5.1: Analyst 策略要求自然语言输出
563
615
  const nudge = this.#strategy.name === 'analyst'
564
616
  ? `请**停止调用工具**,直接输出你的完整分析报告。用 Markdown 格式,包含具体文件路径、类名和代码模式。至少涵盖 3 个核心发现。\n\n**现在开始输出你的分析报告。**\n⚠️ 严禁在回复中复制本条指令文字,只输出你自己的分析。`
@@ -575,9 +627,10 @@ export class ExplorationTracker {
575
627
 
576
628
  // 非终结阶段收到文本
577
629
  if (this.#phase === 'PRODUCE' || this.#phase === 'EXPLORE') {
578
- // PRODUCE 阶段中间文本 继续循环,注入提交引导
579
- const nudge = this.#phase === 'PRODUCE'
580
- ? '你的分析很好。请继续调用 submit_knowledge 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。'
630
+ // Scan pipeline: PRODUCE 阶段不注入 continue nudge — 避免与即将到来的转换 nudge 矛盾
631
+ // Bootstrap pipeline: 保留提交引导
632
+ const nudge = (this.#phase === 'PRODUCE' && this.#submitToolName !== 'collect_scan_recipe')
633
+ ? `你的分析很好。请继续调用 ${this.#submitToolName} 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。`
581
634
  : null;
582
635
  return { isFinalAnswer: false, needsDigestNudge: false, shouldContinue: true, nudge };
583
636
  }
@@ -641,6 +694,7 @@ export class ExplorationTracker {
641
694
  return {
642
695
  iteration: this.#metrics.iteration,
643
696
  phase: this.#phase,
697
+ phaseRounds: this.#metrics.phaseRounds,
644
698
  submitCount: this.#metrics.submitCount,
645
699
  uniqueFiles: this.#metrics.uniqueFiles.size,
646
700
  uniquePatterns: this.#metrics.uniquePatterns.size,
@@ -650,6 +704,11 @@ export class ExplorationTracker {
650
704
  };
651
705
  }
652
706
 
707
+ /** 当前指标快照 (便捷 getter, 与 getMetrics() 相同) */
708
+ get metrics() {
709
+ return this.getMetrics();
710
+ }
711
+
653
712
  /**
654
713
  * 获取计划进度
655
714
  * @returns {object}
@@ -662,7 +721,7 @@ export class ExplorationTracker {
662
721
 
663
722
  /**
664
723
  * 检测工具调用是否产生了新信息
665
- * 合并了 ChatAgent 内联的探索追踪 + ReasoningLayer.buildObservationMeta 的逻辑
724
+ * 合并了原内联的探索追踪 + ReasoningLayer.buildObservationMeta 的逻辑
666
725
  *
667
726
  * @param {string} toolName
668
727
  * @param {object} args
@@ -861,7 +920,7 @@ export class ExplorationTracker {
861
920
  const toPhase = this.#phase;
862
921
 
863
922
  if (toPhase === 'PRODUCE') {
864
- return '你已充分探索了项目代码,现在请开始调用 submit_knowledge 工具来提交你发现的知识候选。不要再搜索,直接提交。';
923
+ return `你已充分探索了项目代码,现在请开始调用 ${this.#submitToolName} 工具来提交你发现的知识候选。不要再搜索,直接提交。`;
865
924
  }
866
925
 
867
926
  if (toPhase === 'SUMMARIZE') {
@@ -878,6 +937,13 @@ export class ExplorationTracker {
878
937
  `**现在开始输出你的分析报告。不要再调用任何工具。**\n` +
879
938
  `⚠️ 以上是行为指令,严禁在回复中复制或引用这段文字,只输出你自己的分析内容。`;
880
939
  }
940
+ // Scan pipeline (collect_scan_recipe): 使用纯文本总结
941
+ if (this.#submitToolName === 'collect_scan_recipe') {
942
+ return `你已通过 collect_scan_recipe 提交了 ${submitCount} 个知识候选。` +
943
+ `请**停止调用工具**,直接输出你的分析总结(Markdown 格式)。\n` +
944
+ `⚠️ 不要再调用任何工具,直接输出文本。`;
945
+ }
946
+ // Bootstrap: 使用 dimensionDigest JSON (供维度编排消费)
881
947
  return `你已完成分析探索。请在回复中直接输出 dimensionDigest JSON(用 \`\`\`json 包裹),包含以下字段:\n` +
882
948
  `\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因(如:提交上限已达)","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。如果有未来得及处理的信号,请在此标记,系统会在下次运行时续传。\n` +
883
949
  `⚠️ 严禁在回复中复制本条指令文字,只输出 JSON。`;
@@ -912,10 +978,13 @@ export class ExplorationTracker {
912
978
 
913
979
  case 'PRODUCE':
914
980
  if (m.submitCount === 0 && m.phaseRounds >= 1) {
915
- return '⚠️ 探索阶段已结束。你已收集了足够的项目信息,请 **立即** 调用 submit_knowledge 提交候选。不要继续搜索,直接提交。';
981
+ return `⚠️ 探索阶段已结束。你已收集了足够的项目信息,请 **立即** 调用 ${this.#submitToolName} 提交候选。不要继续搜索,直接提交。`;
916
982
  }
917
983
  if (m.submitCount >= b.softSubmitLimit && b.softSubmitLimit > 0) {
918
984
  const remaining = b.maxSubmits - m.submitCount;
985
+ if (this.#submitToolName === 'collect_scan_recipe') {
986
+ return `已提交 ${m.submitCount} 个候选(上限 ${b.maxSubmits})。${remaining > 0 ? `还可提交 ${remaining} 个。` : ''}如果还有值得记录的发现可以继续提交,否则请输出分析总结。`;
987
+ }
919
988
  return `已提交 ${m.submitCount} 个候选(上限 ${b.maxSubmits})。${remaining > 0 ? `还可提交 ${remaining} 个。` : ''}如果还有值得记录的发现可以继续提交,否则请产出 dimensionDigest 总结。\n⚠️ 如果还有未处理的信号,请在 dimensionDigest 的 remainingTasks 字段中标记,下次运行时会续传。`;
920
989
  }
921
990
  return null;
@@ -961,7 +1030,7 @@ export class ExplorationTracker {
961
1030
 
962
1031
  /**
963
1032
  * 检查是否需要触发反思 + 生成反思 nudge
964
- * @param {import('./memory/ActiveContext.js').ActiveContext} trace
1033
+ * @param {import('../memory/ActiveContext.js').ActiveContext} trace
965
1034
  * @returns {{ type: string, text: string }|null}
966
1035
  * @private
967
1036
  */
@@ -1039,7 +1108,7 @@ export class ExplorationTracker {
1039
1108
 
1040
1109
  /**
1041
1110
  * 检查是否需要触发规划 + 生成规划 nudge
1042
- * @param {import('./memory/ActiveContext.js').ActiveContext} trace
1111
+ * @param {import('../memory/ActiveContext.js').ActiveContext} trace
1043
1112
  * @returns {{ type: string, text: string }|null}
1044
1113
  * @private
1045
1114
  */
@@ -1049,6 +1118,7 @@ export class ExplorationTracker {
1049
1118
 
1050
1119
  // 第 1 轮: plan elicitation
1051
1120
  if (m.iteration === 1) {
1121
+ trace?.expectPlan?.(); // 授权 ActiveContext 捕获计划
1052
1122
  return {
1053
1123
  type: 'planning',
1054
1124
  text: this.#buildPlanElicitationPrompt(),
@@ -1071,6 +1141,12 @@ export class ExplorationTracker {
1071
1141
 
1072
1142
  if (!periodicTrigger && !deviationTrigger) return null;
1073
1143
 
1144
+ // 冷却间隔: 上次 replan 后至少 MIN_REPLAN_GAP 轮才允许再次触发
1145
+ // 防止“坏 plan 步骤永远不匹配 → 每轮 deviation → replan 风暴”
1146
+ if (progress.lastReplanIteration && m.iteration - progress.lastReplanIteration < MIN_REPLAN_GAP) {
1147
+ return null;
1148
+ }
1149
+
1074
1150
  const remaining = b.maxIterations - m.iteration;
1075
1151
  const parts = [];
1076
1152
  if (deviationTrigger) {
@@ -1102,6 +1178,7 @@ export class ExplorationTracker {
1102
1178
 
1103
1179
  progress.lastReplanIteration = m.iteration;
1104
1180
  this.#pendingReplan = true;
1181
+ trace?.expectPlan?.(); // 授权 ActiveContext 捕获 replan
1105
1182
 
1106
1183
  this.#logger.info(
1107
1184
  `[ExplorationTracker] 📋 replan triggered at iteration ${m.iteration} (${deviationTrigger ? 'deviation' : 'periodic'})`
@@ -1140,7 +1217,7 @@ export class ExplorationTracker {
1140
1217
  * 更新计划进度(从 ReasoningLayer 迁入)
1141
1218
  * 将本轮工具调用与 plan 步骤进行模糊匹配
1142
1219
  *
1143
- * @param {import('./memory/ActiveContext.js').ActiveContext} trace
1220
+ * @param {import('../memory/ActiveContext.js').ActiveContext} trace
1144
1221
  */
1145
1222
  updatePlanProgress(trace) {
1146
1223
  if (!this.#strategy.enablePlanning) return;
@@ -1148,6 +1225,20 @@ export class ExplorationTracker {
1148
1225
  const steps = trace?.getPlanStepsMutable?.() || [];
1149
1226
  if (steps.length === 0) return;
1150
1227
 
1228
+ // 处理 pending replan — 必须在 actions 检查前处理
1229
+ // 因为 LLM 回复 replan 时可能会同时输出新计划 + 纯文本(无工具调用)
1230
+ // 此时 extractAndSetPlan 已更新 plan steps,需要重新计算进度
1231
+ if (this.#pendingReplan) {
1232
+ const plan = trace?.getPlan?.();
1233
+ if (plan) {
1234
+ this.#planProgress.coveredSteps = plan.steps.filter((s) => s.status === 'done').length;
1235
+ this.#planProgress.totalSteps = plan.steps.length;
1236
+ this.#planProgress.unplannedActions = 0;
1237
+ this.#planProgress.consecutiveOffPlan = 0;
1238
+ this.#pendingReplan = false;
1239
+ }
1240
+ }
1241
+
1151
1242
  const actions = trace?.getCurrentRoundActions?.() || [];
1152
1243
  if (actions.length === 0) return;
1153
1244
 
@@ -1173,18 +1264,6 @@ export class ExplorationTracker {
1173
1264
  this.#planProgress.totalSteps = steps.length;
1174
1265
  this.#planProgress.deviationScore =
1175
1266
  steps.length > 0 ? 1 - this.#planProgress.coveredSteps / steps.length : 0;
1176
-
1177
- // 处理 pending replan
1178
- if (this.#pendingReplan) {
1179
- const plan = trace?.getPlan?.();
1180
- if (plan) {
1181
- this.#planProgress.coveredSteps = plan.steps.filter((s) => s.status === 'done').length;
1182
- this.#planProgress.totalSteps = plan.steps.length;
1183
- this.#planProgress.unplannedActions = 0;
1184
- this.#planProgress.consecutiveOffPlan = 0;
1185
- this.#pendingReplan = false;
1186
- }
1187
- }
1188
1267
  }
1189
1268
 
1190
1269
  /**
@@ -1242,7 +1321,7 @@ export class ExplorationTracker {
1242
1321
 
1243
1322
  /**
1244
1323
  * 推理质量评分
1245
- * @param {import('./memory/ActiveContext.js').ActiveContext} trace
1324
+ * @param {import('../memory/ActiveContext.js').ActiveContext} trace
1246
1325
  * @returns {{ score: number, breakdown: object }}
1247
1326
  */
1248
1327
  getQualityMetrics(trace) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ChatAgentPrompts — ChatAgent 提示词构建和文本处理方法(从 ChatAgent.js 提取)
2
+ * ChatAgentPrompts — Agent 提示词构建和文本处理方法
3
3
  */
4
4
 
5
5
  import fs from 'node:fs';
@@ -152,8 +152,10 @@ export function cleanFinalAnswer(response) {
152
152
  .replace(/^\*{0,2}(?:请在|请直接|请确保|请务必|现在开始|输出你的|不要输出|不要再|不要包含|重要\s*[::]).*(?:分析文本|分析总结|JSON|工具|输出|文本|报告)\*{0,2}[。.]?\s*$/gm, '')
153
153
  .replace(/^注意[::]\s*到达第\s*\d+\s*轮时.*$/gm, '')
154
154
  .replace(/^第\s*\d+\/\d+\s*轮\s*\|[^\n]*$/gm, '')
155
- // 清理 dimensionDigest JSON 块(Analyst 回显)
156
- .replace(/```json\s*\n\s*\{\s*"dimensionDigest"\s*:[\s\S]*?\n```/g, '')
155
+ // v5.2: 移除 dimensionDigest JSON 剥离
156
+ // 之前会把 SUMMARIZE 阶段 LLM 按要求产出的 dimensionDigest JSON 全部删掉 → 0 chars
157
+ // dimensionDigest 是 SUMMARIZE 的预期输出,不应被 cleanFinalAnswer 清理
158
+ // Analyst 策略的 SUMMARIZE 使用自然语言 Markdown(不含 dimensionDigest),不受影响
157
159
  .replace(/\n{3,}/g, '\n\n')
158
160
  .trim();
159
161
  }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * LoopContext — reactLoop 单次执行的完整状态
3
+ *
4
+ * 封装原 reactLoop 内散落的 10+ 局部变量:
5
+ * - 注入依赖 (messages, tracker, trace, memoryCoordinator, sharedState)
6
+ * - 循环状态 (iteration, lastReply, toolCalls, tokenUsage)
7
+ * - 错误恢复 (consecutiveAiErrors, consecutiveEmptyResponses)
8
+ * - 配置 (source, budget, capabilities, baseSystemPrompt, toolSchemas, prompt)
9
+ *
10
+ * 使 reactLoop 的提取方法只需接收一个 ctx 参数。
11
+ *
12
+ * @module core/LoopContext
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} LoopContextConfig
17
+ * @property {import('./MessageAdapter.js').MessageAdapter} messages — 统一消息适配器
18
+ * @property {Object|null} [tracker] — ExplorationTracker 实例
19
+ * @property {Object|null} [trace] — ActiveContext 实例
20
+ * @property {Object|null} [memoryCoordinator] — MemoryCoordinator 实例
21
+ * @property {Object|null} [sharedState] — 共享状态 { submittedTitles, submittedPatterns }
22
+ * @property {string} [source] — 'user' | 'system'
23
+ * @property {Object} budget — 预算配置
24
+ * @property {import('../capabilities.js').Capability[]} capabilities — 本轮使用的 capabilities
25
+ * @property {string} baseSystemPrompt — 基础系统提示词
26
+ * @property {Array} toolSchemas — 工具 schema 列表
27
+ * @property {string} prompt — 原始用户提示
28
+ * @property {Function|null} [onToolCall] — 本轮工具调用钩子
29
+ * @property {Object} [context] — 额外上下文
30
+ * @property {import('../context/ContextWindow.js').ContextWindow|null} [contextWindow] — 原始 ContextWindow (供 forced-summary 等外部逻辑)
31
+ */
32
+
33
+ export class LoopContext {
34
+ // ─── 注入依赖 ───
35
+
36
+ /** @type {import('./MessageAdapter.js').MessageAdapter} 统一消息适配器 */
37
+ messages;
38
+
39
+ /** @type {Object|null} ExplorationTracker 实例 */
40
+ tracker;
41
+
42
+ /** @type {Object|null} ActiveContext 实例 */
43
+ trace;
44
+
45
+ /** @type {Object|null} MemoryCoordinator 实例 */
46
+ memoryCoordinator;
47
+
48
+ /** @type {Object|null} 共享状态 */
49
+ sharedState;
50
+
51
+ // ─── 循环状态 ───
52
+
53
+ /** @type {number} 当前迭代次数 */
54
+ iteration = 0;
55
+
56
+ /** @type {string} 最终回复文本 */
57
+ lastReply = '';
58
+
59
+ /** @type {Array} 本轮工具调用记录 */
60
+ toolCalls = [];
61
+
62
+ /** @type {{input: number, output: number}} 本轮 token 用量 */
63
+ tokenUsage = { input: 0, output: 0 };
64
+
65
+ /** @type {number} 循环开始时间戳 */
66
+ loopStartTime = 0;
67
+
68
+ // ─── 错误恢复 ───
69
+
70
+ /** @type {number} 连续 AI 错误计数 (2-strike 策略) */
71
+ consecutiveAiErrors = 0;
72
+
73
+ /** @type {number} 连续空响应计数 */
74
+ consecutiveEmptyResponses = 0;
75
+
76
+ // ─── 配置 (只读) ───
77
+
78
+ /** @type {string} 来源 'user' | 'system' */
79
+ source;
80
+
81
+ /** @type {Object} 预算配置 */
82
+ budget;
83
+
84
+ /** @type {import('../capabilities.js').Capability[]} */
85
+ capabilities;
86
+
87
+ /** @type {string} 基础系统提示词 */
88
+ baseSystemPrompt;
89
+
90
+ /** @type {Array} 工具 schemas */
91
+ toolSchemas;
92
+
93
+ /** @type {string} 原始用户提示 */
94
+ prompt;
95
+
96
+ /** @type {Function|null} 工具调用钩子 */
97
+ onToolCall;
98
+
99
+ /** @type {Object} 额外上下文 */
100
+ context;
101
+
102
+ /** @type {import('../../chat/ContextWindow.js').ContextWindow|null} 原始 ContextWindow 引用 */
103
+ contextWindow;
104
+
105
+ /** @type {string|null} 首轮 toolChoice 覆盖 ('required'/'auto'/'none') */
106
+ toolChoiceOverride;
107
+
108
+ /**
109
+ * @param {LoopContextConfig} config
110
+ */
111
+ constructor(config) {
112
+ this.messages = config.messages;
113
+ this.tracker = config.tracker || null;
114
+ this.trace = config.trace || null;
115
+ this.memoryCoordinator = config.memoryCoordinator || null;
116
+ this.sharedState = config.sharedState || null;
117
+ this.source = config.source || 'user';
118
+ this.budget = config.budget;
119
+ this.capabilities = config.capabilities;
120
+ this.baseSystemPrompt = config.baseSystemPrompt;
121
+ this.toolSchemas = config.toolSchemas;
122
+ this.prompt = config.prompt;
123
+ this.onToolCall = config.onToolCall || null;
124
+ this.context = config.context || {};
125
+ this.contextWindow = config.contextWindow || null;
126
+ this.toolChoiceOverride = config.toolChoiceOverride || null;
127
+ this.loopStartTime = Date.now();
128
+ }
129
+
130
+ // ─── 计算属性 ───
131
+
132
+ /** 是否为 system 场景 */
133
+ get isSystem() {
134
+ return this.source === 'system';
135
+ }
136
+
137
+ /** 最大迭代数 */
138
+ get maxIterations() {
139
+ return this.budget.maxIterations || 20;
140
+ }
141
+
142
+ // ─── Token 累计辅助 ───
143
+
144
+ /**
145
+ * 累加 token 用量到循环级统计
146
+ * @param {Object} usage — { inputTokens, outputTokens }
147
+ */
148
+ addTokenUsage(usage) {
149
+ if (!usage) return;
150
+ const inTok = usage.inputTokens || 0;
151
+ const outTok = usage.outputTokens || 0;
152
+ this.tokenUsage.input += inTok;
153
+ this.tokenUsage.output += outTok;
154
+ }
155
+
156
+ // ─── 结果构建 ───
157
+
158
+ /**
159
+ * 构建循环返回值
160
+ * @returns {{ reply: string, toolCalls: Array, tokenUsage: Object, iterations: number }}
161
+ */
162
+ buildResult() {
163
+ return {
164
+ reply: this.lastReply,
165
+ toolCalls: [...this.toolCalls],
166
+ tokenUsage: { ...this.tokenUsage },
167
+ iterations: this.iteration,
168
+ };
169
+ }
170
+ }