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.
- package/lib/cli/deploy/FileManifest.js +1 -1
- package/lib/external/ai/providers/GoogleGeminiProvider.js +6 -4
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +6 -2
- package/lib/http/routes/recipes.js +1 -5
- package/lib/service/agent/AgentFactory.js +137 -92
- package/lib/service/agent/AgentRuntime.js +43 -3
- package/lib/service/agent/capabilities.js +3 -2
- package/lib/service/agent/context/ExplorationTracker.js +87 -19
- package/lib/service/agent/core/ChatAgentPrompts.js +4 -2
- package/lib/service/agent/domain/ChatAgentTasks.js +2 -6
- package/lib/service/agent/domain/insight-producer.js +4 -1
- package/lib/service/agent/domain/scan-prompts.js +368 -29
- package/lib/service/agent/memory/ActiveContext.js +29 -1
- package/lib/service/agent/presets.js +3 -1
- package/lib/service/agent/strategies.js +41 -2
- package/package.json +1 -1
|
@@ -39,6 +39,8 @@ 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
|
|
|
@@ -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 — 策略配置对象
|
|
@@ -246,6 +250,8 @@ export class ExplorationTracker {
|
|
|
246
250
|
idleRoundsToExit: 3,
|
|
247
251
|
...budget,
|
|
248
252
|
};
|
|
253
|
+
/** @type {string} 提交工具名 — bootstrap 用 submit_knowledge,scan 用 collect_scan_recipe */
|
|
254
|
+
this.#submitToolName = budget.submitToolName || 'submit_knowledge';
|
|
249
255
|
this.#phase = strategy.phases[0];
|
|
250
256
|
this.#logger = Logger.getInstance();
|
|
251
257
|
}
|
|
@@ -306,11 +312,20 @@ export class ExplorationTracker {
|
|
|
306
312
|
}
|
|
307
313
|
}
|
|
308
314
|
|
|
315
|
+
/** 提交工具名 (供外部判断是否为 scan / bootstrap pipeline) */
|
|
316
|
+
get submitToolName() {
|
|
317
|
+
return this.#submitToolName;
|
|
318
|
+
}
|
|
319
|
+
|
|
309
320
|
/**
|
|
310
321
|
* 是否应退出主循环
|
|
311
322
|
* @returns {boolean}
|
|
312
323
|
*/
|
|
313
324
|
shouldExit() {
|
|
325
|
+
// Scan pipeline: SUMMARIZE 无消费方,recipes 已通过工具调用收集,直接退出
|
|
326
|
+
if (this.#isTerminalPhase() && this.#submitToolName === 'collect_scan_recipe') {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
314
329
|
// 终结阶段 + 已给了 2 轮 grace → 退出
|
|
315
330
|
if (this.#isTerminalPhase() && this.#metrics.phaseRounds >= 2) {
|
|
316
331
|
return true;
|
|
@@ -357,7 +372,7 @@ export class ExplorationTracker {
|
|
|
357
372
|
// 1. 强制退出
|
|
358
373
|
if (this.#gracefulExitRound != null && m.iteration === this.#gracefulExitRound) {
|
|
359
374
|
const submitCount = m.submitCount;
|
|
360
|
-
// v5.1: Analyst
|
|
375
|
+
// v5.1: Analyst 策略使用纯文本输出
|
|
361
376
|
if (this.#strategy.name === 'analyst') {
|
|
362
377
|
return {
|
|
363
378
|
type: 'force_exit',
|
|
@@ -371,6 +386,17 @@ export class ExplorationTracker {
|
|
|
371
386
|
`⛔ 严禁在回复中复制或引用本条指令的任何文字,只输出你自己的分析。`,
|
|
372
387
|
};
|
|
373
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 (供维度编排消费)
|
|
374
400
|
return {
|
|
375
401
|
type: 'force_exit',
|
|
376
402
|
text: `⚠️ 你已使用 ${m.iteration}/${b.maxIterations} 轮次,**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
|
|
@@ -537,6 +563,13 @@ export class ExplorationTracker {
|
|
|
537
563
|
// 4. 如果发生了转换,生成 nudge 立即返回给主循环注入
|
|
538
564
|
if (this.#justTransitioned) {
|
|
539
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
|
+
}
|
|
540
573
|
return {
|
|
541
574
|
type: 'phase_transition',
|
|
542
575
|
text: this.#buildTransitionNudge(),
|
|
@@ -568,8 +601,16 @@ export class ExplorationTracker {
|
|
|
568
601
|
}
|
|
569
602
|
|
|
570
603
|
if (isTerminal && transitioned) {
|
|
571
|
-
// 刚转入终结阶段
|
|
604
|
+
// 刚转入终结阶段
|
|
572
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
|
+
|
|
573
614
|
// v5.1: Analyst 策略要求自然语言输出
|
|
574
615
|
const nudge = this.#strategy.name === 'analyst'
|
|
575
616
|
? `请**停止调用工具**,直接输出你的完整分析报告。用 Markdown 格式,包含具体文件路径、类名和代码模式。至少涵盖 3 个核心发现。\n\n**现在开始输出你的分析报告。**\n⚠️ 严禁在回复中复制本条指令文字,只输出你自己的分析。`
|
|
@@ -586,9 +627,10 @@ export class ExplorationTracker {
|
|
|
586
627
|
|
|
587
628
|
// 非终结阶段收到文本
|
|
588
629
|
if (this.#phase === 'PRODUCE' || this.#phase === 'EXPLORE') {
|
|
589
|
-
// PRODUCE
|
|
590
|
-
|
|
591
|
-
|
|
630
|
+
// Scan pipeline: PRODUCE 阶段不注入 continue nudge — 避免与即将到来的转换 nudge 矛盾
|
|
631
|
+
// Bootstrap pipeline: 保留提交引导
|
|
632
|
+
const nudge = (this.#phase === 'PRODUCE' && this.#submitToolName !== 'collect_scan_recipe')
|
|
633
|
+
? `你的分析很好。请继续调用 ${this.#submitToolName} 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。`
|
|
592
634
|
: null;
|
|
593
635
|
return { isFinalAnswer: false, needsDigestNudge: false, shouldContinue: true, nudge };
|
|
594
636
|
}
|
|
@@ -652,6 +694,7 @@ export class ExplorationTracker {
|
|
|
652
694
|
return {
|
|
653
695
|
iteration: this.#metrics.iteration,
|
|
654
696
|
phase: this.#phase,
|
|
697
|
+
phaseRounds: this.#metrics.phaseRounds,
|
|
655
698
|
submitCount: this.#metrics.submitCount,
|
|
656
699
|
uniqueFiles: this.#metrics.uniqueFiles.size,
|
|
657
700
|
uniquePatterns: this.#metrics.uniquePatterns.size,
|
|
@@ -661,6 +704,11 @@ export class ExplorationTracker {
|
|
|
661
704
|
};
|
|
662
705
|
}
|
|
663
706
|
|
|
707
|
+
/** 当前指标快照 (便捷 getter, 与 getMetrics() 相同) */
|
|
708
|
+
get metrics() {
|
|
709
|
+
return this.getMetrics();
|
|
710
|
+
}
|
|
711
|
+
|
|
664
712
|
/**
|
|
665
713
|
* 获取计划进度
|
|
666
714
|
* @returns {object}
|
|
@@ -872,7 +920,7 @@ export class ExplorationTracker {
|
|
|
872
920
|
const toPhase = this.#phase;
|
|
873
921
|
|
|
874
922
|
if (toPhase === 'PRODUCE') {
|
|
875
|
-
return
|
|
923
|
+
return `你已充分探索了项目代码,现在请开始调用 ${this.#submitToolName} 工具来提交你发现的知识候选。不要再搜索,直接提交。`;
|
|
876
924
|
}
|
|
877
925
|
|
|
878
926
|
if (toPhase === 'SUMMARIZE') {
|
|
@@ -889,6 +937,13 @@ export class ExplorationTracker {
|
|
|
889
937
|
`**现在开始输出你的分析报告。不要再调用任何工具。**\n` +
|
|
890
938
|
`⚠️ 以上是行为指令,严禁在回复中复制或引用这段文字,只输出你自己的分析内容。`;
|
|
891
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 (供维度编排消费)
|
|
892
947
|
return `你已完成分析探索。请在回复中直接输出 dimensionDigest JSON(用 \`\`\`json 包裹),包含以下字段:\n` +
|
|
893
948
|
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因(如:提交上限已达)","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。如果有未来得及处理的信号,请在此标记,系统会在下次运行时续传。\n` +
|
|
894
949
|
`⚠️ 严禁在回复中复制本条指令文字,只输出 JSON。`;
|
|
@@ -923,10 +978,13 @@ export class ExplorationTracker {
|
|
|
923
978
|
|
|
924
979
|
case 'PRODUCE':
|
|
925
980
|
if (m.submitCount === 0 && m.phaseRounds >= 1) {
|
|
926
|
-
return
|
|
981
|
+
return `⚠️ 探索阶段已结束。你已收集了足够的项目信息,请 **立即** 调用 ${this.#submitToolName} 提交候选。不要继续搜索,直接提交。`;
|
|
927
982
|
}
|
|
928
983
|
if (m.submitCount >= b.softSubmitLimit && b.softSubmitLimit > 0) {
|
|
929
984
|
const remaining = b.maxSubmits - m.submitCount;
|
|
985
|
+
if (this.#submitToolName === 'collect_scan_recipe') {
|
|
986
|
+
return `已提交 ${m.submitCount} 个候选(上限 ${b.maxSubmits})。${remaining > 0 ? `还可提交 ${remaining} 个。` : ''}如果还有值得记录的发现可以继续提交,否则请输出分析总结。`;
|
|
987
|
+
}
|
|
930
988
|
return `已提交 ${m.submitCount} 个候选(上限 ${b.maxSubmits})。${remaining > 0 ? `还可提交 ${remaining} 个。` : ''}如果还有值得记录的发现可以继续提交,否则请产出 dimensionDigest 总结。\n⚠️ 如果还有未处理的信号,请在 dimensionDigest 的 remainingTasks 字段中标记,下次运行时会续传。`;
|
|
931
989
|
}
|
|
932
990
|
return null;
|
|
@@ -1060,6 +1118,7 @@ export class ExplorationTracker {
|
|
|
1060
1118
|
|
|
1061
1119
|
// 第 1 轮: plan elicitation
|
|
1062
1120
|
if (m.iteration === 1) {
|
|
1121
|
+
trace?.expectPlan?.(); // 授权 ActiveContext 捕获计划
|
|
1063
1122
|
return {
|
|
1064
1123
|
type: 'planning',
|
|
1065
1124
|
text: this.#buildPlanElicitationPrompt(),
|
|
@@ -1082,6 +1141,12 @@ export class ExplorationTracker {
|
|
|
1082
1141
|
|
|
1083
1142
|
if (!periodicTrigger && !deviationTrigger) return null;
|
|
1084
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
|
+
|
|
1085
1150
|
const remaining = b.maxIterations - m.iteration;
|
|
1086
1151
|
const parts = [];
|
|
1087
1152
|
if (deviationTrigger) {
|
|
@@ -1113,6 +1178,7 @@ export class ExplorationTracker {
|
|
|
1113
1178
|
|
|
1114
1179
|
progress.lastReplanIteration = m.iteration;
|
|
1115
1180
|
this.#pendingReplan = true;
|
|
1181
|
+
trace?.expectPlan?.(); // 授权 ActiveContext 捕获 replan
|
|
1116
1182
|
|
|
1117
1183
|
this.#logger.info(
|
|
1118
1184
|
`[ExplorationTracker] 📋 replan triggered at iteration ${m.iteration} (${deviationTrigger ? 'deviation' : 'periodic'})`
|
|
@@ -1159,6 +1225,20 @@ export class ExplorationTracker {
|
|
|
1159
1225
|
const steps = trace?.getPlanStepsMutable?.() || [];
|
|
1160
1226
|
if (steps.length === 0) return;
|
|
1161
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
|
+
|
|
1162
1242
|
const actions = trace?.getCurrentRoundActions?.() || [];
|
|
1163
1243
|
if (actions.length === 0) return;
|
|
1164
1244
|
|
|
@@ -1184,18 +1264,6 @@ export class ExplorationTracker {
|
|
|
1184
1264
|
this.#planProgress.totalSteps = steps.length;
|
|
1185
1265
|
this.#planProgress.deviationScore =
|
|
1186
1266
|
steps.length > 0 ? 1 - this.#planProgress.coveredSteps / steps.length : 0;
|
|
1187
|
-
|
|
1188
|
-
// 处理 pending replan
|
|
1189
|
-
if (this.#pendingReplan) {
|
|
1190
|
-
const plan = trace?.getPlan?.();
|
|
1191
|
-
if (plan) {
|
|
1192
|
-
this.#planProgress.coveredSteps = plan.steps.filter((s) => s.status === 'done').length;
|
|
1193
|
-
this.#planProgress.totalSteps = plan.steps.length;
|
|
1194
|
-
this.#planProgress.unplannedActions = 0;
|
|
1195
|
-
this.#planProgress.consecutiveOffPlan = 0;
|
|
1196
|
-
this.#pendingReplan = false;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
1267
|
}
|
|
1200
1268
|
|
|
1201
1269
|
/**
|
|
@@ -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
|
-
//
|
|
156
|
-
|
|
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
|
}
|
|
@@ -61,16 +61,12 @@ ${highSim.map((s) => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* 任务: 批量发现 Recipe 间的知识图谱关系
|
|
64
|
-
* 委托给 AgentFactory.
|
|
64
|
+
* 委托给 AgentFactory.discoverRelations (独立 Explore → Synthesize 管线)
|
|
65
65
|
*/
|
|
66
66
|
export async function taskDiscoverAllRelations(context, { batchSize = 20 } = {}) {
|
|
67
67
|
const { container } = context;
|
|
68
68
|
const agentFactory = container.get('agentFactory');
|
|
69
|
-
return agentFactory.
|
|
70
|
-
label: 'knowledge-graph',
|
|
71
|
-
files: [],
|
|
72
|
-
task: 'relations',
|
|
73
|
-
});
|
|
69
|
+
return agentFactory.discoverRelations({ batchSize });
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
/**
|
|
@@ -249,8 +249,11 @@ export function producerRejectionGateEvaluator(source, _phaseResults, _strategyC
|
|
|
249
249
|
return { action: 'pass', reason: '' };
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
// 可配置的提交工具名 — bootstrap 用 submit_knowledge/submit_with_check,scan 用 collect_scan_recipe
|
|
253
|
+
const submitToolNames = _strategyContext.submitToolNames
|
|
254
|
+
|| ['submit_knowledge', 'submit_with_check'];
|
|
252
255
|
const submitCalls = (source.toolCalls || []).filter(tc =>
|
|
253
|
-
|
|
256
|
+
submitToolNames.includes(tc.tool || tc.name),
|
|
254
257
|
);
|
|
255
258
|
const rejected = submitCalls.filter(tc => {
|
|
256
259
|
const res = tc.result;
|