autosnippet 3.2.7 → 3.2.9
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/bin/cli.js +13 -5
- package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
- package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +26 -29
- package/lib/cli/SetupService.js +1 -1
- package/lib/core/AstAnalyzer.js +27 -5
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/middleware/requestLogger.js +1 -0
- package/lib/http/routes/ai.js +240 -35
- package/lib/http/routes/candidates.js +2 -3
- package/lib/http/routes/extract.js +13 -11
- package/lib/http/routes/modules.js +2 -2
- package/lib/http/routes/recipes.js +9 -5
- package/lib/http/routes/remote.js +149 -270
- package/lib/http/routes/violations.js +0 -54
- package/lib/http/utils/sse-sessions.js +1 -1
- package/lib/infrastructure/logging/Logger.js +5 -4
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
- package/lib/injection/ServiceContainer.js +70 -28
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/routes/spm.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +490 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1016 -0
- package/lib/service/agent/AgentState.js +217 -0
- package/lib/service/agent/IntentClassifier.js +331 -0
- package/lib/service/agent/LarkTransport.js +389 -0
- package/lib/service/agent/capabilities.js +408 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
- package/lib/service/agent/core/LoopContext.js +170 -0
- package/lib/service/agent/core/MessageAdapter.js +223 -0
- package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
- package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
- package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
- package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
- package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
- package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
- package/lib/service/agent/domain/insight-producer.js +267 -0
- package/lib/service/agent/domain/scan-prompts.js +105 -0
- package/lib/service/agent/forced-summary.js +266 -0
- package/lib/service/agent/index.js +91 -0
- package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
- package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
- package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
- package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +303 -0
- package/lib/service/agent/strategies.js +717 -0
- package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
- package/lib/service/agent/tools/ai-analysis.js +75 -0
- package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
- package/lib/service/{chat → agent}/tools/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +33 -22
- package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
- package/lib/service/agent/tools/knowledge-graph.js +112 -0
- package/lib/service/agent/tools/scan-recipe.js +189 -0
- package/lib/service/agent/tools/system-interaction.js +476 -0
- package/lib/service/automation/DirectiveDetector.js +0 -1
- package/lib/service/automation/FileWatcher.js +0 -8
- package/lib/service/automation/handlers/CreateHandler.js +7 -3
- package/lib/service/automation/handlers/DraftHandler.js +7 -6
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +49 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/FieldSpec.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/lib/shared/StyleGuide.js +1 -1
- package/package.json +4 -1
- package/resources/native-ui/screenshot.swift +228 -0
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
- package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
- package/lib/http/routes/spm.js +0 -5
- package/lib/infrastructure/external/XcodeAutomation.js +0 -15
- package/lib/service/chat/ChatAgent.js +0 -1602
- package/lib/service/chat/Memory.js +0 -161
- package/lib/service/chat/ProducerAgent.js +0 -431
- package/lib/service/chat/ReasoningTrace.js +0 -523
- package/lib/service/chat/TaskPipeline.js +0 -357
- package/lib/service/chat/WorkingMemory.js +0 -357
- package/lib/service/chat/memory/PersistentMemory.js +0 -450
- package/lib/service/chat/tools/ai-analysis.js +0 -267
- package/lib/service/chat/tools/knowledge-graph.js +0 -234
- package/lib/service/chat/tools.js +0 -18
- package/lib/service/snippet/PlaceholderConverter.js +0 -5
- package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
- /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
- /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
- /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
- /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
- /package/lib/service/{chat → agent}/tools/query.js +0 -0
|
@@ -1,1602 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ChatAgent — 项目内唯一 AI 执行中心 (ReAct + DAG Pipeline)
|
|
3
|
-
*
|
|
4
|
-
* 设计原则: 项目内所有 AI 调用都走 ChatAgent + tool 体系。
|
|
5
|
-
* bootstrapKnowledge() 等共享 handler 只做纯启发式,不直接调 AI。
|
|
6
|
-
*
|
|
7
|
-
* 三种调用模式:
|
|
8
|
-
* - Dashboard Chat: execute(prompt, history) → ReAct 循环 → 自动调用工具 → 返回最终回答
|
|
9
|
-
* - 程序化调用: executeTool(toolName, params) → 直接执行指定工具
|
|
10
|
-
* - DAG 管线: runTask(taskName, params) → TaskPipeline 编排多工具协作(支持依赖、并行、条件跳过)
|
|
11
|
-
*
|
|
12
|
-
* 冷启动只是 DAG 管线的一个实例(bootstrap_full_pipeline),
|
|
13
|
-
* 同样的机制可用于任何多步骤 AI 工作流。
|
|
14
|
-
*
|
|
15
|
-
* 与 MCP 外部 Agent 的分工:
|
|
16
|
-
* - ChatAgent: 项目内 AI(Dashboard、HTTP API),所有 AI 推理都经过 tool
|
|
17
|
-
* - MCP: 为外部 Agent(Cursor/Claude)暴露工具,外部 Agent 自带 AI 能力
|
|
18
|
-
* - 共享: handlers/bootstrap-internal.js 等底层 handler 被两者复用(纯数据处理,无 AI)
|
|
19
|
-
*
|
|
20
|
-
* ReAct 模式:
|
|
21
|
-
* Thought → Action(tool_name, params) → Observation → ... → Answer
|
|
22
|
-
* 最多 MAX_ITERATIONS 轮,防止无限循环
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import fs from 'node:fs';
|
|
26
|
-
import path from 'node:path';
|
|
27
|
-
import { fileURLToPath } from 'node:url';
|
|
28
|
-
import Logger from '../../infrastructure/logging/Logger.js';
|
|
29
|
-
import {
|
|
30
|
-
buildNativeToolSystemPrompt,
|
|
31
|
-
buildProjectBriefing,
|
|
32
|
-
cleanFinalAnswer,
|
|
33
|
-
} from './ChatAgentPrompts.js';
|
|
34
|
-
import {
|
|
35
|
-
taskCheckAndSubmit,
|
|
36
|
-
taskDiscoverAllRelations,
|
|
37
|
-
taskFullEnrich,
|
|
38
|
-
taskGuardFullScan,
|
|
39
|
-
taskQualityAudit,
|
|
40
|
-
} from './ChatAgentTasks.js';
|
|
41
|
-
import { ContextWindow, limitToolResult } from './ContextWindow.js';
|
|
42
|
-
import { ConversationStore } from './ConversationStore.js';
|
|
43
|
-
import { ExplorationTracker } from './ExplorationTracker.js';
|
|
44
|
-
import { MemoryCoordinator } from './memory/MemoryCoordinator.js';
|
|
45
|
-
import { ActiveContext } from './memory/ActiveContext.js';
|
|
46
|
-
import { TaskPipeline } from './TaskPipeline.js';
|
|
47
|
-
|
|
48
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
49
|
-
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
|
50
|
-
const SKILLS_DIR = path.resolve(PROJECT_ROOT, 'skills');
|
|
51
|
-
const SOUL_PATH = path.resolve(PROJECT_ROOT, 'SOUL.md');
|
|
52
|
-
const MAX_ITERATIONS = 6;
|
|
53
|
-
/** 系统调用 (如 bootstrap) 允许更多迭代,因为每维度需要多次 submit_knowledge */
|
|
54
|
-
const MAX_ITERATIONS_SYSTEM = 30;
|
|
55
|
-
/** 原生函数调用模式下,已提交 ≥ MIN_SUBMITS_FOR_EARLY_EXIT 个候选后,连续 N 轮无新提交则提前退出 */
|
|
56
|
-
const _MIN_SUBMITS_FOR_EARLY_EXIT = 1;
|
|
57
|
-
const IDLE_ROUNDS_TO_EXIT = 2;
|
|
58
|
-
/** 单个维度最多提交候选数量 — 超过后跳过提交返回提醒 */
|
|
59
|
-
const MAX_SUBMITS_PER_DIMENSION = 6;
|
|
60
|
-
/** 提交达到软上限后注入收尾提示的阈值 */
|
|
61
|
-
const SOFT_SUBMIT_LIMIT = 4;
|
|
62
|
-
/** 连续搜索/阅读轮次预算 — 超过后注入提交提示并切 auto */
|
|
63
|
-
const SEARCH_BUDGET = 8;
|
|
64
|
-
/** 搜索预算耗尽后,额外容忍的轮次 — 再未提交则强制退出 */
|
|
65
|
-
const SEARCH_BUDGET_GRACE = 4;
|
|
66
|
-
|
|
67
|
-
/** 默认预算配置 — 可通过 execute() 的 opts.budget 覆盖 */
|
|
68
|
-
const DEFAULT_BUDGET = Object.freeze({
|
|
69
|
-
maxIterations: MAX_ITERATIONS_SYSTEM,
|
|
70
|
-
searchBudget: SEARCH_BUDGET,
|
|
71
|
-
searchBudgetGrace: SEARCH_BUDGET_GRACE,
|
|
72
|
-
maxSubmits: MAX_SUBMITS_PER_DIMENSION,
|
|
73
|
-
softSubmitLimit: SOFT_SUBMIT_LIMIT,
|
|
74
|
-
idleRoundsToExit: IDLE_ROUNDS_TO_EXIT,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
export class ChatAgent {
|
|
78
|
-
#toolRegistry;
|
|
79
|
-
#aiProvider;
|
|
80
|
-
#container;
|
|
81
|
-
#logger;
|
|
82
|
-
/** @type {Map<string, TaskPipeline>} */
|
|
83
|
-
#pipelines = new Map();
|
|
84
|
-
/** @type {string} 缓存的项目概况(每次 execute 刷新一次) */
|
|
85
|
-
#projectBriefingCache = '';
|
|
86
|
-
/** @type {ConversationStore|null} 对话持久化 */
|
|
87
|
-
#conversations = null;
|
|
88
|
-
/** @type {string|null} 当前 execute 调用的 source — 'user' | 'system' */
|
|
89
|
-
#currentSource = null;
|
|
90
|
-
/** @type {string|null} 当前 execute 调用的 UI 语言偏好 — 'zh' | 'en' | null */
|
|
91
|
-
#currentLang = null;
|
|
92
|
-
/** @type {string|null} 默认 UI 语言偏好(通过 setLang 设置,bootstrap 等非对话场景使用) */
|
|
93
|
-
#defaultLang = null;
|
|
94
|
-
/** @type {Array|null} 内存文件缓存(bootstrap 场景注入,search_project_code/read_project_file 优先使用) */
|
|
95
|
-
#fileCache = null;
|
|
96
|
-
/** @type {Set<string>} 跨维度已提交候选标题(bootstrap 全局去重) */
|
|
97
|
-
#globalSubmittedTitles = new Set();
|
|
98
|
-
/** @type {Set<string>} 跨维度已提交代码模式指纹(bootstrap 全局去重) */
|
|
99
|
-
#globalSubmittedPatterns = new Set();
|
|
100
|
-
/** @type {{ input: number, output: number }} 当前 execute() 累计 token 用量 */
|
|
101
|
-
#currentTokenUsage = { input: 0, output: 0 };
|
|
102
|
-
/** @type {import('./ProjectSemanticMemory.js').ProjectSemanticMemory|null} Tier 3 语义记忆 */
|
|
103
|
-
#semanticMemory = null;
|
|
104
|
-
/** @type {MemoryCoordinator|null} v5.0 统一记忆协调器 */
|
|
105
|
-
#memoryCoordinator = null;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @param {object} opts
|
|
109
|
-
* @param {import('./ToolRegistry.js').ToolRegistry} opts.toolRegistry
|
|
110
|
-
* @param {import('../../external/ai/AiProvider.js').AiProvider} opts.aiProvider
|
|
111
|
-
* @param {import('../../injection/ServiceContainer.js').ServiceContainer} opts.container
|
|
112
|
-
*/
|
|
113
|
-
constructor({ toolRegistry, aiProvider, container }) {
|
|
114
|
-
this.#toolRegistry = toolRegistry;
|
|
115
|
-
this.#aiProvider = aiProvider;
|
|
116
|
-
this.#container = container;
|
|
117
|
-
this.#logger = Logger.getInstance();
|
|
118
|
-
|
|
119
|
-
/** 是否有 AI Provider(只读) */
|
|
120
|
-
this.hasAI = !!aiProvider;
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* 是否有真实(非 Mock)AI Provider
|
|
124
|
-
* MockProvider 不具备实际推理能力,bootstrap 编排时应视为 AI 不可用
|
|
125
|
-
*/
|
|
126
|
-
this.hasRealAI = !!aiProvider && aiProvider.name !== 'mock';
|
|
127
|
-
|
|
128
|
-
/** AI Provider 引用(只读)— 用于外部模块直接调用 structuredOutput / extractJSON */
|
|
129
|
-
this.aiProvider = aiProvider || null;
|
|
130
|
-
|
|
131
|
-
// 初始化对话持久化
|
|
132
|
-
try {
|
|
133
|
-
const projectRoot = container?.singletons?._projectRoot || process.cwd();
|
|
134
|
-
this.#conversations = new ConversationStore(projectRoot);
|
|
135
|
-
} catch {
|
|
136
|
-
/* ConversationStore init failed, degrade silently */
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// v4.1: 尝试初始化 ProjectSemanticMemory (Tier 3)
|
|
140
|
-
this.#initSemanticMemory(container);
|
|
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
|
-
|
|
152
|
-
// 注册内置 DAG 管线
|
|
153
|
-
this.#registerBuiltinPipelines();
|
|
154
|
-
|
|
155
|
-
// 从系统环境变量检测默认语言
|
|
156
|
-
const sysLang = (process.env.LANG || '').split('.')[0];
|
|
157
|
-
if (sysLang.startsWith('en')) {
|
|
158
|
-
this.#defaultLang = 'en';
|
|
159
|
-
} else if (sysLang.startsWith('zh')) {
|
|
160
|
-
this.#defaultLang = 'zh';
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ─── 公共 API ─────────────────────────────────────────
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* 注入内存文件缓存(bootstrap 场景: allFiles 已在内存中,避免重复磁盘读取)
|
|
168
|
-
* 调用后 search_project_code / read_project_file 优先从缓存查找
|
|
169
|
-
* @param {Array|null} files — [{ relativePath, content, name }]
|
|
170
|
-
*/
|
|
171
|
-
setFileCache(files) {
|
|
172
|
-
this.#fileCache = files;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 设置 ProjectSemanticMemory 实例 (Tier 3)
|
|
177
|
-
* @param {import('./ProjectSemanticMemory.js').ProjectSemanticMemory|null} sm
|
|
178
|
-
*/
|
|
179
|
-
setSemanticMemory(sm) {
|
|
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
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* 重置跨维度全局提交标题(新 bootstrap session 开始时调用)
|
|
198
|
-
*/
|
|
199
|
-
resetGlobalSubmittedTitles() {
|
|
200
|
-
this.#globalSubmittedTitles.clear();
|
|
201
|
-
this.#globalSubmittedPatterns.clear();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* 获取当前默认 UI 语言偏好
|
|
206
|
-
* @returns {'zh'|'en'|null}
|
|
207
|
-
*/
|
|
208
|
-
getLang() {
|
|
209
|
-
return this.#defaultLang;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* 设置默认 UI 语言偏好(影响 Agent 回复语言)
|
|
214
|
-
* 由前端通过 bootstrap/chat 等 API 设置,后续所有 AI 调用自动继承。
|
|
215
|
-
* @param {'zh'|'en'|null} lang
|
|
216
|
-
*/
|
|
217
|
-
setLang(lang) {
|
|
218
|
-
this.#defaultLang = lang || null;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* 交互式对话(Dashboard Chat 入口)
|
|
223
|
-
* 自动带 ReAct 循环: LLM 可决定调用工具或直接回答
|
|
224
|
-
*
|
|
225
|
-
* @param {string} prompt — 用户消息
|
|
226
|
-
* @param {object} opts
|
|
227
|
-
* @param {Array} opts.history — 对话历史 [{role, content}]
|
|
228
|
-
* @param {string} [opts.conversationId] — 对话 ID(启用持久化时)
|
|
229
|
-
* @param {'user'|'system'} [opts.source='user'] — 调用来源(影响 Memory 隔离)
|
|
230
|
-
* @param {object} [opts.dimensionMeta] — Bootstrap 维度元数据 { id, outputType, allowedKnowledgeTypes }
|
|
231
|
-
* @param {string} [opts.projectLanguage] — 项目主语言 (e.g. 'swift', 'objectivec'),注入到 submit tool ctx
|
|
232
|
-
* @returns {Promise<{reply: string, toolCalls: Array, hasContext: boolean, conversationId?: string}>}
|
|
233
|
-
*/
|
|
234
|
-
async execute(
|
|
235
|
-
prompt,
|
|
236
|
-
{
|
|
237
|
-
history = [],
|
|
238
|
-
conversationId,
|
|
239
|
-
source = 'user',
|
|
240
|
-
budget: budgetOverrides,
|
|
241
|
-
dimensionId,
|
|
242
|
-
dimensionMeta,
|
|
243
|
-
// v3.0: Agent 分离选项
|
|
244
|
-
systemPromptOverride, // 覆盖默认 system prompt (Analyst/Producer 各自使用)
|
|
245
|
-
allowedTools, // 覆盖默认工具白名单 (string[])
|
|
246
|
-
strategy, // v4.2: 策略名称 ('bootstrap'|'analyst'|'producer')
|
|
247
|
-
temperature: temperatureOverride, // 覆盖默认温度
|
|
248
|
-
projectLanguage, // 项目主语言,注入到 submit tool 的 ctx._projectLanguage
|
|
249
|
-
lang, // UI 语言偏好 ('zh'|'en'),控制回复语言
|
|
250
|
-
// v5.0: 统一记忆协调器
|
|
251
|
-
memoryCoordinator, // MemoryCoordinator 实例
|
|
252
|
-
// v5.1: SSE 流式进度回调
|
|
253
|
-
onProgress, // (event: {type, ...}) => void — 实时推送思考/工具/回答事件
|
|
254
|
-
} = {}
|
|
255
|
-
) {
|
|
256
|
-
this.#currentSource = source;
|
|
257
|
-
this.#currentLang = lang || this.#defaultLang || null;
|
|
258
|
-
this.#currentTokenUsage = { input: 0, output: 0 };
|
|
259
|
-
const execStartTime = Date.now();
|
|
260
|
-
const promptPreview = prompt.length > 80 ? `${prompt.substring(0, 80)}…` : prompt;
|
|
261
|
-
this.#logger.info(
|
|
262
|
-
`[ChatAgent] ▶ execute — source=${source}${dimensionMeta?.id ? `, dim=${dimensionMeta.id}(${dimensionMeta.outputType})` : dimensionId ? `, dim=${dimensionId}` : ''}, prompt="${promptPreview}", historyLen=${history.length}${conversationId ? `, convId=${conversationId.substring(0, 8)}` : ''}`
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
// 合并预算配置: 默认值 + 外部覆盖
|
|
266
|
-
const budget = budgetOverrides
|
|
267
|
-
? { ...DEFAULT_BUDGET, ...budgetOverrides }
|
|
268
|
-
: { ...DEFAULT_BUDGET };
|
|
269
|
-
|
|
270
|
-
// 对话持久化: 如果传了 conversationId,从 ConversationStore 加载历史
|
|
271
|
-
let effectiveHistory = history;
|
|
272
|
-
if (conversationId && this.#conversations) {
|
|
273
|
-
effectiveHistory = this.#conversations.load(conversationId);
|
|
274
|
-
this.#logger.info(
|
|
275
|
-
`[ChatAgent] loaded ${effectiveHistory.length} messages from conversation store`
|
|
276
|
-
);
|
|
277
|
-
this.#conversations.append(conversationId, { role: 'user', content: prompt });
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// 每次对话刷新项目概况(不是每轮 ReAct)
|
|
281
|
-
this.#projectBriefingCache = await buildProjectBriefing({ container: this.#container });
|
|
282
|
-
|
|
283
|
-
// ── 统一原生函数调用路径(v5.0: 移除文本解析路径) ──
|
|
284
|
-
// 所有 Provider 均通过 chatWithTools() 进行结构化工具调用。
|
|
285
|
-
// 不支持原生函数调用的 Provider 在基类 chatWithTools() 中降级为 chat(),
|
|
286
|
-
// 返回 { text, functionCalls: null },被 native 循环视为最终回答。
|
|
287
|
-
this.#logger.info(`[ChatAgent] ✨ using NATIVE tool calling mode (${this.#aiProvider.name})`);
|
|
288
|
-
let result;
|
|
289
|
-
|
|
290
|
-
// v5.0: 确定有效的 MemoryCoordinator
|
|
291
|
-
// 优先使用外部传入的 (bootstrap 模式),否则使用实例级的 (user chat 模式)
|
|
292
|
-
const effectiveCoordinator = memoryCoordinator || this.#memoryCoordinator || null;
|
|
293
|
-
|
|
294
|
-
result = await this.#executeWithNativeTools(prompt, {
|
|
295
|
-
effectiveHistory,
|
|
296
|
-
conversationId,
|
|
297
|
-
source,
|
|
298
|
-
execStartTime,
|
|
299
|
-
budget,
|
|
300
|
-
dimensionMeta,
|
|
301
|
-
systemPromptOverride,
|
|
302
|
-
allowedTools,
|
|
303
|
-
strategy,
|
|
304
|
-
temperatureOverride,
|
|
305
|
-
projectLanguage,
|
|
306
|
-
memoryCoordinator: effectiveCoordinator,
|
|
307
|
-
onProgress,
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// SSE: 推送最终回答(分块模拟流式)
|
|
311
|
-
if (onProgress && result.reply) {
|
|
312
|
-
const textId = `ans_${Date.now()}`;
|
|
313
|
-
onProgress({ type: 'text:start', id: textId, role: 'answer' });
|
|
314
|
-
// 分块推送:每 ~20 字符一块,模拟逐 token 打字效果
|
|
315
|
-
const CHUNK = 20;
|
|
316
|
-
const text = result.reply;
|
|
317
|
-
for (let i = 0; i < text.length; i += CHUNK) {
|
|
318
|
-
onProgress({ type: 'text:delta', id: textId, delta: text.slice(i, i + CHUNK) });
|
|
319
|
-
}
|
|
320
|
-
onProgress({ type: 'text:end', id: textId });
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// 持久化 assistant 回复
|
|
324
|
-
if (conversationId && this.#conversations) {
|
|
325
|
-
this.#conversations.append(conversationId, { role: 'assistant', content: result.reply });
|
|
326
|
-
this.#autoSummarize(conversationId).catch((err) => {
|
|
327
|
-
this.#logger.debug('[ChatAgent] autoSummarize failed', {
|
|
328
|
-
conversationId,
|
|
329
|
-
error: err.message,
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// v5.0: 通过 coordinator 提取记忆
|
|
335
|
-
if (effectiveCoordinator) {
|
|
336
|
-
effectiveCoordinator.extractFromConversation(prompt, result.reply, source);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// 附加 token 用量统计
|
|
340
|
-
result.tokenUsage = { ...this.#currentTokenUsage };
|
|
341
|
-
|
|
342
|
-
// 持久化 token 消耗到数据库(fire-and-forget)
|
|
343
|
-
try {
|
|
344
|
-
const tokenStore = this.#container?.get?.('tokenUsageStore');
|
|
345
|
-
if (tokenStore) {
|
|
346
|
-
const aiProvider = this.#aiProvider;
|
|
347
|
-
tokenStore.record({
|
|
348
|
-
source: source || 'unknown',
|
|
349
|
-
dimension: dimensionId || dimensionMeta?.id || null,
|
|
350
|
-
provider: aiProvider?.name || null,
|
|
351
|
-
model: aiProvider?.model || null,
|
|
352
|
-
inputTokens: this.#currentTokenUsage.input,
|
|
353
|
-
outputTokens: this.#currentTokenUsage.output,
|
|
354
|
-
durationMs: Date.now() - execStartTime,
|
|
355
|
-
toolCalls: result.toolCalls?.length || 0,
|
|
356
|
-
sessionId: conversationId || null,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// 通知前端 token 用量变化
|
|
360
|
-
try {
|
|
361
|
-
const realtime = this.#container?.get?.('realtimeService');
|
|
362
|
-
realtime?.broadcastTokenUsageUpdated?.();
|
|
363
|
-
} catch {
|
|
364
|
-
/* optional */
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
} catch {
|
|
368
|
-
/* token logging should never break execution */
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return { ...result, conversationId };
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// ─── Native Tool Calling ReAct 循环 ──────────────────────
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* 原生结构化函数调用 ReAct 循环
|
|
378
|
-
*
|
|
379
|
-
* v4.2 重写: 用 ExplorationTracker 统一管理生命周期
|
|
380
|
-
* - ExplorationTracker: 信号收集 + 阶段路由 + Nudge 生成 + Graceful exit
|
|
381
|
-
* - ReasoningTrace: 纯数据收集(推理链记录)
|
|
382
|
-
* - ContextWindow: 消息管理 + 三级压缩
|
|
383
|
-
*
|
|
384
|
-
* @param {string} prompt
|
|
385
|
-
* @param {object} opts
|
|
386
|
-
* @returns {Promise<{reply: string, toolCalls: Array, hasContext: boolean}>}
|
|
387
|
-
*/
|
|
388
|
-
async #executeWithNativeTools(
|
|
389
|
-
prompt,
|
|
390
|
-
{
|
|
391
|
-
effectiveHistory,
|
|
392
|
-
conversationId,
|
|
393
|
-
source,
|
|
394
|
-
execStartTime,
|
|
395
|
-
budget = DEFAULT_BUDGET,
|
|
396
|
-
dimensionMeta,
|
|
397
|
-
systemPromptOverride,
|
|
398
|
-
allowedTools,
|
|
399
|
-
strategy,
|
|
400
|
-
temperatureOverride,
|
|
401
|
-
projectLanguage,
|
|
402
|
-
memoryCoordinator,
|
|
403
|
-
onProgress,
|
|
404
|
-
}
|
|
405
|
-
) {
|
|
406
|
-
const isSystem = source === 'system';
|
|
407
|
-
const temperature = temperatureOverride ?? (isSystem ? 0.3 : 0.7);
|
|
408
|
-
|
|
409
|
-
// ── Layer 1: ContextWindow ──
|
|
410
|
-
const tokenBudget = ContextWindow.resolveTokenBudget(this.#aiProvider?.model, { isSystem });
|
|
411
|
-
const ctx = new ContextWindow(tokenBudget);
|
|
412
|
-
for (const h of effectiveHistory) {
|
|
413
|
-
if (h.role === 'assistant') {
|
|
414
|
-
ctx.appendAssistantText(h.content);
|
|
415
|
-
} else {
|
|
416
|
-
ctx.appendUserMessage(h.content);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
ctx.appendUserMessage(prompt);
|
|
420
|
-
|
|
421
|
-
// ── Pre-check: 首条 prompt 过大时预警 ──
|
|
422
|
-
const initialUsage = ctx.getTokenUsageRatio();
|
|
423
|
-
if (initialUsage > 0.7) {
|
|
424
|
-
this.#logger.warn(
|
|
425
|
-
`[ChatAgent] ⚠ initial prompt already at ${(initialUsage * 100).toFixed(0)}% of token budget (${ctx.estimateTokens()}/${ctx.tokenBudget})`
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// ── Layer 2: ExplorationTracker (替代 PhaseRouter + 内联探索追踪 + ReasoningLayer 行为控制) ──
|
|
430
|
-
const tracker = ExplorationTracker.resolve(
|
|
431
|
-
{ source, strategy, dimensionMeta, allowedTools },
|
|
432
|
-
budget
|
|
433
|
-
);
|
|
434
|
-
|
|
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
|
-
// ── 系统提示词 ──
|
|
451
|
-
let baseSystemPrompt =
|
|
452
|
-
systemPromptOverride ||
|
|
453
|
-
buildNativeToolSystemPrompt({
|
|
454
|
-
currentSource: this.#currentSource,
|
|
455
|
-
projectBriefingCache: this.#projectBriefingCache,
|
|
456
|
-
memoryCoordinator,
|
|
457
|
-
budget,
|
|
458
|
-
soulPath: SOUL_PATH,
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// ── 语言指令 ──
|
|
462
|
-
const effectiveLang = this.#currentLang;
|
|
463
|
-
if (effectiveLang === 'en') {
|
|
464
|
-
baseSystemPrompt +=
|
|
465
|
-
'\n\n## Language\nYou MUST respond in English. All output text, analysis, titles and descriptions must be in English.';
|
|
466
|
-
} else if (effectiveLang === 'zh') {
|
|
467
|
-
baseSystemPrompt +=
|
|
468
|
-
'\n\n## 语言\n你必须使用中文回复。所有输出文本、分析、标题和描述都必须是中文。';
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// 注入轮次预算
|
|
472
|
-
if (isSystem && !baseSystemPrompt.includes('轮次预算')) {
|
|
473
|
-
const exploreEnd = Math.floor(budget.maxIterations * 0.6);
|
|
474
|
-
const verifyEnd = Math.floor(budget.maxIterations * 0.8);
|
|
475
|
-
baseSystemPrompt += `\n\n## 轮次预算\n- 总轮次: **${budget.maxIterations} 轮**\n- 探索阶段: 第 1-${exploreEnd} 轮(搜索和结构化查询)\n- 验证阶段: 第 ${exploreEnd + 1}-${verifyEnd} 轮(读取关键文件确认细节)\n- 总结阶段: 第 ${verifyEnd + 1}-${budget.maxIterations} 轮(**停止工具调用,输出分析文本**)\n\n到达第 ${verifyEnd} 轮时你必须开始输出总结,不要继续搜索。`;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// ── 工具白名单 ──
|
|
479
|
-
const effectiveAllowedTools =
|
|
480
|
-
allowedTools ||
|
|
481
|
-
(isSystem
|
|
482
|
-
? [
|
|
483
|
-
'search_project_code',
|
|
484
|
-
'read_project_file',
|
|
485
|
-
'submit_knowledge',
|
|
486
|
-
'submit_with_check',
|
|
487
|
-
'list_project_structure',
|
|
488
|
-
'get_file_summary',
|
|
489
|
-
'semantic_search_code',
|
|
490
|
-
'get_project_overview',
|
|
491
|
-
'get_class_hierarchy',
|
|
492
|
-
'get_class_info',
|
|
493
|
-
'get_protocol_info',
|
|
494
|
-
'get_method_overrides',
|
|
495
|
-
'get_category_map',
|
|
496
|
-
'get_previous_analysis',
|
|
497
|
-
'note_finding',
|
|
498
|
-
'get_previous_evidence',
|
|
499
|
-
]
|
|
500
|
-
: null);
|
|
501
|
-
const toolSchemas = this.#toolRegistry.getToolSchemas(effectiveAllowedTools);
|
|
502
|
-
|
|
503
|
-
const toolCalls = [];
|
|
504
|
-
const maxIter = isSystem ? budget.maxIterations : MAX_ITERATIONS;
|
|
505
|
-
let consecutiveAiErrors = 0;
|
|
506
|
-
let consecutiveEmptyResponses = 0;
|
|
507
|
-
const submittedTitles = new Set(this.#globalSubmittedTitles);
|
|
508
|
-
const sharedState = {};
|
|
509
|
-
|
|
510
|
-
// ── 主循环 ──
|
|
511
|
-
while (true) {
|
|
512
|
-
// ── ExplorationTracker: tick + 退出检查 ──
|
|
513
|
-
if (tracker) {
|
|
514
|
-
tracker.tick();
|
|
515
|
-
if (tracker.shouldExit()) {
|
|
516
|
-
this.#logger.info(
|
|
517
|
-
`[ChatAgent] tracker exit: phase=${tracker.phase}, iter=${tracker.iteration}, submits=${tracker.totalSubmits}`
|
|
518
|
-
);
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
} else if (!isSystem && ctx.length > maxIter * 2 + 2) {
|
|
522
|
-
// User 模式: 简单消息数限制
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
const currentIter = tracker?.iteration || (ctx.length - 1);
|
|
527
|
-
const iterStartTime = Date.now();
|
|
528
|
-
|
|
529
|
-
// ── 动态 toolChoice ──
|
|
530
|
-
let currentChoice;
|
|
531
|
-
if (tracker) {
|
|
532
|
-
currentChoice = tracker.getToolChoice();
|
|
533
|
-
} else {
|
|
534
|
-
currentChoice = 'auto';
|
|
535
|
-
}
|
|
536
|
-
|
|
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
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// ── ReasoningTrace: 开始新轮次 ──
|
|
547
|
-
trace.startRound(currentIter);
|
|
548
|
-
|
|
549
|
-
// ── 压缩检查 ──
|
|
550
|
-
const compactResult = ctx.compactIfNeeded();
|
|
551
|
-
if (compactResult.level > 0) {
|
|
552
|
-
this.#logger.info(
|
|
553
|
-
`[ChatAgent] context compacted: L${compactResult.level}, removed ${compactResult.removed} items`
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// ── 构建 systemPrompt (含阶段上下文) ──
|
|
558
|
-
let systemPrompt = baseSystemPrompt;
|
|
559
|
-
if (tracker) {
|
|
560
|
-
systemPrompt += tracker.getPhaseContext();
|
|
561
|
-
} else if (isSystem) {
|
|
562
|
-
// fallback: 非 tracker 路径注入进度
|
|
563
|
-
const remaining = maxIter - currentIter;
|
|
564
|
-
systemPrompt += `\n\n## 当前进度\n第 ${currentIter}/${maxIter} 轮 | 剩余 ${remaining} 轮`;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// ── WorkingMemory 上下文注入 ──
|
|
568
|
-
// v5.0: 通过 coordinator 获取动态记忆上下文
|
|
569
|
-
if (isSystem && memoryCoordinator) {
|
|
570
|
-
const wmContext = memoryCoordinator.buildDynamicMemoryPrompt({
|
|
571
|
-
mode: strategy || 'analyst',
|
|
572
|
-
scopeId: dimensionScopeId,
|
|
573
|
-
});
|
|
574
|
-
if (wmContext) {
|
|
575
|
-
systemPrompt += `\n\n${wmContext}`;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// ── AI 调用 ──
|
|
580
|
-
let aiResult;
|
|
581
|
-
try {
|
|
582
|
-
const messages = ctx.toMessages();
|
|
583
|
-
const currentPhase = tracker?.phase || 'user';
|
|
584
|
-
this.#logger.info(
|
|
585
|
-
`[ChatAgent] 🔄 iteration ${currentIter}/${maxIter} — phase=${currentPhase}, ${messages.length} msgs, toolChoice=${currentChoice}, tokens~${ctx.estimateTokens()}`
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
onProgress?.({
|
|
589
|
-
type: 'step:start',
|
|
590
|
-
step: currentIter,
|
|
591
|
-
maxSteps: maxIter,
|
|
592
|
-
phase: currentPhase,
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
aiResult = await this.#aiProvider.chatWithTools(prompt, {
|
|
596
|
-
messages,
|
|
597
|
-
toolSchemas,
|
|
598
|
-
toolChoice: currentChoice,
|
|
599
|
-
systemPrompt,
|
|
600
|
-
temperature,
|
|
601
|
-
maxTokens: 8192,
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
const aiDuration = Date.now() - iterStartTime;
|
|
605
|
-
|
|
606
|
-
if (aiResult.usage) {
|
|
607
|
-
this.#currentTokenUsage.input += aiResult.usage.inputTokens || 0;
|
|
608
|
-
this.#currentTokenUsage.output += aiResult.usage.outputTokens || 0;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (aiResult.functionCalls?.length > 0) {
|
|
612
|
-
this.#logger.info(
|
|
613
|
-
`[ChatAgent] ✓ AI returned ${aiResult.functionCalls.length} function calls in ${aiDuration}ms: [${aiResult.functionCalls.map((fc) => fc.name).join(', ')}]`
|
|
614
|
-
);
|
|
615
|
-
} else {
|
|
616
|
-
const textPreview = (aiResult.text || '').substring(0, 120).replace(/\n/g, '↵');
|
|
617
|
-
this.#logger.info(
|
|
618
|
-
`[ChatAgent] ✓ AI returned text in ${aiDuration}ms (${(aiResult.text || '').length} chars) — "${textPreview}…"`
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
consecutiveAiErrors = 0;
|
|
622
|
-
|
|
623
|
-
// ── ReasoningTrace: 提取 Thought + Plan ──
|
|
624
|
-
if (aiResult.text) {
|
|
625
|
-
trace.setThought(aiResult.text);
|
|
626
|
-
trace.extractAndSetPlan(aiResult.text, currentIter);
|
|
627
|
-
}
|
|
628
|
-
} catch (aiErr) {
|
|
629
|
-
consecutiveAiErrors++;
|
|
630
|
-
this.#logger.warn(
|
|
631
|
-
`[ChatAgent] AI call failed (attempt ${consecutiveAiErrors}): ${aiErr.message}`
|
|
632
|
-
);
|
|
633
|
-
|
|
634
|
-
// 回退 tick(AI 失败不计入迭代)
|
|
635
|
-
tracker?.rollbackTick();
|
|
636
|
-
|
|
637
|
-
if (aiErr.code === 'CIRCUIT_OPEN') {
|
|
638
|
-
if (isSystem) {
|
|
639
|
-
this.#logger.warn(`[ChatAgent] 🛑 circuit breaker is OPEN — skipping to summary`);
|
|
640
|
-
break;
|
|
641
|
-
}
|
|
642
|
-
trace.endRound();
|
|
643
|
-
return {
|
|
644
|
-
reply: `抱歉,AI 服务暂时不可用(${aiErr.message})。请稍后重试,或检查 API 配置。`,
|
|
645
|
-
toolCalls,
|
|
646
|
-
hasContext: toolCalls.length > 0,
|
|
647
|
-
reasoningTrace: trace,
|
|
648
|
-
reasoningQuality: tracker?.getQualityMetrics(trace) || null,
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
if (consecutiveAiErrors >= 2) {
|
|
653
|
-
if (isSystem) {
|
|
654
|
-
this.#logger.warn(
|
|
655
|
-
`[ChatAgent] 🛑 2 consecutive AI errors — resetting context, breaking to summary`
|
|
656
|
-
);
|
|
657
|
-
ctx.resetToPromptOnly();
|
|
658
|
-
break;
|
|
659
|
-
}
|
|
660
|
-
trace.endRound();
|
|
661
|
-
onProgress?.({ type: 'step:end', step: currentIter });
|
|
662
|
-
return {
|
|
663
|
-
reply: `抱歉,AI 服务暂时不可用(${aiErr.message})。请稍后重试,或检查 API 配置。`,
|
|
664
|
-
toolCalls,
|
|
665
|
-
hasContext: toolCalls.length > 0,
|
|
666
|
-
reasoningTrace: trace,
|
|
667
|
-
reasoningQuality: tracker?.getQualityMetrics(trace) || null,
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// ── 处理 functionCalls ──
|
|
675
|
-
if (aiResult.functionCalls && aiResult.functionCalls.length > 0) {
|
|
676
|
-
// Graceful exit 保护: 忽略 toolChoice='none' 下的工具调用 (Gemini 偶发)
|
|
677
|
-
if (tracker?.isGracefulExit) {
|
|
678
|
-
this.#logger.warn(
|
|
679
|
-
`[ChatAgent] ⚠ AI returned ${aiResult.functionCalls.length} tool calls despite toolChoice=none (graceful exit) — ignoring tools, treating as text`
|
|
680
|
-
);
|
|
681
|
-
if (aiResult.text) {
|
|
682
|
-
const reply = cleanFinalAnswer(aiResult.text);
|
|
683
|
-
const totalDuration = Date.now() - execStartTime;
|
|
684
|
-
trace.endRound();
|
|
685
|
-
this.#logger.info(
|
|
686
|
-
`[ChatAgent] ✅ final answer (graceful exit, forced) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
687
|
-
);
|
|
688
|
-
return {
|
|
689
|
-
reply,
|
|
690
|
-
toolCalls,
|
|
691
|
-
hasContext: toolCalls.length > 0,
|
|
692
|
-
reasoningTrace: trace,
|
|
693
|
-
reasoningQuality: tracker?.getQualityMetrics(trace) || null,
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// 限制单次工具调用数量
|
|
700
|
-
const MAX_TOOL_CALLS_PER_ITER = 8;
|
|
701
|
-
let activeCalls = aiResult.functionCalls;
|
|
702
|
-
if (activeCalls.length > MAX_TOOL_CALLS_PER_ITER) {
|
|
703
|
-
this.#logger.warn(
|
|
704
|
-
`[ChatAgent] ⚠ ${activeCalls.length} tool calls, capping to ${MAX_TOOL_CALLS_PER_ITER}`
|
|
705
|
-
);
|
|
706
|
-
tracker?.recordTruncatedCalls(activeCalls.length - MAX_TOOL_CALLS_PER_ITER);
|
|
707
|
-
activeCalls = activeCalls.slice(0, MAX_TOOL_CALLS_PER_ITER);
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
ctx.appendAssistantWithToolCalls(aiResult.text || null, activeCalls);
|
|
711
|
-
|
|
712
|
-
let roundSubmitCount = 0;
|
|
713
|
-
let roundHasNewInfo = false;
|
|
714
|
-
const roundToolNames = [];
|
|
715
|
-
|
|
716
|
-
for (const fc of activeCalls) {
|
|
717
|
-
const toolStartTime = Date.now();
|
|
718
|
-
this.#logger.info(
|
|
719
|
-
`[ChatAgent] 🔧 ${fc.name}(${JSON.stringify(fc.args).substring(0, 100)})`
|
|
720
|
-
);
|
|
721
|
-
|
|
722
|
-
onProgress?.({
|
|
723
|
-
type: 'tool:start',
|
|
724
|
-
id: `tc_${fc.name}_${Date.now()}`,
|
|
725
|
-
tool: fc.name,
|
|
726
|
-
args: fc.args,
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
let toolResult;
|
|
730
|
-
let cacheHit = false;
|
|
731
|
-
|
|
732
|
-
// v5.0: 缓存检查通过 coordinator
|
|
733
|
-
if (memoryCoordinator) {
|
|
734
|
-
const cached = memoryCoordinator.getCachedResult(fc.name, fc.args);
|
|
735
|
-
if (cached !== null) {
|
|
736
|
-
toolResult = cached;
|
|
737
|
-
cacheHit = true;
|
|
738
|
-
this.#logger.info(`[ChatAgent] 🔧 CACHE HIT: ${fc.name} → skipped execution`);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
if (!cacheHit) {
|
|
743
|
-
try {
|
|
744
|
-
toolResult = await this.#toolRegistry.execute(
|
|
745
|
-
fc.name,
|
|
746
|
-
fc.args,
|
|
747
|
-
this.#getToolContext({
|
|
748
|
-
_sessionToolCalls: toolCalls,
|
|
749
|
-
_dimensionMeta: dimensionMeta,
|
|
750
|
-
_submittedTitles: submittedTitles,
|
|
751
|
-
_submittedPatterns: this.#globalSubmittedPatterns,
|
|
752
|
-
_sharedState: sharedState,
|
|
753
|
-
_projectLanguage: projectLanguage,
|
|
754
|
-
_memoryCoordinator: memoryCoordinator || null,
|
|
755
|
-
_dimensionScopeId: dimensionScopeId,
|
|
756
|
-
_currentRound: currentIter,
|
|
757
|
-
})
|
|
758
|
-
);
|
|
759
|
-
const toolDuration = Date.now() - toolStartTime;
|
|
760
|
-
const resultSize =
|
|
761
|
-
typeof toolResult === 'string'
|
|
762
|
-
? toolResult.length
|
|
763
|
-
: JSON.stringify(toolResult).length;
|
|
764
|
-
this.#logger.info(
|
|
765
|
-
`[ChatAgent] 🔧 done: ${fc.name} → ${resultSize} chars in ${toolDuration}ms`
|
|
766
|
-
);
|
|
767
|
-
|
|
768
|
-
onProgress?.({
|
|
769
|
-
type: 'tool:end',
|
|
770
|
-
tool: fc.name,
|
|
771
|
-
status: 'ok',
|
|
772
|
-
resultSize,
|
|
773
|
-
duration: toolDuration,
|
|
774
|
-
});
|
|
775
|
-
} catch (toolErr) {
|
|
776
|
-
this.#logger.warn(`[ChatAgent] 🔧 FAILED: ${fc.name} — ${toolErr.message}`);
|
|
777
|
-
toolResult = { error: `tool "${fc.name}" failed: ${toolErr.message}` };
|
|
778
|
-
|
|
779
|
-
onProgress?.({
|
|
780
|
-
type: 'tool:end',
|
|
781
|
-
tool: fc.name,
|
|
782
|
-
status: 'error',
|
|
783
|
-
error: toolErr.message,
|
|
784
|
-
duration: Date.now() - toolStartTime,
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// v5.0: 统一记录观察 (缓存写入)
|
|
790
|
-
if (memoryCoordinator) {
|
|
791
|
-
memoryCoordinator.recordObservation(fc.name, fc.args, toolResult, currentIter, cacheHit);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
// 记录到全局 toolCalls
|
|
795
|
-
const summarized = this.#summarizeResult(toolResult);
|
|
796
|
-
toolCalls.push({ tool: fc.name, params: fc.args, result: summarized });
|
|
797
|
-
|
|
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;
|
|
804
|
-
}
|
|
805
|
-
|
|
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: 动态配额压缩 ──
|
|
812
|
-
const quota = ctx.getToolResultQuota();
|
|
813
|
-
let resultStr = limitToolResult(fc.name, toolResult, quota);
|
|
814
|
-
|
|
815
|
-
// ── 重复提交 / 维度范围校验 ──
|
|
816
|
-
if (fc.name === 'submit_knowledge' || fc.name === 'submit_with_check') {
|
|
817
|
-
const title = fc.args?.title || fc.args?.category || '';
|
|
818
|
-
const isRejected = typeof toolResult === 'object' && toolResult?.status === 'rejected';
|
|
819
|
-
const isError =
|
|
820
|
-
typeof toolResult === 'object' &&
|
|
821
|
-
(toolResult?.error || toolResult?.status === 'error');
|
|
822
|
-
|
|
823
|
-
if (isRejected) {
|
|
824
|
-
this.#logger.info(`[ChatAgent] 🚫 off-topic rejected: "${title}"`);
|
|
825
|
-
} else if (isError) {
|
|
826
|
-
this.#logger.info(
|
|
827
|
-
`[ChatAgent] ⚠ submit error: "${title}" — ${toolResult.error || 'unknown'}`
|
|
828
|
-
);
|
|
829
|
-
} else if (submittedTitles.has(title.toLowerCase().trim())) {
|
|
830
|
-
resultStr = `⚠ 重复提交: "${title}" 已存在。`;
|
|
831
|
-
this.#logger.info(`[ChatAgent] 🔁 duplicate: "${title}"`);
|
|
832
|
-
} else {
|
|
833
|
-
submittedTitles.add(title.toLowerCase().trim());
|
|
834
|
-
this.#globalSubmittedTitles.add(title.toLowerCase().trim());
|
|
835
|
-
const pattern = fc.args?.content?.pattern || '';
|
|
836
|
-
if (pattern.length >= 30) {
|
|
837
|
-
const fp = pattern
|
|
838
|
-
.replace(/\/\/[^\n]*/g, '')
|
|
839
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
840
|
-
.replace(/[\s]+/g, '')
|
|
841
|
-
.toLowerCase()
|
|
842
|
-
.slice(0, 200);
|
|
843
|
-
if (fp.length >= 20) {
|
|
844
|
-
this.#globalSubmittedPatterns.add(fp);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
roundSubmitCount++;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
ctx.appendToolResult(fc.id, fc.name, resultStr);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// ── ExplorationTracker: endRound → 检查阶段转换 ──
|
|
855
|
-
if (tracker) {
|
|
856
|
-
tracker.updatePlanProgress(trace);
|
|
857
|
-
const transitionNudge = tracker.endRound({
|
|
858
|
-
hasNewInfo: roundHasNewInfo,
|
|
859
|
-
submitCount: roundSubmitCount,
|
|
860
|
-
toolNames: roundToolNames,
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
if (transitionNudge) {
|
|
864
|
-
ctx.appendUserNudge(transitionNudge.text);
|
|
865
|
-
this.#logger.info(
|
|
866
|
-
`[ChatAgent] 📝 injected ${transitionNudge.type} nudge (${tracker.phase})`
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
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
|
-
|
|
881
|
-
onProgress?.({ type: 'step:end', step: currentIter });
|
|
882
|
-
continue;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// ── 文字回答 ──
|
|
886
|
-
onProgress?.({ type: 'step:end', step: currentIter });
|
|
887
|
-
|
|
888
|
-
// 空响应重试 (Gemini 偶发)
|
|
889
|
-
if (!aiResult.text && isSystem && consecutiveEmptyResponses < 2) {
|
|
890
|
-
consecutiveEmptyResponses++;
|
|
891
|
-
this.#logger.warn(
|
|
892
|
-
`[ChatAgent] ⚠ empty response from system source — retrying (${consecutiveEmptyResponses}/2)`
|
|
893
|
-
);
|
|
894
|
-
tracker?.rollbackTick();
|
|
895
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
896
|
-
continue;
|
|
897
|
-
}
|
|
898
|
-
if (aiResult.text) {
|
|
899
|
-
consecutiveEmptyResponses = 0;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// ── ExplorationTracker: 处理文本响应 ──
|
|
903
|
-
if (tracker) {
|
|
904
|
-
const textResult = tracker.onTextResponse();
|
|
905
|
-
|
|
906
|
-
if (textResult.isFinalAnswer) {
|
|
907
|
-
// 已在终结阶段且非刚转入 → 最终回答
|
|
908
|
-
const reply = cleanFinalAnswer(aiResult.text || '');
|
|
909
|
-
const totalDuration = Date.now() - execStartTime;
|
|
910
|
-
trace.endRound();
|
|
911
|
-
this.#logger.info(
|
|
912
|
-
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${tracker.iteration} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
913
|
-
);
|
|
914
|
-
return {
|
|
915
|
-
reply,
|
|
916
|
-
toolCalls,
|
|
917
|
-
hasContext: toolCalls.length > 0,
|
|
918
|
-
reasoningTrace: trace,
|
|
919
|
-
reasoningQuality: tracker.getQualityMetrics(trace),
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
|
|
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;
|
|
930
|
-
}
|
|
931
|
-
|
|
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
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// User 模式 / 非 tracker: 文字回答即最终回答
|
|
944
|
-
const reply = cleanFinalAnswer(aiResult.text || '');
|
|
945
|
-
const totalDuration = Date.now() - execStartTime;
|
|
946
|
-
trace.endRound();
|
|
947
|
-
this.#logger.info(
|
|
948
|
-
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
949
|
-
);
|
|
950
|
-
return {
|
|
951
|
-
reply,
|
|
952
|
-
toolCalls,
|
|
953
|
-
hasContext: toolCalls.length > 0,
|
|
954
|
-
reasoningTrace: trace,
|
|
955
|
-
reasoningQuality: tracker?.getQualityMetrics(trace) || null,
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// ── 循环退出: 产出 dimensionDigest 总结 ──
|
|
960
|
-
trace.endRound();
|
|
961
|
-
const forcedResult = await this.#produceForcedSummary({
|
|
962
|
-
source,
|
|
963
|
-
toolCalls,
|
|
964
|
-
toolSchemas,
|
|
965
|
-
ctx,
|
|
966
|
-
tracker,
|
|
967
|
-
execStartTime,
|
|
968
|
-
prompt,
|
|
969
|
-
});
|
|
970
|
-
forcedResult.reasoningTrace = trace;
|
|
971
|
-
forcedResult.reasoningQuality = tracker?.getQualityMetrics(trace) || null;
|
|
972
|
-
return forcedResult;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
/**
|
|
976
|
-
* 强制退出后的摘要生成 — 独立方法,避免主循环代码膨胀
|
|
977
|
-
* @private
|
|
978
|
-
*/
|
|
979
|
-
async #produceForcedSummary({
|
|
980
|
-
source,
|
|
981
|
-
toolCalls,
|
|
982
|
-
toolSchemas,
|
|
983
|
-
ctx,
|
|
984
|
-
tracker,
|
|
985
|
-
execStartTime,
|
|
986
|
-
prompt,
|
|
987
|
-
}) {
|
|
988
|
-
const iterations = tracker?.iteration || 0;
|
|
989
|
-
const isSystem = source === 'system';
|
|
990
|
-
this.#logger.info(
|
|
991
|
-
`[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls, source=${source})`
|
|
992
|
-
);
|
|
993
|
-
|
|
994
|
-
const candidateCount = toolCalls.filter(
|
|
995
|
-
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
996
|
-
).length;
|
|
997
|
-
|
|
998
|
-
let finalReply;
|
|
999
|
-
|
|
1000
|
-
// 如果熔断器已打开,跳过 AI 调用直接合成摘要(避免无用的失败 + 计数累积)
|
|
1001
|
-
const isCircuitOpen = this.#aiProvider._circuitState === 'OPEN';
|
|
1002
|
-
if (isCircuitOpen) {
|
|
1003
|
-
this.#logger.warn(
|
|
1004
|
-
`[ChatAgent] circuit breaker is OPEN — skipping AI summary, using synthetic ${isSystem ? 'digest' : 'summary'}`
|
|
1005
|
-
);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// ── 收集工具调用摘要(user / system 共用) ──
|
|
1009
|
-
const submitSummary = toolCalls
|
|
1010
|
-
.filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
1011
|
-
.map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
|
|
1012
|
-
.join('\n');
|
|
1013
|
-
|
|
1014
|
-
// ── 收集工具调用上下文(user 源需要更丰富的上下文来生成自然语言总结) ──
|
|
1015
|
-
const toolContextSummary = isSystem ? '' : this.#buildToolContextForUserSummary(toolCalls);
|
|
1016
|
-
|
|
1017
|
-
try {
|
|
1018
|
-
if (isCircuitOpen) {
|
|
1019
|
-
throw new Error('circuit open — skip to synthetic summary');
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
let summaryPrompt;
|
|
1023
|
-
let systemPrompt;
|
|
1024
|
-
|
|
1025
|
-
if (isSystem) {
|
|
1026
|
-
// ── system 源: 输出 dimensionDigest JSON(供 Bootstrap 管线消费) ──
|
|
1027
|
-
summaryPrompt = `你已完成 ${iterations} 轮工具调用(共 ${toolCalls.length} 次),提交了 ${candidateCount} 个候选。
|
|
1028
|
-
${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
1029
|
-
**必须**输出 dimensionDigest JSON(用 \`\`\`json 包裹):
|
|
1030
|
-
\`\`\`json
|
|
1031
|
-
{
|
|
1032
|
-
"dimensionDigest": {
|
|
1033
|
-
"summary": "本维度分析总结",
|
|
1034
|
-
"candidateCount": ${candidateCount},
|
|
1035
|
-
"keyFindings": ["发现1", "发现2"],
|
|
1036
|
-
"crossRefs": {},
|
|
1037
|
-
"gaps": ["未覆盖方面"],
|
|
1038
|
-
"remainingTasks": [
|
|
1039
|
-
{ "signal": "未处理信号名", "reason": "达到提交上限/时间限制", "priority": "high", "searchHints": ["搜索词"] }
|
|
1040
|
-
]
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
\`\`\`
|
|
1044
|
-
> remainingTasks: 列出本次未来得及处理的信号/主题。已全部覆盖则留空 \`[]\`。`;
|
|
1045
|
-
systemPrompt = '直接输出 dimensionDigest JSON 总结,不要调用工具。';
|
|
1046
|
-
} else {
|
|
1047
|
-
// ── user 源: 输出人类可读的 Markdown 结构化总结(前端 AI Chat 展示) ──
|
|
1048
|
-
const userQuestion = prompt ? `用户的原始问题:「${prompt.slice(0, 500)}」\n\n` : '';
|
|
1049
|
-
summaryPrompt = `${userQuestion}你刚才通过 ${toolCalls.length} 次工具调用分析了项目代码。以下是你调用过的工具和获取到的关键信息:
|
|
1050
|
-
|
|
1051
|
-
${toolContextSummary}
|
|
1052
|
-
|
|
1053
|
-
请基于以上收集到的信息,用**清晰易读的 Markdown** 格式撰写分析总结,直接回答用户的问题。
|
|
1054
|
-
|
|
1055
|
-
要求:
|
|
1056
|
-
- 使用二级/三级标题组织内容
|
|
1057
|
-
- 要有具体的代码文件路径、类名、模式名称等细节
|
|
1058
|
-
- 关键发现用列表项罗列
|
|
1059
|
-
- 如果发现了架构模式或最佳实践,用简短代码块举例
|
|
1060
|
-
- 语言自然流畅,像一份技术分析报告`;
|
|
1061
|
-
systemPrompt =
|
|
1062
|
-
'你是项目分析助手。请用纯 Markdown 格式输出结构清晰的分析总结,只输出人类可读的自然语言文档,不要输出 JSON 格式的数据。';
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// 用空 messages 避免累积上下文导致 400
|
|
1066
|
-
const summaryResult = await this.#aiProvider.chatWithTools(summaryPrompt, {
|
|
1067
|
-
messages: [],
|
|
1068
|
-
toolSchemas,
|
|
1069
|
-
toolChoice: 'none',
|
|
1070
|
-
systemPrompt,
|
|
1071
|
-
temperature: isSystem ? 0.3 : 0.5,
|
|
1072
|
-
maxTokens: 8192,
|
|
1073
|
-
});
|
|
1074
|
-
// 累计 token 用量
|
|
1075
|
-
if (summaryResult.usage) {
|
|
1076
|
-
this.#currentTokenUsage.input += summaryResult.usage.inputTokens || 0;
|
|
1077
|
-
this.#currentTokenUsage.output += summaryResult.usage.outputTokens || 0;
|
|
1078
|
-
}
|
|
1079
|
-
finalReply = cleanFinalAnswer(summaryResult.text || '');
|
|
1080
|
-
} catch (err) {
|
|
1081
|
-
this.#logger.warn(`[ChatAgent] forced summary AI call failed: ${err.message}`);
|
|
1082
|
-
|
|
1083
|
-
if (isSystem) {
|
|
1084
|
-
// ── system 源兜底: 合成 dimensionDigest JSON ──
|
|
1085
|
-
const titles = toolCalls
|
|
1086
|
-
.filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
1087
|
-
.map((tc) => tc.params?.title || 'untitled');
|
|
1088
|
-
finalReply = `\`\`\`json
|
|
1089
|
-
{
|
|
1090
|
-
"dimensionDigest": {
|
|
1091
|
-
"summary": "通过 ${toolCalls.length} 次工具调用分析了项目代码,提交了 ${candidateCount} 个候选。",
|
|
1092
|
-
"candidateCount": ${candidateCount},
|
|
1093
|
-
"keyFindings": ${JSON.stringify(titles.slice(0, 5))},
|
|
1094
|
-
"crossRefs": {},
|
|
1095
|
-
"gaps": ["AI 服务异常,部分分析未完成"]
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
\`\`\``;
|
|
1099
|
-
} else {
|
|
1100
|
-
// ── user 源兜底: 合成人类可读的 Markdown 摘要 ──
|
|
1101
|
-
const toolNames = [...new Set(toolCalls.map((tc) => tc.tool))];
|
|
1102
|
-
const filesRead = toolCalls
|
|
1103
|
-
.filter((tc) => tc.tool === 'read_project_file')
|
|
1104
|
-
.flatMap((tc) => {
|
|
1105
|
-
if (tc.params?.filePaths) {
|
|
1106
|
-
return tc.params.filePaths;
|
|
1107
|
-
}
|
|
1108
|
-
if (tc.params?.filePath) {
|
|
1109
|
-
return [tc.params.filePath];
|
|
1110
|
-
}
|
|
1111
|
-
return [];
|
|
1112
|
-
})
|
|
1113
|
-
.slice(0, 10);
|
|
1114
|
-
const searches = toolCalls
|
|
1115
|
-
.filter((tc) => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code')
|
|
1116
|
-
.map((tc) => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
|
|
1117
|
-
.filter(Boolean)
|
|
1118
|
-
.slice(0, 5);
|
|
1119
|
-
|
|
1120
|
-
finalReply = `## 分析总结\n\n通过 **${toolCalls.length} 次工具调用**探索了项目代码。\n\n`;
|
|
1121
|
-
if (searches.length > 0) {
|
|
1122
|
-
finalReply += `### 搜索的关键词\n${searches.map((s) => `- \`${s}\``).join('\n')}\n\n`;
|
|
1123
|
-
}
|
|
1124
|
-
if (filesRead.length > 0) {
|
|
1125
|
-
finalReply += `### 读取的文件\n${filesRead.map((f) => `- \`${f}\``).join('\n')}\n\n`;
|
|
1126
|
-
}
|
|
1127
|
-
finalReply += `### 使用的工具\n${toolNames.map((t) => `- ${t}`).join('\n')}\n\n`;
|
|
1128
|
-
finalReply += `> ⚠️ AI 服务异常,未能生成完整分析。请稍后重试或缩小分析范围。`;
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
const totalDuration = Date.now() - execStartTime;
|
|
1133
|
-
this.#logger.info(
|
|
1134
|
-
`[ChatAgent] ✅ forced summary — ${finalReply.length} chars, ${totalDuration}ms total`
|
|
1135
|
-
);
|
|
1136
|
-
return { reply: finalReply, toolCalls, hasContext: toolCalls.length > 0 };
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
/**
|
|
1140
|
-
* 从工具调用记录中提取上下文摘要(供 user 源强制总结使用)
|
|
1141
|
-
* @private
|
|
1142
|
-
*/
|
|
1143
|
-
#buildToolContextForUserSummary(toolCalls) {
|
|
1144
|
-
const sections = [];
|
|
1145
|
-
|
|
1146
|
-
// 目录结构探索
|
|
1147
|
-
const structureCalls = toolCalls.filter((tc) => tc.tool === 'list_project_structure');
|
|
1148
|
-
if (structureCalls.length > 0) {
|
|
1149
|
-
const dirs = structureCalls.map((tc) => tc.params?.directory || '/').slice(0, 5);
|
|
1150
|
-
sections.push(`**目录探索**: ${dirs.map((d) => `\`${d}\``).join(', ')}`);
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
// 项目概况
|
|
1154
|
-
const overviewCalls = toolCalls.filter((tc) => tc.tool === 'get_project_overview');
|
|
1155
|
-
if (overviewCalls.length > 0) {
|
|
1156
|
-
sections.push('**项目概况**: 已获取');
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// 代码搜索
|
|
1160
|
-
const searchCalls = toolCalls.filter(
|
|
1161
|
-
(tc) => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code'
|
|
1162
|
-
);
|
|
1163
|
-
if (searchCalls.length > 0) {
|
|
1164
|
-
const queries = searchCalls
|
|
1165
|
-
.map((tc) => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
|
|
1166
|
-
.filter(Boolean)
|
|
1167
|
-
.slice(0, 8);
|
|
1168
|
-
sections.push(
|
|
1169
|
-
`**代码搜索** (${searchCalls.length} 次): ${queries.map((q) => `\`${q}\``).join(', ')}`
|
|
1170
|
-
);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
// 文件读取
|
|
1174
|
-
const readCalls = toolCalls.filter((tc) => tc.tool === 'read_project_file');
|
|
1175
|
-
if (readCalls.length > 0) {
|
|
1176
|
-
const files = readCalls
|
|
1177
|
-
.flatMap((tc) => {
|
|
1178
|
-
if (tc.params?.filePaths) {
|
|
1179
|
-
return tc.params.filePaths;
|
|
1180
|
-
}
|
|
1181
|
-
if (tc.params?.filePath) {
|
|
1182
|
-
return [tc.params.filePath];
|
|
1183
|
-
}
|
|
1184
|
-
return [];
|
|
1185
|
-
})
|
|
1186
|
-
.slice(0, 10);
|
|
1187
|
-
sections.push(
|
|
1188
|
-
`**文件读取** (${readCalls.length} 次): ${files.map((f) => `\`${f}\``).join(', ')}`
|
|
1189
|
-
);
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
// AST 分析
|
|
1193
|
-
const astCalls = toolCalls.filter((tc) =>
|
|
1194
|
-
[
|
|
1195
|
-
'get_class_hierarchy',
|
|
1196
|
-
'get_class_info',
|
|
1197
|
-
'get_protocol_info',
|
|
1198
|
-
'get_method_overrides',
|
|
1199
|
-
'get_category_map',
|
|
1200
|
-
].includes(tc.tool)
|
|
1201
|
-
);
|
|
1202
|
-
if (astCalls.length > 0) {
|
|
1203
|
-
const entities = astCalls
|
|
1204
|
-
.map(
|
|
1205
|
-
(tc) =>
|
|
1206
|
-
tc.params?.className ||
|
|
1207
|
-
tc.params?.name ||
|
|
1208
|
-
tc.params?.protocolName ||
|
|
1209
|
-
tc.params?.rootClass
|
|
1210
|
-
)
|
|
1211
|
-
.filter(Boolean)
|
|
1212
|
-
.slice(0, 5);
|
|
1213
|
-
sections.push(
|
|
1214
|
-
`**AST 结构分析** (${astCalls.length} 次): ${entities.map((e) => `\`${e}\``).join(', ')}`
|
|
1215
|
-
);
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
// 知识库搜索
|
|
1219
|
-
const kbCalls = toolCalls.filter((tc) =>
|
|
1220
|
-
['search_knowledge', 'search_recipes', 'knowledge_overview'].includes(tc.tool)
|
|
1221
|
-
);
|
|
1222
|
-
if (kbCalls.length > 0) {
|
|
1223
|
-
sections.push(`**知识库查询**: ${kbCalls.length} 次`);
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
return sections.length > 0 ? sections.join('\n') : '(工具调用记录为空)';
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
// ─── Text Parsing 已移除 (v5.0) ────────────────────────
|
|
1230
|
-
// 所有 Provider 统一走 chatWithTools() 原生函数调用路径。
|
|
1231
|
-
// 不支持 native tool calling 的 Provider 在基类 chatWithTools()
|
|
1232
|
-
// 中降级为 chat(),返回 { text, functionCalls: null }。
|
|
1233
|
-
|
|
1234
|
-
/**
|
|
1235
|
-
* 程序化直接调用指定工具(跳过 ReAct 循环)
|
|
1236
|
-
* 用于: 候选提交时自动查重、定时任务等
|
|
1237
|
-
*
|
|
1238
|
-
* @param {string} toolName
|
|
1239
|
-
* @param {object} params
|
|
1240
|
-
* @returns {Promise<any>}
|
|
1241
|
-
*/
|
|
1242
|
-
async executeTool(toolName, params = {}) {
|
|
1243
|
-
return this.#toolRegistry.execute(toolName, params, this.#getToolContext());
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// ─── 对话管理 API ──────────────────────────────────────
|
|
1247
|
-
|
|
1248
|
-
/**
|
|
1249
|
-
* 创建新对话(用于 Dashboard 前端)
|
|
1250
|
-
* @param {object} [opts]
|
|
1251
|
-
* @param {'user'|'system'} [opts.category='user']
|
|
1252
|
-
* @param {string} [opts.title]
|
|
1253
|
-
* @returns {string} conversationId
|
|
1254
|
-
*/
|
|
1255
|
-
createConversation({ category = 'user', title = '' } = {}) {
|
|
1256
|
-
if (!this.#conversations) {
|
|
1257
|
-
return null;
|
|
1258
|
-
}
|
|
1259
|
-
return this.#conversations.create({ category, title });
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
/**
|
|
1263
|
-
* 获取对话列表
|
|
1264
|
-
* @param {object} [opts]
|
|
1265
|
-
* @param {'user'|'system'} [opts.category]
|
|
1266
|
-
* @param {number} [opts.limit=20]
|
|
1267
|
-
* @returns {Array}
|
|
1268
|
-
*/
|
|
1269
|
-
getConversations({ category, limit = 20 } = {}) {
|
|
1270
|
-
if (!this.#conversations) {
|
|
1271
|
-
return [];
|
|
1272
|
-
}
|
|
1273
|
-
return this.#conversations.list({ category, limit });
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
/**
|
|
1277
|
-
* 获取 ConversationStore 实例(供外部使用,如 HTTP 路由)
|
|
1278
|
-
* @returns {ConversationStore|null}
|
|
1279
|
-
*/
|
|
1280
|
-
getConversationStore() {
|
|
1281
|
-
return this.#conversations;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
/**
|
|
1285
|
-
* 预定义任务流
|
|
1286
|
-
* 将常见多步骤操作封装为一个任务名。
|
|
1287
|
-
* 优先查找 DAG 管线(TaskPipeline),其次使用硬编码任务方法。
|
|
1288
|
-
*/
|
|
1289
|
-
async runTask(taskName, params = {}) {
|
|
1290
|
-
// DAG 管线优先
|
|
1291
|
-
if (this.#pipelines.has(taskName)) {
|
|
1292
|
-
return this.runPipeline(taskName, params);
|
|
1293
|
-
}
|
|
1294
|
-
// 构建任务上下文(提供给外部任务函数)
|
|
1295
|
-
const taskContext = {
|
|
1296
|
-
executeTool: (name, p) => this.executeTool(name, p),
|
|
1297
|
-
aiProvider: this.#aiProvider,
|
|
1298
|
-
container: this.#container,
|
|
1299
|
-
logger: this.#logger,
|
|
1300
|
-
};
|
|
1301
|
-
// 降级到硬编码任务(复杂交互逻辑无法用 DAG 表达的场景)
|
|
1302
|
-
switch (taskName) {
|
|
1303
|
-
case 'check_and_submit':
|
|
1304
|
-
return taskCheckAndSubmit(taskContext, params);
|
|
1305
|
-
case 'discover_all_relations':
|
|
1306
|
-
return taskDiscoverAllRelations(taskContext, params);
|
|
1307
|
-
case 'full_enrich':
|
|
1308
|
-
return taskFullEnrich(taskContext, params);
|
|
1309
|
-
case 'quality_audit':
|
|
1310
|
-
return taskQualityAudit(taskContext, params);
|
|
1311
|
-
case 'guard_full_scan':
|
|
1312
|
-
return taskGuardFullScan(taskContext, params);
|
|
1313
|
-
default:
|
|
1314
|
-
throw new Error(`Unknown task: ${taskName}`);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
/**
|
|
1319
|
-
* 注册自定义 DAG 管线
|
|
1320
|
-
*
|
|
1321
|
-
* @param {TaskPipeline} pipeline — TaskPipeline 实例
|
|
1322
|
-
*/
|
|
1323
|
-
registerPipeline(pipeline) {
|
|
1324
|
-
if (!(pipeline instanceof TaskPipeline)) {
|
|
1325
|
-
throw new Error('Expected TaskPipeline instance');
|
|
1326
|
-
}
|
|
1327
|
-
this.#pipelines.set(pipeline.id, pipeline);
|
|
1328
|
-
this.#logger.info(`Pipeline registered: ${pipeline.id} (${pipeline.size} steps)`);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
/**
|
|
1332
|
-
* 执行 DAG 管线
|
|
1333
|
-
*
|
|
1334
|
-
* @param {string} pipelineId — 管线 ID
|
|
1335
|
-
* @param {object} [inputs={}] — 管线初始输入
|
|
1336
|
-
* @returns {Promise<import('./TaskPipeline.js').PipelineResult>}
|
|
1337
|
-
*/
|
|
1338
|
-
async runPipeline(pipelineId, inputs = {}) {
|
|
1339
|
-
const pipeline = this.#pipelines.get(pipelineId);
|
|
1340
|
-
if (!pipeline) {
|
|
1341
|
-
throw new Error(`Pipeline '${pipelineId}' not found`);
|
|
1342
|
-
}
|
|
1343
|
-
const executor = (toolName, params) => this.executeTool(toolName, params);
|
|
1344
|
-
return pipeline.execute(executor, inputs);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
/**
|
|
1348
|
-
* 获取已注册的管线列表
|
|
1349
|
-
*/
|
|
1350
|
-
getPipelines() {
|
|
1351
|
-
return [...this.#pipelines.values()].map((p) => p.describe());
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
/**
|
|
1355
|
-
* 获取 Agent 能力清单(供 MCP / API 描述)
|
|
1356
|
-
*/
|
|
1357
|
-
getCapabilities() {
|
|
1358
|
-
return {
|
|
1359
|
-
tools: this.#toolRegistry.getToolSchemas(),
|
|
1360
|
-
tasks: [
|
|
1361
|
-
{ name: 'check_and_submit', description: '提交候选前自动查重 + 质量预评' },
|
|
1362
|
-
{ name: 'discover_all_relations', description: '批量发现 Recipe 之间的知识图谱关系' },
|
|
1363
|
-
{ name: 'full_enrich', description: '批量 AI 语义补全候选字段' },
|
|
1364
|
-
{ name: 'quality_audit', description: '批量质量审计全部 Recipe,标记低分项' },
|
|
1365
|
-
{ name: 'guard_full_scan', description: '用全部 Guard 规则扫描指定代码,生成完整报告' },
|
|
1366
|
-
{
|
|
1367
|
-
name: 'bootstrap_full_pipeline',
|
|
1368
|
-
description:
|
|
1369
|
-
'冷启动全流程 DAG: bootstrap(纯启发式) → enrich(AI结构补齐) + loadSkill(并行) → refine(AI内容润色)',
|
|
1370
|
-
},
|
|
1371
|
-
],
|
|
1372
|
-
pipelines: this.getPipelines(),
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
// ─── 内置 DAG 管线注册 ─────────────────────────────────
|
|
1377
|
-
|
|
1378
|
-
/**
|
|
1379
|
-
* 注册内置 DAG 管线
|
|
1380
|
-
*
|
|
1381
|
-
* v6 变更:
|
|
1382
|
-
* - 移除旧的 4 步 DAG (bootstrap → enrich → loadSkill → refine)
|
|
1383
|
-
* - 冷启动 AI 增强现在通过 orchestrator.js 中的 ChatAgent per-dimension production 完成
|
|
1384
|
-
* - 保留简化版 bootstrap_full_pipeline: 只做 Phase 1-4 启发式
|
|
1385
|
-
* (Phase 5 ChatAgent 生产由 orchestrator.js 管理,不再走 DAG 编排)
|
|
1386
|
-
*/
|
|
1387
|
-
#registerBuiltinPipelines() {
|
|
1388
|
-
// ── bootstrap_full_pipeline (v6 简化版) ──────────────────
|
|
1389
|
-
// 只做启发式 Phase 1-5.5 (含 ChatAgent per-dimension production)
|
|
1390
|
-
// 不再需要 enrich/refine 后置步骤
|
|
1391
|
-
this.registerPipeline(
|
|
1392
|
-
new TaskPipeline('bootstrap_full_pipeline', [
|
|
1393
|
-
{
|
|
1394
|
-
name: 'bootstrap',
|
|
1395
|
-
tool: 'bootstrap_knowledge',
|
|
1396
|
-
params: {
|
|
1397
|
-
maxFiles: (ctx) => ctx._inputs.maxFiles || 500,
|
|
1398
|
-
skipGuard: (ctx) => ctx._inputs.skipGuard || false,
|
|
1399
|
-
contentMaxLines: (ctx) => ctx._inputs.contentMaxLines || 120,
|
|
1400
|
-
loadSkills: true,
|
|
1401
|
-
},
|
|
1402
|
-
},
|
|
1403
|
-
])
|
|
1404
|
-
);
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// ─── Native Tool Calling 内部方法 ──────────────────────
|
|
1408
|
-
|
|
1409
|
-
/**
|
|
1410
|
-
* 获取工具执行上下文
|
|
1411
|
-
* @param {object} [extras] — 额外注入到上下文的字段(如 _sessionToolCalls)
|
|
1412
|
-
*/
|
|
1413
|
-
#getToolContext(extras) {
|
|
1414
|
-
return {
|
|
1415
|
-
container: this.#container,
|
|
1416
|
-
aiProvider: this.#aiProvider,
|
|
1417
|
-
projectRoot: this.#container?.singletons?._projectRoot || process.cwd(),
|
|
1418
|
-
logger: this.#logger,
|
|
1419
|
-
source: this.#currentSource,
|
|
1420
|
-
fileCache: this.#fileCache || null,
|
|
1421
|
-
lang: this.#currentLang || this.#defaultLang || 'en',
|
|
1422
|
-
...extras,
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
/**
|
|
1427
|
-
* 列出可用的 Skills 及其摘要(用于系统提示词)
|
|
1428
|
-
* 加载顺序: 内置 skills/ → 项目级 AutoSnippet/skills/(同名覆盖)
|
|
1429
|
-
* @returns {{ name: string, summary: string }[]}
|
|
1430
|
-
*/
|
|
1431
|
-
#listAvailableSkills() {
|
|
1432
|
-
const skillMap = new Map();
|
|
1433
|
-
|
|
1434
|
-
// 1. 内置 Skills
|
|
1435
|
-
this.#loadSkillsFromDir(SKILLS_DIR, skillMap);
|
|
1436
|
-
|
|
1437
|
-
// 2. 项目级 Skills(覆盖同名内置 Skill)
|
|
1438
|
-
const projectSkillsDir = path.resolve(PROJECT_ROOT, '.autosnippet', 'skills');
|
|
1439
|
-
this.#loadSkillsFromDir(projectSkillsDir, skillMap);
|
|
1440
|
-
|
|
1441
|
-
return Array.from(skillMap.values());
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
/**
|
|
1445
|
-
* 从目录加载 Skills 到 Map
|
|
1446
|
-
*/
|
|
1447
|
-
#loadSkillsFromDir(dir, skillMap) {
|
|
1448
|
-
try {
|
|
1449
|
-
const dirs = fs
|
|
1450
|
-
.readdirSync(dir, { withFileTypes: true })
|
|
1451
|
-
.filter((d) => d.isDirectory())
|
|
1452
|
-
.map((d) => d.name);
|
|
1453
|
-
for (const name of dirs) {
|
|
1454
|
-
const skillPath = path.join(dir, name, 'SKILL.md');
|
|
1455
|
-
let summary = '';
|
|
1456
|
-
try {
|
|
1457
|
-
const raw = fs.readFileSync(skillPath, 'utf-8');
|
|
1458
|
-
const fmMatch = raw.match(/^---[\s\S]*?description:\s*["']?(.+?)["']?\s*$/m);
|
|
1459
|
-
if (fmMatch) {
|
|
1460
|
-
summary = fmMatch[1];
|
|
1461
|
-
} else {
|
|
1462
|
-
const lines = raw.split('\n');
|
|
1463
|
-
for (const line of lines) {
|
|
1464
|
-
const trimmed = line.trim();
|
|
1465
|
-
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('---')) {
|
|
1466
|
-
summary = trimmed.length > 80 ? `${trimmed.substring(0, 80)}...` : trimmed;
|
|
1467
|
-
break;
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
} catch {
|
|
1472
|
-
/* SKILL.md not found */
|
|
1473
|
-
}
|
|
1474
|
-
skillMap.set(name, { name, summary });
|
|
1475
|
-
}
|
|
1476
|
-
} catch {
|
|
1477
|
-
/* directory not found */
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
/**
|
|
1482
|
-
* 异步初始化 ProjectSemanticMemory (Tier 3)
|
|
1483
|
-
* 在构造函数中调用 (fire-and-forget)
|
|
1484
|
-
*/
|
|
1485
|
-
#initSemanticMemory(container) {
|
|
1486
|
-
try {
|
|
1487
|
-
const db = container?.get?.('database');
|
|
1488
|
-
if (db) {
|
|
1489
|
-
import('./ProjectSemanticMemory.js')
|
|
1490
|
-
.then(({ ProjectSemanticMemory }) => {
|
|
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
|
-
}
|
|
1504
|
-
})
|
|
1505
|
-
.catch(() => {
|
|
1506
|
-
/* Semantic Memory not available */
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
} catch {
|
|
1510
|
-
/* container.get failed, degrade silently */
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
/**
|
|
1515
|
-
* 自动压缩过长的对话(异步后台执行)
|
|
1516
|
-
* 当对话消息数超过 12 条时触发 AI 摘要压缩
|
|
1517
|
-
*/
|
|
1518
|
-
async #autoSummarize(conversationId) {
|
|
1519
|
-
if (!this.#conversations || !this.#aiProvider) {
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
|
-
try {
|
|
1523
|
-
const messages = this.#conversations.load(conversationId, { tokenBudget: Infinity });
|
|
1524
|
-
if (messages.length >= 12) {
|
|
1525
|
-
await this.#conversations.summarize(conversationId, {
|
|
1526
|
-
aiProvider: this.#aiProvider,
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
} catch {
|
|
1530
|
-
// 摘要失败不影响主流程
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
/**
|
|
1535
|
-
* 事件驱动入口(P2 预留接口)
|
|
1536
|
-
* @param {{ type: string, payload: object, source?: string }} event
|
|
1537
|
-
*/
|
|
1538
|
-
async executeEvent(event) {
|
|
1539
|
-
const { type, payload } = event;
|
|
1540
|
-
const prompt = this.#eventToPrompt(type, payload);
|
|
1541
|
-
return this.execute(prompt, { history: [], source: 'system' });
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
#eventToPrompt(type, payload) {
|
|
1545
|
-
switch (type) {
|
|
1546
|
-
case 'file_saved':
|
|
1547
|
-
return `文件 ${payload.filePath} 刚被保存,变更了 ${payload.changedLines} 行。请分析是否有值得提取为 Recipe 的代码模式。如果有,说明原因;没有就说"无需操作"。`;
|
|
1548
|
-
case 'candidate_backlog':
|
|
1549
|
-
return `当前有 ${payload.count} 条候选积压(最早 ${payload.oldest})。请按质量分类:哪些值得审核、哪些可以直接拒绝、哪些需要补充信息。`;
|
|
1550
|
-
case 'scheduled_health':
|
|
1551
|
-
return `请执行知识库健康检查:Recipe 覆盖率、过时标记、Guard 规则有效性。给出简要报告。`;
|
|
1552
|
-
default:
|
|
1553
|
-
return `事件: ${type}\n${JSON.stringify(payload)}`;
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
/**
|
|
1558
|
-
* 截断长文本
|
|
1559
|
-
*/
|
|
1560
|
-
#truncate(text, maxLen = 4000) {
|
|
1561
|
-
if (!text || text.length <= maxLen) {
|
|
1562
|
-
return text;
|
|
1563
|
-
}
|
|
1564
|
-
return `${text.substring(0, maxLen)}\n...(truncated, ${text.length - maxLen} chars omitted)`;
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
/**
|
|
1568
|
-
* 精简工具结果(避免过长的 observation)
|
|
1569
|
-
*/
|
|
1570
|
-
#summarizeResult(result) {
|
|
1571
|
-
if (!result) {
|
|
1572
|
-
return null;
|
|
1573
|
-
}
|
|
1574
|
-
const str = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1575
|
-
if (str.length <= 500) {
|
|
1576
|
-
return result;
|
|
1577
|
-
}
|
|
1578
|
-
// 返回截断版
|
|
1579
|
-
if (typeof result === 'object') {
|
|
1580
|
-
if (Array.isArray(result)) {
|
|
1581
|
-
return { _summary: `Array with ${result.length} items`, first3: result.slice(0, 3) };
|
|
1582
|
-
}
|
|
1583
|
-
// 保留 key 结构
|
|
1584
|
-
const keys = Object.keys(result);
|
|
1585
|
-
const summary = {};
|
|
1586
|
-
for (const k of keys) {
|
|
1587
|
-
const v = result[k];
|
|
1588
|
-
if (typeof v === 'string' && v.length > 200) {
|
|
1589
|
-
summary[k] = `${v.substring(0, 200)}...`;
|
|
1590
|
-
} else if (Array.isArray(v)) {
|
|
1591
|
-
summary[k] = { _count: v.length, first2: v.slice(0, 2) };
|
|
1592
|
-
} else {
|
|
1593
|
-
summary[k] = v;
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
return summary;
|
|
1597
|
-
}
|
|
1598
|
-
return str.substring(0, 500);
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
export default ChatAgent;
|