autosnippet 3.2.9 → 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.
@@ -146,7 +146,7 @@ export const MANIFEST = [
146
146
  src: 'pre-commit-guard.sh',
147
147
  dest: null, // 动态决定:.husky/pre-commit 或 .git/hooks/pre-commit
148
148
  strategy: 'create-only',
149
- on: 'setup',
149
+ on: 'manual', // 暂不在 setup 中强制安装 git 工作流
150
150
  category: 'pre-commit-hook',
151
151
  chmod: true,
152
152
  resolveDest: 'resolvePreCommitDest',
@@ -115,10 +115,12 @@ export class GoogleGeminiProvider extends AiProvider {
115
115
  ];
116
116
  }
117
117
 
118
- // toolChoice → Gemini mode
119
- body.toolConfig = {
120
- functionCallingConfig: { mode: this.#toGeminiMode(toolChoice) },
121
- };
118
+ // toolChoice → Gemini mode (仅在有工具声明时设置,无工具时设 toolConfig 可能导致空响应)
119
+ if (body.tools) {
120
+ body.toolConfig = {
121
+ functionCallingConfig: { mode: this.#toGeminiMode(toolChoice) },
122
+ };
123
+ }
122
124
 
123
125
  // 系统指令
124
126
  if (systemPrompt) {
@@ -24,6 +24,7 @@ import { SessionStore } from '../../../../../service/agent/memory/SessionStore.j
24
24
  import { AgentMessage } from '../../../../../service/agent/AgentMessage.js';
25
25
  import { BudgetPolicy } from '../../../../../service/agent/policies.js';
26
26
  import { PRESETS } from '../../../../../service/agent/presets.js';
27
+ import { ANALYST_BUDGET } from '../../../../../service/agent/domain/insight-analyst.js';
27
28
  import { ContextWindow } from '../../../../../service/agent/context/ContextWindow.js';
28
29
  import { ExplorationTracker } from '../../../../../service/agent/context/ExplorationTracker.js';
29
30
  import { clearCheckpoints, loadCheckpoints, saveDimensionCheckpoint } from './checkpoint.js';
@@ -607,9 +608,12 @@ export async function fillDimensionsV3(fillContext) {
607
608
  outputType: dimConfig.outputType || 'analysis',
608
609
  // ── 引擎增强参数 (PipelineStrategy → reactLoop 透传) ──
609
610
  contextWindow: agentFactory.createContextWindow({ isSystem: true }),
611
+ // B1 fix: 分析阶段使用 analyst 策略 (SCAN→EXPLORE→VERIFY→SUMMARIZE)
612
+ // 而非 bootstrap (EXPLORE→PRODUCE→SUMMARIZE),避免 PRODUCE nudge 浪费轮次
613
+ // B3 fix: 透传完整 ANALYST_BUDGET (searchBudget/maxSubmits/softSubmitLimit/idleRoundsToExit)
610
614
  tracker: ExplorationTracker.resolve(
611
- { source: 'system', dimensionMeta: dimConfig },
612
- { maxIterations: PRESETS.insight.strategy.stages[0].budget?.maxIterations || 24 },
615
+ { source: 'system', strategy: 'analyst' },
616
+ { ...ANALYST_BUDGET },
613
617
  ),
614
618
  trace: memoryCoordinator.getActiveContext(analystScopeId),
615
619
  memoryCoordinator,
@@ -111,11 +111,7 @@ router.post(
111
111
  // 异步执行,不 await
112
112
  (async () => {
113
113
  try {
114
- const result = await agentFactory.scanKnowledge({
115
- label: 'knowledge-graph',
116
- files: [],
117
- task: 'relations',
118
- });
114
+ const result = await agentFactory.discoverRelations();
119
115
  discoverTask.status = 'done';
120
116
  discoverTask.finishedAt = new Date().toISOString();
121
117
  discoverTask.discovered = result.discovered || 0;
@@ -23,8 +23,8 @@ import { BudgetPolicy, PolicyEngine } from './policies.js';
23
23
  import { getPreset, resolveStrategy } from './presets.js';
24
24
  import { ContextWindow } from './context/ContextWindow.js';
25
25
  import { ExplorationTracker } from './context/ExplorationTracker.js';
26
- import { ANALYST_SYSTEM_PROMPT } from './domain/insight-analyst.js';
27
- import { SCAN_TASK_CONFIGS } from './domain/scan-prompts.js';
26
+ import { MemoryCoordinator } from './memory/MemoryCoordinator.js';
27
+ import { SCAN_TASK_CONFIGS, buildScanPipelineStages, buildRelationsPipelineStages } from './domain/scan-prompts.js';
28
28
 
29
29
  export class AgentFactory {
30
30
  #container;
@@ -138,26 +138,63 @@ export class AgentFactory {
138
138
  * 抽取 bootstrap orchestrator 中创建 ExplorationTracker / ContextWindow / source 的
139
139
  * 通用逻辑,供 scanKnowledge 等系统场景共用完整的多轮 Agent 框架。
140
140
  *
141
- * bootstrap orchestrator 不使用此方法(它还需要领域特定的 MemoryCoordinator / dimContext 等),
141
+ * bootstrap orchestrator 保持一致的 MemoryCoordinator 管理模式:
142
+ * - 创建轻量级 MemoryCoordinator (无 PersistentMemory/SessionStore)
143
+ * - 通过 MC.createDimensionScope 创建并注册 ActiveContext
144
+ * - trace 从 MC.getActiveContext 获取 (统一生命周期管理)
145
+ * - memoryCoordinator 传入 strategyContext,供 reactLoop 每轮 buildDynamicMemoryPrompt
146
+ *
147
+ * 与 bootstrap orchestrator 对齐的关键字段:
148
+ * - activeContext: 与 trace 同一实例 — insightGateEvaluator 通过此字段
149
+ * 决定走 buildAnalysisArtifact (完整: findings/evidenceMap/negativeSignals)
150
+ * 还是 buildAnalysisReport (降级: 仅文本)
151
+ * - outputType: 'candidate' — 设置 quality_gate 的评判标准
152
+ * - dimId: 维度 ID — buildAnalysisArtifact 的 dimensionId 参数
153
+ *
154
+ * bootstrap orchestrator 不使用此方法(它还需要领域特定的 SessionStore / dimContext 等),
142
155
  * 但引擎层基础设施是一致的。
143
156
  *
144
157
  * @param {Object} [opts]
145
158
  * @param {Object} [opts.budget] — 预算覆盖 (透传给 ExplorationTracker)
146
159
  * @param {string} [opts.trackerStrategy='analyst'] — tracker 策略名: 'analyst' | 'producer' | 'bootstrap'
147
- * @returns {{ contextWindow: ContextWindow, tracker: ExplorationTracker, sharedState: Object, source: string }}
160
+ * @param {string} [opts.label='default'] 作用域标签 (用于 scopeId 命名 + dimId)
161
+ * @param {string} [opts.lang] — 项目语言 (透传给 sharedState._projectLanguage)
162
+ * @returns {{ contextWindow: ContextWindow, tracker: ExplorationTracker, trace: import('./memory/ActiveContext.js').ActiveContext, activeContext: import('./memory/ActiveContext.js').ActiveContext, memoryCoordinator: MemoryCoordinator, outputType: string, dimId: string, sharedState: Object, source: string, scopeId: string }}
148
163
  */
149
- buildSystemContext({ budget, trackerStrategy = 'analyst' } = {}) {
164
+ buildSystemContext({ budget, trackerStrategy = 'analyst', label = 'default', lang } = {}) {
165
+ // 创建轻量级 MemoryCoordinator (scan 场景无 PersistentMemory/SessionStore)
166
+ const mc = new MemoryCoordinator({ mode: 'bootstrap' });
167
+ const scopeId = `scan:${label}`;
168
+ mc.createDimensionScope(scopeId);
169
+
170
+ const activeContext = mc.getActiveContext(scopeId);
171
+
150
172
  return {
151
173
  contextWindow: this.createContextWindow({ isSystem: true }),
152
174
  tracker: ExplorationTracker.resolve(
153
175
  { source: 'system', strategy: trackerStrategy },
154
176
  budget || {},
155
177
  ),
178
+ // trace & activeContext 是同一个 ActiveContext 实例
179
+ // trace: AgentRuntime reactLoop 使用 (startRound/setThought/endRound)
180
+ // activeContext: insightGateEvaluator 检查此字段决定 artifact 路径
181
+ trace: activeContext,
182
+ activeContext,
183
+ memoryCoordinator: mc,
184
+ // outputType: bootstrap orchestrator 设为 'candidate'(insightGateEvaluator 的评判标准)
185
+ outputType: 'candidate',
186
+ // dimId: buildAnalysisArtifact 的 dimensionId 参数
187
+ dimId: label,
156
188
  sharedState: {
157
189
  submittedTitles: new Set(),
158
190
  submittedPatterns: new Set(),
191
+ // G6: _projectLanguage — ToolExecutionPipeline 透传给工具 handler 上下文
192
+ _projectLanguage: lang || null,
193
+ // G7: _dimensionScopeId — ToolExecutionPipeline 透传给工具 handler (note_finding scope)
194
+ _dimensionScopeId: scopeId,
159
195
  },
160
196
  source: 'system',
197
+ scopeId,
161
198
  };
162
199
  }
163
200
 
@@ -209,15 +246,19 @@ export class AgentFactory {
209
246
  // ─── 领域语义方法 (意图驱动, Agent 直接完成 AI 推理) ─────
210
247
 
211
248
  /**
212
- * 统一知识扫描 — 走 insight 管线 (Analyze → QualityGate → Produce)
249
+ * 统一知识扫描 — 走 insight 管线 (Analyze → QualityGate → Produce → RejectionGate)
250
+ *
251
+ * extract 和 summarize 共享工具驱动管线 (collect_scan_recipe),
252
+ * 仅 Produce 阶段的 systemPrompt 和预算不同:
253
+ * - extract: 多文件 target 扫描,24 iter analyze,24 iter produce
254
+ * - summarize: 单文件/代码片段,12 iter analyze,12 iter produce
213
255
  *
214
- * 三种任务模式共享同一管线,仅 Produce 阶段的 systemPrompt 不同。
215
- * 替代: extractRecipes + summarizeCode + discoverRelations
256
+ * 关系发现请使用单独的 discoverRelations() 方法。
216
257
  *
217
258
  * @param {Object} opts
218
259
  * @param {string} opts.label — 上下文标签(target 名 / 文件名)
219
260
  * @param {Array<{name, content, language?}>} opts.files — 源文件
220
- * @param {'extract'|'summarize'|'relations'} [opts.task='extract'] — 任务类型
261
+ * @param {'extract'|'summarize'} [opts.task='extract'] — 任务类型
221
262
  * @param {string} [opts.lang] — 语言提示
222
263
  * @param {boolean} [opts.comprehensive] — 深度扫描标志
223
264
  * @returns {Promise<Object>} — task-specific JSON
@@ -229,60 +270,29 @@ export class AgentFactory {
229
270
  }
230
271
  const { producePrompt, fallback } = taskConfig;
231
272
 
232
- // relations task 需要知识查询工具
233
- const caps = task === 'relations'
234
- ? ['code_analysis', 'knowledge_production']
235
- : ['code_analysis'];
236
-
237
- // produce 阶段: extract 使用 scan_production(工具驱动,与冷启动一致),
238
- // summarize/relations 保持纯文本输出
239
- const produceCaps = task === 'extract'
240
- ? ['scan_production']
241
- : [];
242
-
243
- // ── Pipeline 配置 (与冷启动对齐: 24 轮标准) ──
244
- const analyzeMaxIter = 24;
245
- const stages = [
246
- {
247
- name: 'analyze',
248
- capabilities: caps,
249
- budget: { maxIterations: analyzeMaxIter, maxTokens: 8192, temperature: 0.3 },
250
- systemPrompt: ANALYST_SYSTEM_PROMPT,
251
- },
252
- {
253
- name: 'produce',
254
- capabilities: produceCaps,
255
- budget: { maxIterations: 24, temperature: 0.2 },
256
- systemPrompt: producePrompt,
257
- promptTransform: (_input, prev) => {
258
- const analysis = prev.analyze?.reply || '';
259
- if (analysis.length >= 200) {
260
- return `将以下代码分析转化为结构化输出。\n\n## 代码分析\n${analysis}`;
261
- }
262
- // Fallback: analyze reply 不足时直接提供源代码
263
- const fileCtx = (files || []).slice(0, 15).map(f => {
264
- const body = f.content.length > 1200
265
- ? f.content.slice(0, 1200) + '\n// ... (truncated)'
266
- : f.content;
267
- return `### ${f.relativePath}\n\`\`\`\n${body}\n\`\`\``;
268
- }).join('\n\n');
269
- const preamble = analysis
270
- ? `## 部分分析\n${analysis}\n\n`
271
- : '';
272
- return `${preamble}分析以下 ${files?.length || 0} 个源文件,提取知识 Recipe。\n\n${fileCtx}`;
273
- },
274
- },
275
- ];
273
+ // extract summarize 都使用 code_analysis 分析 + scan_production 工具驱动
274
+ const analyzeCaps = ['code_analysis'];
275
+ const produceCaps = ['scan_production'];
276
+
277
+ // ── 统一 4 阶段 Pipeline (与冷启动 orchestrator 对齐) ──
278
+ // summarize (单文件) 使用较低预算
279
+ const analyzeMaxIter = task === 'summarize' ? 12 : 24;
280
+ const stages = buildScanPipelineStages({
281
+ task,
282
+ producePrompt,
283
+ analyzeCaps,
284
+ produceCaps,
285
+ files,
286
+ analyzeMaxIter,
287
+ });
276
288
 
277
289
  // ── 创建 Runtime — 使用 insight preset + 对齐 policies ──
278
- // policies override: 让 Runtime 级 BudgetPolicy 与 stage budget 对齐
279
- // (避免 insight preset 的 maxIterations:24 与 stage 的 10 冲突)
280
290
  const runtime = this.createRuntime(PresetName.INSIGHT, {
281
- strategy: { type: 'pipeline', stages },
282
- capabilities: caps,
291
+ strategy: { type: 'pipeline', maxRetries: 1, stages },
292
+ capabilities: analyzeCaps,
283
293
  policies: [
284
294
  new BudgetPolicy({
285
- maxIterations: 30, // 24 stage budget + 6 tracker grace (SUMMARIZE 2轮 + 硬上限 buffer)
295
+ maxIterations: 30, // 24 stage budget + 6 tracker grace
286
296
  maxTokens: 8192,
287
297
  temperature: 0.3,
288
298
  timeoutMs: 600_000,
@@ -295,20 +305,12 @@ export class AgentFactory {
295
305
  runtime.setFileCache(files);
296
306
  }
297
307
 
298
- // ── 完整的系统级多轮基础设施 ──
299
- // ExplorationTracker (analyst 策略) 提供:
300
- // - 四阶段管理: SCAN → EXPLORE → VERIFY → SUMMARIZE
301
- // - Nudge: budget_warning / convergence / reflection / force_exit
302
- // - 阶段 toolChoice: SUMMARIZE='none' 强制 LLM 输出文本
303
- // - Graceful exit: 终结阶段 2 轮 grace + 硬上限兜底
304
- // ContextWindow 提供:
305
- // - 自动三级递进上下文压缩
306
- // source:'system' 提供:
307
- // - 空响应自动重试 (2 次)
308
- // - finalize 强制摘要兜底
308
+ // ── 完整的系统级多轮基础设施 (含 MemoryCoordinator 管理 ActiveContext) ──
309
309
  const systemCtx = this.buildSystemContext({
310
310
  budget: { maxIterations: analyzeMaxIter },
311
311
  trackerStrategy: 'analyst',
312
+ label: `${task}:${label}`,
313
+ lang,
312
314
  });
313
315
 
314
316
  // ── 执行 ──
@@ -317,36 +319,79 @@ export class AgentFactory {
317
319
  );
318
320
  const result = await runtime.execute(message, { strategyContext: systemCtx });
319
321
 
320
- // ── 提取结果 ──
321
- if (task === 'extract') {
322
- // 工具驱动模式: tool calls 中提取 collected recipes (与冷启动相同的提取方式)
323
- const allToolCalls = result.toolCalls || [];
324
- const recipes = allToolCalls
325
- .filter(tc => (tc.tool || tc.name) === 'collect_scan_recipe')
326
- .map(tc => {
327
- // 优先从 handler 返回的 recipe 对象取,回退到参数
328
- const res = tc.result;
329
- if (res && typeof res === 'object' && res.status === 'collected' && res.recipe) {
330
- return res.recipe;
331
- }
332
- return null;
333
- })
334
- .filter(Boolean);
335
-
336
- if (recipes.length > 0) {
337
- return { targetName: label, extracted: recipes.length, recipes };
322
+ // ── 提取结果 — extract 和 summarize 统一从 toolCalls 提取 ──
323
+ const allToolCalls = result.toolCalls || [];
324
+ const recipes = allToolCalls
325
+ .filter(tc => (tc.tool || tc.name) === 'collect_scan_recipe')
326
+ .map(tc => {
327
+ const res = tc.result;
328
+ if (res && typeof res === 'object' && res.status === 'collected' && res.recipe) {
329
+ return res.recipe;
330
+ }
331
+ return null;
332
+ })
333
+ .filter(Boolean);
334
+
335
+ if (recipes.length > 0) {
336
+ // summarize 向后兼容: 扁平化首个 recipe 为 { title, summary, usageGuide, ... }
337
+ if (task === 'summarize') {
338
+ const first = recipes[0];
339
+ return {
340
+ title: first.title || '',
341
+ summary: first.description || first.summary || '',
342
+ usageGuide: first.usageGuide || '',
343
+ category: first.category || '',
344
+ headers: first.headers || [],
345
+ tags: first.tags || [],
346
+ trigger: first.trigger || '',
347
+ recipes,
348
+ extracted: recipes.length,
349
+ };
338
350
  }
339
-
340
- // Fallback: 工具未被调用时,尝试从文本解析
341
- const produceReply = result.phases?.produce?.reply || result.reply;
342
- return this.#parseJsonResponse(produceReply, fallback(label));
351
+ return { targetName: label, extracted: recipes.length, recipes };
343
352
  }
344
353
 
345
- // summarize / relations: 纯文本 JSON 输出
354
+ // Fallback: 工具未被调用时,尝试从文本解析
346
355
  const produceReply = result.phases?.produce?.reply || result.reply;
347
356
  return this.#parseJsonResponse(produceReply, fallback(label));
348
357
  }
349
358
 
359
+ /**
360
+ * 知识图谱关系发现 — 独立管线 (Explore → Synthesize)
361
+ *
362
+ * 与 scanKnowledge 不同,relations 不需要源文件输入,
363
+ * 而是通过查询知识库 + 读取源码发现知识条目间的语义关系。
364
+ *
365
+ * @param {Object} [opts]
366
+ * @param {number} [opts.batchSize=20] — 批次大小提示
367
+ * @returns {Promise<{ analyzed: number, relations: Array }>}
368
+ */
369
+ async discoverRelations({ batchSize = 20 } = {}) {
370
+ const stages = buildRelationsPipelineStages();
371
+
372
+ const runtime = this.createRuntime(PresetName.INSIGHT, {
373
+ strategy: { type: 'pipeline', stages },
374
+ capabilities: ['knowledge_production', 'code_analysis'],
375
+ policies: [
376
+ new BudgetPolicy({
377
+ maxIterations: 28,
378
+ maxTokens: 8192,
379
+ temperature: 0.3,
380
+ timeoutMs: 420_000,
381
+ }),
382
+ ],
383
+ memory: { enabled: false },
384
+ });
385
+
386
+ const message = AgentMessage.internal(
387
+ `探索知识库中所有知识条目之间的语义关系。每批分析约 ${batchSize} 条知识。`,
388
+ );
389
+ const result = await runtime.execute(message);
390
+
391
+ const synthesizeReply = result.phases?.synthesize?.reply || result.reply;
392
+ return this.#parseJsonResponse(synthesizeReply, { analyzed: 0, relations: [] });
393
+ }
394
+
350
395
  /**
351
396
  * AI 翻译 — chat 模式,单轮生成
352
397
  *
@@ -334,7 +334,7 @@ export class AgentRuntime {
334
334
  : this.capabilities;
335
335
 
336
336
  // 构建基础系统提示词
337
- const baseSystemPrompt = systemPromptOverride || this.#buildSystemPrompt(caps, context);
337
+ let baseSystemPrompt = systemPromptOverride || this.#buildSystemPrompt(caps, context);
338
338
 
339
339
  // 收集工具 (caps 为空数组时 = 明确无工具)
340
340
  const allowedTools = this.#collectTools(caps);
@@ -361,6 +361,16 @@ export class AgentRuntime {
361
361
  maxIterations: 20, maxTokens: 4096, temperature: 0.7,
362
362
  };
363
363
 
364
+ // 系统源: 注入轮次预算 (先验锚定阶段节奏)
365
+ // 与旧版 ChatAgent 对齐: 60% 探索 → 80% 验证 → 最后 20% 输出总结
366
+ const isSystem = source === 'system';
367
+ if (isSystem && tracker && !baseSystemPrompt.includes('轮次预算')) {
368
+ const maxIter = budget.maxIterations || 24;
369
+ const exploreEnd = Math.floor(maxIter * 0.6);
370
+ const verifyEnd = Math.floor(maxIter * 0.8);
371
+ baseSystemPrompt += `\n\n## 轮次预算\n- 总轮次: **${maxIter} 轮**\n- 探索阶段: 第 1-${exploreEnd} 轮(搜索和结构化查询)\n- 验证阶段: 第 ${exploreEnd + 1}-${verifyEnd} 轮(读取关键文件确认细节)\n- 总结阶段: 第 ${verifyEnd + 1}-${maxIter} 轮(**停止工具调用,输出分析文本**)\n\n到达第 ${verifyEnd} 轮时你必须开始输出总结,不要继续搜索。`;
372
+ }
373
+
364
374
  // 状态转移
365
375
  this.#safeTransition('start', { prompt: prompt.slice(0, 100) });
366
376
  this.#safeTransition('plan_ready');
@@ -534,10 +544,15 @@ export class AgentRuntime {
534
544
 
535
545
  let llmResult;
536
546
  try {
547
+ // toolChoice='none' 时不发送 toolSchemas —— 部分 LLM (Gemini) 在看到
548
+ // 工具定义但被禁止调用时会返回空内容,导致 SUMMARIZE 阶段失败
549
+ const effectiveToolSchemas = toolChoice === 'none'
550
+ ? undefined
551
+ : (ctx.toolSchemas.length > 0 ? ctx.toolSchemas : undefined);
537
552
  llmResult = await this.aiProvider.chatWithTools(effectivePrompt, {
538
553
  messages: ctx.messages.toMessages(),
539
- toolSchemas: ctx.toolSchemas.length > 0 ? ctx.toolSchemas : undefined,
540
- toolChoice,
554
+ toolSchemas: effectiveToolSchemas,
555
+ toolChoice: effectiveToolSchemas ? toolChoice : undefined,
541
556
  systemPrompt: effectiveSystemPrompt,
542
557
  temperature: ctx.budget.temperature ?? (ctx.isSystem ? 0.3 : 0.7),
543
558
  maxTokens: ctx.budget.maxTokens ?? (ctx.isSystem ? 8192 : 4096),
@@ -563,6 +578,24 @@ export class AgentRuntime {
563
578
 
564
579
  // 空响应重试
565
580
  if (!llmResult.text && !llmResult.functionCalls?.length) {
581
+ // B4 fix: SUMMARIZE 阶段也允许重试 — force_exit nudge 刚注入时 LLM 可能
582
+ // 需要额外一轮才能生成有效输出。与 ExplorationTracker 的 2 轮 grace 对齐,
583
+ // 避免 grace 机制被架空。重试次数由 tracker.phaseRounds 控制而非独立计数。
584
+ const isTerminal = ctx.tracker && (ctx.tracker.phase === 'SUMMARIZE');
585
+ if (isTerminal) {
586
+ const phaseRounds = ctx.tracker.metrics?.phaseRounds ?? 0;
587
+ if (phaseRounds < 2) {
588
+ ctx.consecutiveEmptyResponses++;
589
+ this.logger.warn(
590
+ `[AgentRuntime] ⚠ empty response in SUMMARIZE — retrying (grace ${phaseRounds + 1}/2)`
591
+ );
592
+ // 不 rollbackTick: 让 tracker 计入 phaseRounds 以便到达 grace 上限退出
593
+ await new Promise(r => setTimeout(r, 1500));
594
+ return { __continue: true };
595
+ }
596
+ this.logger.warn('[AgentRuntime] ⚠ empty response in SUMMARIZE (grace exhausted) — proceeding to forced summary');
597
+ return null;
598
+ }
566
599
  if (ctx.isSystem && ctx.consecutiveEmptyResponses < 2) {
567
600
  ctx.consecutiveEmptyResponses++;
568
601
  this.logger.warn(
@@ -843,6 +876,13 @@ export class AgentRuntime {
843
876
  * @returns {Promise<Object>}
844
877
  */
845
878
  async #finalize(ctx) {
879
+ // Scan pipeline: 所有结果在 toolCalls 中 (collect_scan_recipe),不需要文本回复
880
+ // 直接跳过 forced summary,避免浪费一次 LLM 调用
881
+ if (!ctx.lastReply && ctx.tracker?.submitToolName === 'collect_scan_recipe') {
882
+ const recipeCount = ctx.toolCalls.filter(tc => (tc.tool || tc.name) === 'collect_scan_recipe').length;
883
+ ctx.lastReply = `[scan complete: ${recipeCount} recipes collected]`;
884
+ }
885
+
846
886
  // 强制摘要 (系统场景或 tracker 场景)
847
887
  if (!ctx.lastReply && (ctx.tracker || ctx.isSystem)) {
848
888
  const forcedResult = await produceForcedSummary({
@@ -239,11 +239,12 @@ export class KnowledgeProduction extends Capability {
239
239
  }
240
240
 
241
241
  get tools() {
242
+ // 与旧版 PRODUCER_TOOLS 保持一致: 仅 3 个核心工具
243
+ // guard_check_code / validate_candidate 不需要:提交时 UnifiedValidator 已自动校验
244
+ // 额外工具会分散 LLM 注意力,浪费 produce 轮次在验证而非提交上
242
245
  return [
243
246
  'submit_knowledge', 'submit_with_check',
244
247
  'read_project_file', // 获取代码片段用于知识正文
245
- 'guard_check_code', // 提交前质量检查
246
- 'validate_candidate', // 校验候选质量
247
248
  ];
248
249
  }
249
250
  }