autosnippet 3.0.0 → 3.0.2
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/README.md +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +759 -243
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- package/template.json +0 -39
|
@@ -26,12 +26,11 @@ import fs from 'node:fs';
|
|
|
26
26
|
import path from 'node:path';
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
28
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
29
|
-
import {
|
|
30
|
-
import { Memory } from './Memory.js';
|
|
29
|
+
import { ContextWindow, limitToolResult, PhaseRouter } from './ContextWindow.js';
|
|
31
30
|
import { ConversationStore } from './ConversationStore.js';
|
|
32
|
-
import {
|
|
31
|
+
import { Memory } from './Memory.js';
|
|
33
32
|
import { ReasoningLayer } from './ReasoningLayer.js';
|
|
34
|
-
import {
|
|
33
|
+
import { TaskPipeline } from './TaskPipeline.js';
|
|
35
34
|
|
|
36
35
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
37
36
|
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
|
@@ -41,7 +40,7 @@ const MAX_ITERATIONS = 6;
|
|
|
41
40
|
/** 系统调用 (如 bootstrap) 允许更多迭代,因为每维度需要多次 submit_knowledge */
|
|
42
41
|
const MAX_ITERATIONS_SYSTEM = 30;
|
|
43
42
|
/** 原生函数调用模式下,已提交 ≥ MIN_SUBMITS_FOR_EARLY_EXIT 个候选后,连续 N 轮无新提交则提前退出 */
|
|
44
|
-
const
|
|
43
|
+
const _MIN_SUBMITS_FOR_EARLY_EXIT = 1;
|
|
45
44
|
const IDLE_ROUNDS_TO_EXIT = 2;
|
|
46
45
|
/** 单个维度最多提交候选数量 — 超过后跳过提交返回提醒 */
|
|
47
46
|
const MAX_SUBMITS_PER_DIMENSION = 6;
|
|
@@ -77,10 +76,16 @@ export class ChatAgent {
|
|
|
77
76
|
#conversations = null;
|
|
78
77
|
/** @type {string|null} 当前 execute 调用的 source — 'user' | 'system' */
|
|
79
78
|
#currentSource = null;
|
|
79
|
+
/** @type {string|null} 当前 execute 调用的 UI 语言偏好 — 'zh' | 'en' | null */
|
|
80
|
+
#currentLang = null;
|
|
81
|
+
/** @type {string|null} 默认 UI 语言偏好(通过 setLang 设置,bootstrap 等非对话场景使用) */
|
|
82
|
+
#defaultLang = null;
|
|
80
83
|
/** @type {Array|null} 内存文件缓存(bootstrap 场景注入,search_project_code/read_project_file 优先使用) */
|
|
81
84
|
#fileCache = null;
|
|
82
85
|
/** @type {Set<string>} 跨维度已提交候选标题(bootstrap 全局去重) */
|
|
83
86
|
#globalSubmittedTitles = new Set();
|
|
87
|
+
/** @type {Set<string>} 跨维度已提交代码模式指纹(bootstrap 全局去重) */
|
|
88
|
+
#globalSubmittedPatterns = new Set();
|
|
84
89
|
/** @type {{ input: number, output: number }} 当前 execute() 累计 token 用量 */
|
|
85
90
|
#currentTokenUsage = { input: 0, output: 0 };
|
|
86
91
|
/** @type {import('./ProjectSemanticMemory.js').ProjectSemanticMemory|null} Tier 3 语义记忆 */
|
|
@@ -115,13 +120,23 @@ export class ChatAgent {
|
|
|
115
120
|
const projectRoot = container?.singletons?._projectRoot || process.cwd();
|
|
116
121
|
this.#memory = new Memory(projectRoot);
|
|
117
122
|
this.#conversations = new ConversationStore(projectRoot);
|
|
118
|
-
} catch {
|
|
123
|
+
} catch {
|
|
124
|
+
/* Memory/ConversationStore init failed, degrade silently */
|
|
125
|
+
}
|
|
119
126
|
|
|
120
127
|
// v4.1: 尝试初始化 ProjectSemanticMemory (Tier 3)
|
|
121
128
|
this.#initSemanticMemory(container);
|
|
122
129
|
|
|
123
130
|
// 注册内置 DAG 管线
|
|
124
131
|
this.#registerBuiltinPipelines();
|
|
132
|
+
|
|
133
|
+
// 从系统环境变量检测默认语言
|
|
134
|
+
const sysLang = (process.env.LANG || '').split('.')[0];
|
|
135
|
+
if (sysLang.startsWith('en')) {
|
|
136
|
+
this.#defaultLang = 'en';
|
|
137
|
+
} else if (sysLang.startsWith('zh')) {
|
|
138
|
+
this.#defaultLang = 'zh';
|
|
139
|
+
}
|
|
125
140
|
}
|
|
126
141
|
|
|
127
142
|
// ─── 公共 API ─────────────────────────────────────────
|
|
@@ -148,6 +163,24 @@ export class ChatAgent {
|
|
|
148
163
|
*/
|
|
149
164
|
resetGlobalSubmittedTitles() {
|
|
150
165
|
this.#globalSubmittedTitles.clear();
|
|
166
|
+
this.#globalSubmittedPatterns.clear();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 获取当前默认 UI 语言偏好
|
|
171
|
+
* @returns {'zh'|'en'|null}
|
|
172
|
+
*/
|
|
173
|
+
getLang() {
|
|
174
|
+
return this.#defaultLang;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 设置默认 UI 语言偏好(影响 Agent 回复语言)
|
|
179
|
+
* 由前端通过 bootstrap/chat 等 API 设置,后续所有 AI 调用自动继承。
|
|
180
|
+
* @param {'zh'|'en'|null} lang
|
|
181
|
+
*/
|
|
182
|
+
setLang(lang) {
|
|
183
|
+
this.#defaultLang = lang || null;
|
|
151
184
|
}
|
|
152
185
|
|
|
153
186
|
/**
|
|
@@ -163,25 +196,38 @@ export class ChatAgent {
|
|
|
163
196
|
* @param {string} [opts.projectLanguage] — 项目主语言 (e.g. 'swift', 'objectivec'),注入到 submit tool ctx
|
|
164
197
|
* @returns {Promise<{reply: string, toolCalls: Array, hasContext: boolean, conversationId?: string}>}
|
|
165
198
|
*/
|
|
166
|
-
async execute(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
199
|
+
async execute(
|
|
200
|
+
prompt,
|
|
201
|
+
{
|
|
202
|
+
history = [],
|
|
203
|
+
conversationId,
|
|
204
|
+
source = 'user',
|
|
205
|
+
budget: budgetOverrides,
|
|
206
|
+
dimensionId,
|
|
207
|
+
dimensionMeta,
|
|
208
|
+
// v3.0: Agent 分离选项
|
|
209
|
+
systemPromptOverride, // 覆盖默认 system prompt (Analyst/Producer 各自使用)
|
|
210
|
+
allowedTools, // 覆盖默认工具白名单 (string[])
|
|
211
|
+
disablePhaseRouter = false, // 禁用 PhaseRouter (Analyst 不需要阶段控制)
|
|
212
|
+
temperature: temperatureOverride, // 覆盖默认温度
|
|
213
|
+
projectLanguage, // 项目主语言,注入到 submit tool 的 ctx._projectLanguage
|
|
214
|
+
lang, // UI 语言偏好 ('zh'|'en'),控制回复语言
|
|
215
|
+
// v4.0: Agent Memory 集成
|
|
216
|
+
workingMemory, // WorkingMemory 实例 (由 orchestrator 注入)
|
|
217
|
+
episodicMemory, // EpisodicMemory 实例 (跨维度情景记忆)
|
|
218
|
+
toolResultCache, // ToolResultCache 实例 (跨维度工具结果缓存)
|
|
219
|
+
// v5.1: SSE 流式进度回调
|
|
220
|
+
onProgress, // (event: {type, ...}) => void — 实时推送思考/工具/回答事件
|
|
221
|
+
} = {}
|
|
222
|
+
) {
|
|
180
223
|
this.#currentSource = source;
|
|
224
|
+
this.#currentLang = lang || this.#defaultLang || null;
|
|
181
225
|
this.#currentTokenUsage = { input: 0, output: 0 };
|
|
182
226
|
const execStartTime = Date.now();
|
|
183
|
-
const promptPreview = prompt.length > 80 ? prompt.substring(0, 80)
|
|
184
|
-
this.#logger.info(
|
|
227
|
+
const promptPreview = prompt.length > 80 ? `${prompt.substring(0, 80)}…` : prompt;
|
|
228
|
+
this.#logger.info(
|
|
229
|
+
`[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)}` : ''}`
|
|
230
|
+
);
|
|
185
231
|
|
|
186
232
|
// 合并预算配置: 默认值 + 外部覆盖
|
|
187
233
|
const budget = budgetOverrides
|
|
@@ -192,7 +238,9 @@ export class ChatAgent {
|
|
|
192
238
|
let effectiveHistory = history;
|
|
193
239
|
if (conversationId && this.#conversations) {
|
|
194
240
|
effectiveHistory = this.#conversations.load(conversationId);
|
|
195
|
-
this.#logger.info(
|
|
241
|
+
this.#logger.info(
|
|
242
|
+
`[ChatAgent] loaded ${effectiveHistory.length} messages from conversation store`
|
|
243
|
+
);
|
|
196
244
|
this.#conversations.append(conversationId, { role: 'user', content: prompt });
|
|
197
245
|
}
|
|
198
246
|
|
|
@@ -206,10 +254,20 @@ export class ChatAgent {
|
|
|
206
254
|
this.#logger.info(`[ChatAgent] ✨ using NATIVE tool calling mode (${this.#aiProvider.name})`);
|
|
207
255
|
let result;
|
|
208
256
|
result = await this.#executeWithNativeTools(prompt, {
|
|
209
|
-
effectiveHistory,
|
|
210
|
-
|
|
257
|
+
effectiveHistory,
|
|
258
|
+
conversationId,
|
|
259
|
+
source,
|
|
260
|
+
execStartTime,
|
|
261
|
+
budget,
|
|
262
|
+
dimensionMeta,
|
|
263
|
+
systemPromptOverride,
|
|
264
|
+
allowedTools,
|
|
265
|
+
disablePhaseRouter,
|
|
266
|
+
temperatureOverride,
|
|
211
267
|
projectLanguage,
|
|
212
|
-
workingMemory,
|
|
268
|
+
workingMemory,
|
|
269
|
+
episodicMemory,
|
|
270
|
+
toolResultCache,
|
|
213
271
|
onProgress,
|
|
214
272
|
});
|
|
215
273
|
|
|
@@ -229,8 +287,11 @@ export class ChatAgent {
|
|
|
229
287
|
// 持久化 assistant 回复
|
|
230
288
|
if (conversationId && this.#conversations) {
|
|
231
289
|
this.#conversations.append(conversationId, { role: 'assistant', content: result.reply });
|
|
232
|
-
this.#autoSummarize(conversationId).catch(err => {
|
|
233
|
-
this.#logger.debug('[ChatAgent] autoSummarize failed', {
|
|
290
|
+
this.#autoSummarize(conversationId).catch((err) => {
|
|
291
|
+
this.#logger.debug('[ChatAgent] autoSummarize failed', {
|
|
292
|
+
conversationId,
|
|
293
|
+
error: err.message,
|
|
294
|
+
});
|
|
234
295
|
});
|
|
235
296
|
}
|
|
236
297
|
|
|
@@ -260,9 +321,13 @@ export class ChatAgent {
|
|
|
260
321
|
try {
|
|
261
322
|
const realtime = this.#container?.get?.('realtimeService');
|
|
262
323
|
realtime?.broadcastTokenUsageUpdated?.();
|
|
263
|
-
} catch {
|
|
324
|
+
} catch {
|
|
325
|
+
/* optional */
|
|
326
|
+
}
|
|
264
327
|
}
|
|
265
|
-
} catch {
|
|
328
|
+
} catch {
|
|
329
|
+
/* token logging should never break execution */
|
|
330
|
+
}
|
|
266
331
|
|
|
267
332
|
return { ...result, conversationId };
|
|
268
333
|
}
|
|
@@ -281,15 +346,29 @@ export class ChatAgent {
|
|
|
281
346
|
* @param {object} opts
|
|
282
347
|
* @returns {Promise<{reply: string, toolCalls: Array, hasContext: boolean}>}
|
|
283
348
|
*/
|
|
284
|
-
async #executeWithNativeTools(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
349
|
+
async #executeWithNativeTools(
|
|
350
|
+
prompt,
|
|
351
|
+
{
|
|
352
|
+
effectiveHistory,
|
|
353
|
+
conversationId,
|
|
354
|
+
source,
|
|
355
|
+
execStartTime,
|
|
356
|
+
budget = DEFAULT_BUDGET,
|
|
357
|
+
dimensionMeta,
|
|
358
|
+
// v3.0: Agent 分离新增选项
|
|
359
|
+
systemPromptOverride,
|
|
360
|
+
allowedTools,
|
|
361
|
+
disablePhaseRouter = false,
|
|
362
|
+
temperatureOverride,
|
|
363
|
+
projectLanguage,
|
|
364
|
+
// v4.0: Agent Memory 集成
|
|
365
|
+
workingMemory,
|
|
366
|
+
episodicMemory,
|
|
367
|
+
toolResultCache,
|
|
368
|
+
// v5.1: SSE 流式进度回调
|
|
369
|
+
onProgress,
|
|
370
|
+
}
|
|
371
|
+
) {
|
|
293
372
|
const isSystem = source === 'system';
|
|
294
373
|
const isSkillOnly = dimensionMeta?.outputType === 'skill';
|
|
295
374
|
const temperature = temperatureOverride ?? (isSystem ? 0.3 : 0.7);
|
|
@@ -297,9 +376,7 @@ export class ChatAgent {
|
|
|
297
376
|
// ── Layer 1: ContextWindow ──
|
|
298
377
|
// messages[0] = prompt(不可压缩),历史消息在前面
|
|
299
378
|
// token 预算按模型动态适配:大窗口模型(Gemini/Claude)给更多预算,小窗口(Ollama)限制预算
|
|
300
|
-
const tokenBudget = ContextWindow.resolveTokenBudget(
|
|
301
|
-
this.#aiProvider?.model, { isSystem },
|
|
302
|
-
);
|
|
379
|
+
const tokenBudget = ContextWindow.resolveTokenBudget(this.#aiProvider?.model, { isSystem });
|
|
303
380
|
const ctx = new ContextWindow(tokenBudget);
|
|
304
381
|
for (const h of effectiveHistory) {
|
|
305
382
|
if (h.role === 'assistant') {
|
|
@@ -314,19 +391,33 @@ export class ChatAgent {
|
|
|
314
391
|
// ── P5: Pre-check — 首条 prompt 过大时预警 ──
|
|
315
392
|
const initialUsage = ctx.getTokenUsageRatio();
|
|
316
393
|
if (initialUsage > 0.7) {
|
|
317
|
-
this.#logger.warn(
|
|
394
|
+
this.#logger.warn(
|
|
395
|
+
`[ChatAgent] ⚠ initial prompt already at ${(initialUsage * 100).toFixed(0)}% of token budget (${ctx.estimateTokens()}/${ctx.tokenBudget})`
|
|
396
|
+
);
|
|
318
397
|
if (initialUsage > 0.9 && isSystem) {
|
|
319
398
|
// 仅 1 条消息时 compactIfNeeded 无法压缩(需 >4 条),
|
|
320
399
|
// 依赖 P0/P1 信号限制来控制 prompt 大小
|
|
321
|
-
this.#logger.warn(
|
|
400
|
+
this.#logger.warn(
|
|
401
|
+
`[ChatAgent] ⚠ prompt exceeds 90% budget — P0/P1 signal limiting should have prevented this. Check PROMPT_LIMITS config.`
|
|
402
|
+
);
|
|
322
403
|
}
|
|
323
404
|
}
|
|
324
405
|
|
|
325
406
|
// ── Layer 2: PhaseRouter (仅 system 源且未禁用时使用) ──
|
|
326
|
-
const phaseRouter =
|
|
407
|
+
const phaseRouter =
|
|
408
|
+
isSystem && !disablePhaseRouter ? new PhaseRouter(budget, isSkillOnly) : null;
|
|
327
409
|
|
|
328
410
|
// ── 系统提示词 (支持外部覆盖) ──
|
|
329
411
|
let baseSystemPrompt = systemPromptOverride || this.#buildNativeToolSystemPrompt(budget);
|
|
412
|
+
|
|
413
|
+
// ── 统一注入语言指令(对所有来源生效: user 对话 / system bootstrap / analyst / producer)──
|
|
414
|
+
const effectiveLang = this.#currentLang;
|
|
415
|
+
if (effectiveLang === 'en') {
|
|
416
|
+
baseSystemPrompt += '\n\n## Language\nYou MUST respond in English. All output text, analysis, titles and descriptions must be in English.';
|
|
417
|
+
} else if (effectiveLang === 'zh') {
|
|
418
|
+
baseSystemPrompt += '\n\n## 语言\n你必须使用中文回复。所有输出文本、分析、标题和描述都必须是中文。';
|
|
419
|
+
}
|
|
420
|
+
|
|
330
421
|
// 统一注入轮次预算,确保 AI 始终知道具体数字和节奏
|
|
331
422
|
if (isSystem && !baseSystemPrompt.includes('轮次预算')) {
|
|
332
423
|
const exploreEnd = Math.floor(budget.maxIterations * 0.6);
|
|
@@ -335,47 +426,60 @@ export class ChatAgent {
|
|
|
335
426
|
}
|
|
336
427
|
|
|
337
428
|
// Bootstrap 场景限制可用工具集 (支持外部覆盖)
|
|
338
|
-
const effectiveAllowedTools =
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
429
|
+
const effectiveAllowedTools =
|
|
430
|
+
allowedTools ||
|
|
431
|
+
(isSystem
|
|
432
|
+
? [
|
|
433
|
+
'search_project_code',
|
|
434
|
+
'read_project_file',
|
|
435
|
+
'submit_knowledge',
|
|
436
|
+
'submit_with_check',
|
|
437
|
+
'list_project_structure',
|
|
438
|
+
'get_file_summary',
|
|
439
|
+
'semantic_search_code',
|
|
440
|
+
// AST 结构化分析工具
|
|
441
|
+
'get_project_overview',
|
|
442
|
+
'get_class_hierarchy',
|
|
443
|
+
'get_class_info',
|
|
444
|
+
'get_protocol_info',
|
|
445
|
+
'get_method_overrides',
|
|
446
|
+
'get_category_map',
|
|
447
|
+
'get_previous_analysis',
|
|
448
|
+
// Agent Memory 工具 (v4.0)
|
|
449
|
+
'note_finding',
|
|
450
|
+
'get_previous_evidence',
|
|
451
|
+
]
|
|
452
|
+
: null);
|
|
349
453
|
const toolSchemas = this.#toolRegistry.getToolSchemas(effectiveAllowedTools);
|
|
350
454
|
|
|
351
455
|
const toolCalls = [];
|
|
352
456
|
const maxIter = isSystem ? budget.maxIterations : MAX_ITERATIONS;
|
|
353
457
|
let consecutiveAiErrors = 0;
|
|
354
458
|
let consecutiveEmptyResponses = 0;
|
|
355
|
-
let iterationCount = 0;
|
|
459
|
+
let iterationCount = 0; // v3: 独立迭代计数器
|
|
356
460
|
const submittedTitles = new Set(this.#globalSubmittedTitles);
|
|
357
461
|
const sharedState = {}; // P2.2: 跨工具调用共享状态(搜索计数器等)
|
|
358
462
|
|
|
359
463
|
// ── 进度感知收敛 (Analyst 专用: disablePhaseRouter=true) ──
|
|
360
464
|
// 追踪搜索/读取新信息的效率,当探索饱和时提前引导 AI 收敛
|
|
361
465
|
const explorationMetrics = {
|
|
362
|
-
uniqueFiles: new Set(),
|
|
363
|
-
uniquePatterns: new Set(),
|
|
364
|
-
uniqueQueries: new Set(),
|
|
365
|
-
totalToolCalls: 0,
|
|
366
|
-
newInfoRounds: 0,
|
|
367
|
-
staleRounds: 0,
|
|
368
|
-
convergenceNudged: false,
|
|
466
|
+
uniqueFiles: new Set(), // 已读取的唯一文件
|
|
467
|
+
uniquePatterns: new Set(), // 已搜索的唯一 pattern
|
|
468
|
+
uniqueQueries: new Set(), // 已查询的唯一类名/目录(AST + list_project_structure)
|
|
469
|
+
totalToolCalls: 0, // 总工具调用数
|
|
470
|
+
newInfoRounds: 0, // 最近 N 轮中获取到新信息的轮次
|
|
471
|
+
staleRounds: 0, // 连续未获取新信息的轮次
|
|
472
|
+
convergenceNudged: false, // 是否已注入收敛 nudge
|
|
369
473
|
};
|
|
370
|
-
const MIN_EXPLORE_ITERS = 16;
|
|
371
|
-
const STALE_THRESHOLD = 3;
|
|
474
|
+
const MIN_EXPLORE_ITERS = 16; // 最少探索轮次(冷启动质量保障)
|
|
475
|
+
const STALE_THRESHOLD = 3; // 连续无新信息轮次触发收敛
|
|
372
476
|
|
|
373
477
|
// ── Layer 4: ReasoningLayer — 推理链采集 + 结构化观察 + 反思 + 规划 ──
|
|
374
478
|
const reasoning = new ReasoningLayer({
|
|
375
479
|
enabled: true,
|
|
376
|
-
reflectionEnabled: isSystem,
|
|
480
|
+
reflectionEnabled: isSystem, // 仅 system 模式启用反思
|
|
377
481
|
reflectionInterval: isSystem ? 5 : 0,
|
|
378
|
-
planningEnabled: isSystem,
|
|
482
|
+
planningEnabled: isSystem, // 仅 system 模式启用规划
|
|
379
483
|
replanInterval: isSystem ? 8 : 0,
|
|
380
484
|
deviationThreshold: 0.6,
|
|
381
485
|
});
|
|
@@ -386,49 +490,71 @@ export class ChatAgent {
|
|
|
386
490
|
if (phaseRouter) {
|
|
387
491
|
phaseRouter.tick();
|
|
388
492
|
if (phaseRouter.shouldExit()) {
|
|
389
|
-
this.#logger.info(
|
|
493
|
+
this.#logger.info(
|
|
494
|
+
`[ChatAgent] PhaseRouter exit: phase=${phaseRouter.phase}, iter=${phaseRouter.totalIterations}, submits=${phaseRouter.totalSubmits}`
|
|
495
|
+
);
|
|
390
496
|
break;
|
|
391
497
|
}
|
|
392
498
|
// PhaseRouter 因 maxIterations 强制转入 SUMMARIZE → 注入收尾 nudge
|
|
393
499
|
if (phaseRouter.consumeForcedSummarize()) {
|
|
394
|
-
const submitCount = toolCalls.filter(
|
|
500
|
+
const submitCount = toolCalls.filter(
|
|
501
|
+
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
502
|
+
).length;
|
|
395
503
|
ctx.appendUserNudge(
|
|
396
504
|
`⚠️ 轮次即将耗尽 (${phaseRouter.totalIterations}/${budget.maxIterations}),**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
|
|
397
|
-
|
|
505
|
+
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
|
|
506
|
+
);
|
|
507
|
+
this.#logger.info(
|
|
508
|
+
'[ChatAgent] 📝 injected forced SUMMARIZE nudge (maxIterations reached)'
|
|
398
509
|
);
|
|
399
|
-
this.#logger.info('[ChatAgent] 📝 injected forced SUMMARIZE nudge (maxIterations reached)');
|
|
400
510
|
}
|
|
401
|
-
} else if (
|
|
402
|
-
|
|
511
|
+
} else if (
|
|
512
|
+
isSystem &&
|
|
513
|
+
!phaseRouter &&
|
|
514
|
+
!ctx.__gracefulExitInjected &&
|
|
515
|
+
!explorationMetrics.convergenceNudged &&
|
|
516
|
+
iterationCount >= MIN_EXPLORE_ITERS &&
|
|
517
|
+
explorationMetrics.staleRounds >= STALE_THRESHOLD
|
|
518
|
+
) {
|
|
403
519
|
// ── 进度感知收敛:探索已饱和,提前引导 AI 总结 ──
|
|
404
520
|
this.#logger.info(
|
|
405
521
|
`[ChatAgent] 📊 Exploration saturated at iter ${iterationCount}/${maxIter} — ` +
|
|
406
|
-
|
|
407
|
-
|
|
522
|
+
`files=${explorationMetrics.uniqueFiles.size}, patterns=${explorationMetrics.uniquePatterns.size}, ` +
|
|
523
|
+
`queries=${explorationMetrics.uniqueQueries.size}, staleRounds=${explorationMetrics.staleRounds} — nudging convergence`
|
|
408
524
|
);
|
|
409
525
|
ctx.appendUserNudge(
|
|
410
526
|
`你已经充分探索了项目代码(${explorationMetrics.uniqueFiles.size} 个文件,${explorationMetrics.uniquePatterns.size} 次不同搜索,${explorationMetrics.uniqueQueries.size} 次结构化查询)。` +
|
|
411
|
-
|
|
412
|
-
|
|
527
|
+
`最近 ${explorationMetrics.staleRounds} 轮没有发现新信息,建议开始撰写分析总结。\n` +
|
|
528
|
+
`如果你确信还有重要方面未覆盖,可以继续探索(剩余 ${maxIter - iterationCount} 轮);否则请直接输出你的分析发现。`
|
|
413
529
|
);
|
|
414
530
|
explorationMetrics.convergenceNudged = true;
|
|
415
531
|
// 不 break,不设 toolChoice=none — 这是软 nudge,AI 仍可继续探索
|
|
416
|
-
} else if (
|
|
417
|
-
|
|
532
|
+
} else if (
|
|
533
|
+
isSystem &&
|
|
534
|
+
!phaseRouter &&
|
|
535
|
+
!ctx.__budgetWarningInjected &&
|
|
536
|
+
iterationCount >= Math.floor(maxIter * 0.75)
|
|
537
|
+
) {
|
|
418
538
|
// ── 预算感知提醒:75% 预算消耗时轻量提示 ──
|
|
419
539
|
ctx.appendUserNudge(
|
|
420
|
-
`📌 进度提醒:你已使用 ${iterationCount}/${maxIter} 轮次(${Math.round(iterationCount / maxIter * 100)}%)。` +
|
|
421
|
-
|
|
540
|
+
`📌 进度提醒:你已使用 ${iterationCount}/${maxIter} 轮次(${Math.round((iterationCount / maxIter) * 100)}%)。` +
|
|
541
|
+
`请确保核心方面已覆盖,开始准备总结。剩余 ${maxIter - iterationCount} 轮,优先填补最重要的分析空白。`
|
|
422
542
|
);
|
|
423
543
|
ctx.__budgetWarningInjected = true;
|
|
424
|
-
this.#logger.info(
|
|
544
|
+
this.#logger.info(
|
|
545
|
+
`[ChatAgent] 📌 Budget warning at ${iterationCount}/${maxIter} (${Math.round((iterationCount / maxIter) * 100)}%)`
|
|
546
|
+
);
|
|
425
547
|
} else if (isSystem && iterationCount >= maxIter && !ctx.__gracefulExitInjected) {
|
|
426
548
|
// 达到上限 → 注入收尾消息让 AI 快速总结(而非硬中断)
|
|
427
|
-
this.#logger.info(
|
|
428
|
-
|
|
549
|
+
this.#logger.info(
|
|
550
|
+
`[ChatAgent] Iteration cap reached (${iterationCount}/${maxIter}) — injecting graceful exit nudge`
|
|
551
|
+
);
|
|
552
|
+
const submitCount = toolCalls.filter(
|
|
553
|
+
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
554
|
+
).length;
|
|
429
555
|
ctx.appendUserNudge(
|
|
430
556
|
`⚠️ 你已使用 ${iterationCount}/${maxIter} 轮次,**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
|
|
431
|
-
|
|
557
|
+
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
|
|
432
558
|
);
|
|
433
559
|
ctx.__gracefulExitInjected = true;
|
|
434
560
|
// 不 break,给 AI 2 轮 grace 来产出总结
|
|
@@ -459,7 +585,9 @@ export class ChatAgent {
|
|
|
459
585
|
|
|
460
586
|
// ── ReasoningLayer Hook 1: beforeAICall — 开始新轮次 + 反思检查 ──
|
|
461
587
|
const reflectionNudge = reasoning.beforeAICall(iterationCount, {
|
|
462
|
-
explorationMetrics,
|
|
588
|
+
explorationMetrics,
|
|
589
|
+
budget,
|
|
590
|
+
phase: phaseRouter?.phase,
|
|
463
591
|
});
|
|
464
592
|
if (reflectionNudge) {
|
|
465
593
|
ctx.appendUserNudge(reflectionNudge);
|
|
@@ -469,7 +597,9 @@ export class ChatAgent {
|
|
|
469
597
|
// ── 压缩检查 (每次 AI 调用前) ──
|
|
470
598
|
const compactResult = ctx.compactIfNeeded();
|
|
471
599
|
if (compactResult.level > 0) {
|
|
472
|
-
this.#logger.info(
|
|
600
|
+
this.#logger.info(
|
|
601
|
+
`[ChatAgent] context compacted: L${compactResult.level}, removed ${compactResult.removed} items`
|
|
602
|
+
);
|
|
473
603
|
}
|
|
474
604
|
|
|
475
605
|
// ── 构建 systemPrompt (含阶段提示) ──
|
|
@@ -499,7 +629,7 @@ export class ChatAgent {
|
|
|
499
629
|
if (workingMemory && isSystem) {
|
|
500
630
|
const wmContext = workingMemory.buildContext();
|
|
501
631
|
if (wmContext) {
|
|
502
|
-
systemPrompt +=
|
|
632
|
+
systemPrompt += `\n\n${wmContext}`;
|
|
503
633
|
}
|
|
504
634
|
}
|
|
505
635
|
|
|
@@ -508,10 +638,17 @@ export class ChatAgent {
|
|
|
508
638
|
try {
|
|
509
639
|
const messages = ctx.toMessages();
|
|
510
640
|
const currentPhase = phaseRouter?.phase || 'user';
|
|
511
|
-
this.#logger.info(
|
|
641
|
+
this.#logger.info(
|
|
642
|
+
`[ChatAgent] 🔄 iteration ${currentIter}/${maxIter} — phase=${currentPhase}, ${messages.length} msgs, toolChoice=${currentChoice}, tokens~${ctx.estimateTokens()}`
|
|
643
|
+
);
|
|
512
644
|
|
|
513
645
|
// SSE: 推送步骤开始
|
|
514
|
-
onProgress?.({
|
|
646
|
+
onProgress?.({
|
|
647
|
+
type: 'step:start',
|
|
648
|
+
step: currentIter,
|
|
649
|
+
maxSteps: maxIter,
|
|
650
|
+
phase: currentPhase,
|
|
651
|
+
});
|
|
515
652
|
|
|
516
653
|
aiResult = await this.#aiProvider.chatWithTools(prompt, {
|
|
517
654
|
messages,
|
|
@@ -531,10 +668,14 @@ export class ChatAgent {
|
|
|
531
668
|
}
|
|
532
669
|
|
|
533
670
|
if (aiResult.functionCalls?.length > 0) {
|
|
534
|
-
this.#logger.info(
|
|
671
|
+
this.#logger.info(
|
|
672
|
+
`[ChatAgent] ✓ AI returned ${aiResult.functionCalls.length} function calls in ${aiDuration}ms: [${aiResult.functionCalls.map((fc) => fc.name).join(', ')}]`
|
|
673
|
+
);
|
|
535
674
|
} else {
|
|
536
675
|
const textPreview = (aiResult.text || '').substring(0, 120).replace(/\n/g, '↵');
|
|
537
|
-
this.#logger.info(
|
|
676
|
+
this.#logger.info(
|
|
677
|
+
`[ChatAgent] ✓ AI returned text in ${aiDuration}ms (${(aiResult.text || '').length} chars) — "${textPreview}…"`
|
|
678
|
+
);
|
|
538
679
|
}
|
|
539
680
|
consecutiveAiErrors = 0;
|
|
540
681
|
|
|
@@ -542,7 +683,9 @@ export class ChatAgent {
|
|
|
542
683
|
reasoning.afterAICall(aiResult);
|
|
543
684
|
} catch (aiErr) {
|
|
544
685
|
consecutiveAiErrors++;
|
|
545
|
-
this.#logger.warn(
|
|
686
|
+
this.#logger.warn(
|
|
687
|
+
`[ChatAgent] AI call failed (attempt ${consecutiveAiErrors}): ${aiErr.message}`
|
|
688
|
+
);
|
|
546
689
|
|
|
547
690
|
// 熔断器已打开时立即跳出 — 不要继续浪费重试、也避免失败计数加速累积
|
|
548
691
|
if (aiErr.code === 'CIRCUIT_OPEN') {
|
|
@@ -562,7 +705,9 @@ export class ChatAgent {
|
|
|
562
705
|
|
|
563
706
|
if (consecutiveAiErrors >= 2) {
|
|
564
707
|
if (isSystem) {
|
|
565
|
-
this.#logger.warn(
|
|
708
|
+
this.#logger.warn(
|
|
709
|
+
`[ChatAgent] 🛑 2 consecutive AI errors — resetting context, breaking to summary`
|
|
710
|
+
);
|
|
566
711
|
ctx.resetToPromptOnly();
|
|
567
712
|
break;
|
|
568
713
|
}
|
|
@@ -576,7 +721,7 @@ export class ChatAgent {
|
|
|
576
721
|
reasoningQuality: reasoning.getQualityMetrics(),
|
|
577
722
|
};
|
|
578
723
|
}
|
|
579
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
724
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
580
725
|
continue;
|
|
581
726
|
}
|
|
582
727
|
|
|
@@ -585,13 +730,23 @@ export class ChatAgent {
|
|
|
585
730
|
// ── Graceful exit 保护: Gemini 有时会无视 toolChoice='none' 继续返回工具调用
|
|
586
731
|
// 强制忽略工具调用,将附带文本视为最终回复
|
|
587
732
|
if (ctx.__gracefulExitInjected) {
|
|
588
|
-
this.#logger.warn(
|
|
733
|
+
this.#logger.warn(
|
|
734
|
+
`[ChatAgent] ⚠ AI returned ${aiResult.functionCalls.length} tool calls despite toolChoice=none (graceful exit) — ignoring tools, treating as text`
|
|
735
|
+
);
|
|
589
736
|
if (aiResult.text) {
|
|
590
737
|
const reply = this.#cleanFinalAnswer(aiResult.text);
|
|
591
738
|
const totalDuration = Date.now() - execStartTime;
|
|
592
739
|
reasoning.afterRound();
|
|
593
|
-
this.#logger.info(
|
|
594
|
-
|
|
740
|
+
this.#logger.info(
|
|
741
|
+
`[ChatAgent] ✅ final answer (graceful exit, forced) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
742
|
+
);
|
|
743
|
+
return {
|
|
744
|
+
reply,
|
|
745
|
+
toolCalls,
|
|
746
|
+
hasContext: toolCalls.length > 0,
|
|
747
|
+
reasoningTrace: reasoning.trace,
|
|
748
|
+
reasoningQuality: reasoning.getQualityMetrics(),
|
|
749
|
+
};
|
|
595
750
|
}
|
|
596
751
|
// 无文本时继续循环,下一轮硬上限兜底
|
|
597
752
|
continue;
|
|
@@ -601,7 +756,9 @@ export class ChatAgent {
|
|
|
601
756
|
const MAX_TOOL_CALLS_PER_ITER = 8;
|
|
602
757
|
let activeCalls = aiResult.functionCalls;
|
|
603
758
|
if (activeCalls.length > MAX_TOOL_CALLS_PER_ITER) {
|
|
604
|
-
this.#logger.warn(
|
|
759
|
+
this.#logger.warn(
|
|
760
|
+
`[ChatAgent] ⚠ ${activeCalls.length} tool calls, capping to ${MAX_TOOL_CALLS_PER_ITER}`
|
|
761
|
+
);
|
|
605
762
|
activeCalls = activeCalls.slice(0, MAX_TOOL_CALLS_PER_ITER);
|
|
606
763
|
}
|
|
607
764
|
|
|
@@ -609,14 +766,21 @@ export class ChatAgent {
|
|
|
609
766
|
ctx.appendAssistantWithToolCalls(aiResult.text || null, activeCalls);
|
|
610
767
|
|
|
611
768
|
let roundSubmitCount = 0;
|
|
612
|
-
let roundHasNewInfo = false;
|
|
769
|
+
let roundHasNewInfo = false; // 本轮是否获取到新信息
|
|
613
770
|
|
|
614
771
|
for (const fc of activeCalls) {
|
|
615
772
|
const toolStartTime = Date.now();
|
|
616
|
-
this.#logger.info(
|
|
773
|
+
this.#logger.info(
|
|
774
|
+
`[ChatAgent] 🔧 ${fc.name}(${JSON.stringify(fc.args).substring(0, 100)})`
|
|
775
|
+
);
|
|
617
776
|
|
|
618
777
|
// SSE: 推送工具调用开始
|
|
619
|
-
onProgress?.({
|
|
778
|
+
onProgress?.({
|
|
779
|
+
type: 'tool:start',
|
|
780
|
+
id: `tc_${fc.name}_${Date.now()}`,
|
|
781
|
+
tool: fc.name,
|
|
782
|
+
args: fc.args,
|
|
783
|
+
});
|
|
620
784
|
|
|
621
785
|
let toolResult;
|
|
622
786
|
let cacheHit = false;
|
|
@@ -637,28 +801,48 @@ export class ChatAgent {
|
|
|
637
801
|
fc.name,
|
|
638
802
|
fc.args,
|
|
639
803
|
this.#getToolContext({
|
|
640
|
-
_sessionToolCalls: toolCalls,
|
|
641
|
-
|
|
804
|
+
_sessionToolCalls: toolCalls,
|
|
805
|
+
_dimensionMeta: dimensionMeta,
|
|
806
|
+
_submittedTitles: submittedTitles,
|
|
807
|
+
_submittedPatterns: this.#globalSubmittedPatterns,
|
|
808
|
+
_sharedState: sharedState,
|
|
642
809
|
_projectLanguage: projectLanguage,
|
|
643
810
|
// v4.0: Agent Memory
|
|
644
811
|
_workingMemory: workingMemory || null,
|
|
645
812
|
_episodicMemory: episodicMemory || null,
|
|
646
813
|
_toolResultCache: toolResultCache || null,
|
|
647
814
|
_currentRound: iterationCount,
|
|
648
|
-
})
|
|
815
|
+
})
|
|
649
816
|
);
|
|
650
817
|
const toolDuration = Date.now() - toolStartTime;
|
|
651
|
-
const resultSize =
|
|
652
|
-
|
|
818
|
+
const resultSize =
|
|
819
|
+
typeof toolResult === 'string'
|
|
820
|
+
? toolResult.length
|
|
821
|
+
: JSON.stringify(toolResult).length;
|
|
822
|
+
this.#logger.info(
|
|
823
|
+
`[ChatAgent] 🔧 done: ${fc.name} → ${resultSize} chars in ${toolDuration}ms`
|
|
824
|
+
);
|
|
653
825
|
|
|
654
826
|
// SSE: 推送工具调用完成
|
|
655
|
-
onProgress?.({
|
|
827
|
+
onProgress?.({
|
|
828
|
+
type: 'tool:end',
|
|
829
|
+
tool: fc.name,
|
|
830
|
+
status: 'ok',
|
|
831
|
+
resultSize,
|
|
832
|
+
duration: toolDuration,
|
|
833
|
+
});
|
|
656
834
|
} catch (toolErr) {
|
|
657
835
|
this.#logger.warn(`[ChatAgent] 🔧 FAILED: ${fc.name} — ${toolErr.message}`);
|
|
658
836
|
toolResult = { error: `tool "${fc.name}" failed: ${toolErr.message}` };
|
|
659
837
|
|
|
660
838
|
// SSE: 推送工具调用失败
|
|
661
|
-
onProgress?.({
|
|
839
|
+
onProgress?.({
|
|
840
|
+
type: 'tool:end',
|
|
841
|
+
tool: fc.name,
|
|
842
|
+
status: 'error',
|
|
843
|
+
error: toolErr.message,
|
|
844
|
+
duration: Date.now() - toolStartTime,
|
|
845
|
+
});
|
|
662
846
|
}
|
|
663
847
|
}
|
|
664
848
|
|
|
@@ -710,7 +894,7 @@ export class ChatAgent {
|
|
|
710
894
|
}
|
|
711
895
|
}
|
|
712
896
|
for (const sub of Object.values(batchResults)) {
|
|
713
|
-
for (const m of
|
|
897
|
+
for (const m of sub.matches || []) {
|
|
714
898
|
if (m.file && !explorationMetrics.uniqueFiles.has(m.file)) {
|
|
715
899
|
explorationMetrics.uniqueFiles.add(m.file);
|
|
716
900
|
foundNewInfo = true;
|
|
@@ -739,11 +923,16 @@ export class ChatAgent {
|
|
|
739
923
|
explorationMetrics.uniqueQueries.add(qKey);
|
|
740
924
|
foundNewInfo = true;
|
|
741
925
|
}
|
|
742
|
-
} else if (
|
|
743
|
-
|
|
744
|
-
|
|
926
|
+
} else if (
|
|
927
|
+
fc.name === 'get_class_info' ||
|
|
928
|
+
fc.name === 'get_class_hierarchy' ||
|
|
929
|
+
fc.name === 'get_protocol_info' ||
|
|
930
|
+
fc.name === 'get_method_overrides' ||
|
|
931
|
+
fc.name === 'get_category_map'
|
|
932
|
+
) {
|
|
745
933
|
// AST 结构化查询:同一类名/协议名只算一次新信息
|
|
746
|
-
const queryTarget =
|
|
934
|
+
const queryTarget =
|
|
935
|
+
fc.args?.className || fc.args?.protocolName || fc.args?.name || '';
|
|
747
936
|
const qKey = `${fc.name}:${queryTarget}`;
|
|
748
937
|
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
749
938
|
explorationMetrics.uniqueQueries.add(qKey);
|
|
@@ -780,19 +969,31 @@ export class ChatAgent {
|
|
|
780
969
|
if (fc.name === 'submit_knowledge' || fc.name === 'submit_with_check') {
|
|
781
970
|
const title = fc.args?.title || fc.args?.category || '';
|
|
782
971
|
const isRejected = typeof toolResult === 'object' && toolResult?.status === 'rejected';
|
|
783
|
-
const isError =
|
|
972
|
+
const isError =
|
|
973
|
+
typeof toolResult === 'object' &&
|
|
974
|
+
(toolResult?.error || toolResult?.status === 'error');
|
|
784
975
|
|
|
785
976
|
if (isRejected) {
|
|
786
977
|
this.#logger.info(`[ChatAgent] 🚫 off-topic rejected: "${title}"`);
|
|
787
978
|
} else if (isError) {
|
|
788
979
|
// 候选创建失败(如 validation error)— 不加入 submittedTitles,允许 AI 重试
|
|
789
|
-
this.#logger.info(
|
|
980
|
+
this.#logger.info(
|
|
981
|
+
`[ChatAgent] ⚠ submit error: "${title}" — ${toolResult.error || 'unknown'}`
|
|
982
|
+
);
|
|
790
983
|
} else if (submittedTitles.has(title)) {
|
|
791
984
|
resultStr = `⚠ 重复提交: "${title}" 已存在。`;
|
|
792
985
|
this.#logger.info(`[ChatAgent] 🔁 duplicate: "${title}"`);
|
|
793
986
|
} else {
|
|
794
987
|
submittedTitles.add(title);
|
|
795
988
|
this.#globalSubmittedTitles.add(title);
|
|
989
|
+
// 记录代码模式指纹用于跨维度去重
|
|
990
|
+
const pattern = fc.args?.content?.pattern || '';
|
|
991
|
+
if (pattern.length >= 30) {
|
|
992
|
+
const fp = pattern.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '').replace(/[\s]+/g, '').toLowerCase().slice(0, 200);
|
|
993
|
+
if (fp.length >= 20) {
|
|
994
|
+
this.#globalSubmittedPatterns.add(fp);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
796
997
|
roundSubmitCount++;
|
|
797
998
|
}
|
|
798
999
|
}
|
|
@@ -836,7 +1037,9 @@ export class ChatAgent {
|
|
|
836
1037
|
// skill-only 维度从 EXPLORE 直接进入 SUMMARIZE (跳过 PRODUCE),
|
|
837
1038
|
// 需要明确告知 AI 输出 dimensionDigest JSON
|
|
838
1039
|
if (transition.transitioned && transition.newPhase === 'SUMMARIZE') {
|
|
839
|
-
const submitCount = toolCalls.filter(
|
|
1040
|
+
const submitCount = toolCalls.filter(
|
|
1041
|
+
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
1042
|
+
).length;
|
|
840
1043
|
ctx.appendUserNudge(
|
|
841
1044
|
`你已完成分析探索。请在回复中直接输出 dimensionDigest JSON(用 \`\`\`json 包裹),包含以下字段:\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因(如:提交上限已达)","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。如果有未来得及处理的信号,请在此标记,系统会在下次运行时续传。`
|
|
842
1045
|
);
|
|
@@ -855,8 +1058,10 @@ export class ChatAgent {
|
|
|
855
1058
|
// 空响应重试(Gemini 偶发)
|
|
856
1059
|
if (!aiResult.text && isSystem && consecutiveEmptyResponses < 2) {
|
|
857
1060
|
consecutiveEmptyResponses++;
|
|
858
|
-
this.#logger.warn(
|
|
859
|
-
|
|
1061
|
+
this.#logger.warn(
|
|
1062
|
+
`[ChatAgent] ⚠ empty response from system source — retrying (${consecutiveEmptyResponses}/2)`
|
|
1063
|
+
);
|
|
1064
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
860
1065
|
continue;
|
|
861
1066
|
}
|
|
862
1067
|
// 收到非空响应时重置空响应计数器
|
|
@@ -878,19 +1083,31 @@ export class ChatAgent {
|
|
|
878
1083
|
// 注入 nudge 让 AI 再输出一次 digest JSON
|
|
879
1084
|
if (transition.transitioned) {
|
|
880
1085
|
ctx.appendAssistantText(aiResult.text || '');
|
|
881
|
-
const submitCount = toolCalls.filter(
|
|
1086
|
+
const submitCount = toolCalls.filter(
|
|
1087
|
+
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
1088
|
+
).length;
|
|
882
1089
|
ctx.appendUserNudge(
|
|
883
1090
|
`请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹):\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理的信号","reason":"原因","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 记录未来得及处理的信号。已全部覆盖则留空 \`[]\`。`
|
|
884
1091
|
);
|
|
885
|
-
this.#logger.info(
|
|
1092
|
+
this.#logger.info(
|
|
1093
|
+
'[ChatAgent] 📝 injected SUMMARIZE nudge (text-triggered transition)'
|
|
1094
|
+
);
|
|
886
1095
|
continue;
|
|
887
1096
|
}
|
|
888
1097
|
// 已在 SUMMARIZE 阶段 — 这就是最终回答
|
|
889
1098
|
const reply = this.#cleanFinalAnswer(aiResult.text || '');
|
|
890
1099
|
const totalDuration = Date.now() - execStartTime;
|
|
891
1100
|
reasoning.afterRound();
|
|
892
|
-
this.#logger.info(
|
|
893
|
-
|
|
1101
|
+
this.#logger.info(
|
|
1102
|
+
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${phaseRouter.totalIterations} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
1103
|
+
);
|
|
1104
|
+
return {
|
|
1105
|
+
reply,
|
|
1106
|
+
toolCalls,
|
|
1107
|
+
hasContext: toolCalls.length > 0,
|
|
1108
|
+
reasoningTrace: reasoning.trace,
|
|
1109
|
+
reasoningQuality: reasoning.getQualityMetrics(),
|
|
1110
|
+
};
|
|
894
1111
|
}
|
|
895
1112
|
|
|
896
1113
|
if (!transition.transitioned) {
|
|
@@ -902,7 +1119,9 @@ export class ChatAgent {
|
|
|
902
1119
|
ctx.appendUserNudge(
|
|
903
1120
|
'你的分析很好。请继续调用 submit_knowledge 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。'
|
|
904
1121
|
);
|
|
905
|
-
this.#logger.info(
|
|
1122
|
+
this.#logger.info(
|
|
1123
|
+
'[ChatAgent] 📝 injected submit nudge (text in PRODUCE, not transitioning)'
|
|
1124
|
+
);
|
|
906
1125
|
}
|
|
907
1126
|
continue;
|
|
908
1127
|
}
|
|
@@ -910,8 +1129,16 @@ export class ChatAgent {
|
|
|
910
1129
|
const reply = this.#cleanFinalAnswer(aiResult.text || '');
|
|
911
1130
|
const totalDuration = Date.now() - execStartTime;
|
|
912
1131
|
reasoning.afterRound();
|
|
913
|
-
this.#logger.info(
|
|
914
|
-
|
|
1132
|
+
this.#logger.info(
|
|
1133
|
+
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${phaseRouter.totalIterations} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
1134
|
+
);
|
|
1135
|
+
return {
|
|
1136
|
+
reply,
|
|
1137
|
+
toolCalls,
|
|
1138
|
+
hasContext: toolCalls.length > 0,
|
|
1139
|
+
reasoningTrace: reasoning.trace,
|
|
1140
|
+
reasoningQuality: reasoning.getQualityMetrics(),
|
|
1141
|
+
};
|
|
915
1142
|
}
|
|
916
1143
|
|
|
917
1144
|
// 其他阶段的文字回答 → 继续循环(PhaseRouter 已自动转换阶段)
|
|
@@ -924,22 +1151,44 @@ export class ChatAgent {
|
|
|
924
1151
|
const reply = this.#cleanFinalAnswer(aiResult.text || '');
|
|
925
1152
|
const totalDuration = Date.now() - execStartTime;
|
|
926
1153
|
reasoning.afterRound();
|
|
927
|
-
this.#logger.info(
|
|
928
|
-
|
|
1154
|
+
this.#logger.info(
|
|
1155
|
+
`[ChatAgent] ✅ final answer (${ctx.__gracefulExitInjected ? 'graceful exit' : 'user'}) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
1156
|
+
);
|
|
1157
|
+
return {
|
|
1158
|
+
reply,
|
|
1159
|
+
toolCalls,
|
|
1160
|
+
hasContext: toolCalls.length > 0,
|
|
1161
|
+
reasoningTrace: reasoning.trace,
|
|
1162
|
+
reasoningQuality: reasoning.getQualityMetrics(),
|
|
1163
|
+
};
|
|
929
1164
|
}
|
|
930
1165
|
|
|
931
1166
|
// system 模式非 graceful exit 的文字回答(理论上不应到这里)
|
|
932
1167
|
const reply = this.#cleanFinalAnswer(aiResult.text || '');
|
|
933
1168
|
const totalDuration = Date.now() - execStartTime;
|
|
934
1169
|
reasoning.afterRound();
|
|
935
|
-
this.#logger.info(
|
|
936
|
-
|
|
1170
|
+
this.#logger.info(
|
|
1171
|
+
`[ChatAgent] ✅ final answer — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
|
|
1172
|
+
);
|
|
1173
|
+
return {
|
|
1174
|
+
reply,
|
|
1175
|
+
toolCalls,
|
|
1176
|
+
hasContext: toolCalls.length > 0,
|
|
1177
|
+
reasoningTrace: reasoning.trace,
|
|
1178
|
+
reasoningQuality: reasoning.getQualityMetrics(),
|
|
1179
|
+
};
|
|
937
1180
|
}
|
|
938
1181
|
|
|
939
1182
|
// ── 循环退出: 产出 dimensionDigest 总结 ──
|
|
940
1183
|
reasoning.afterRound();
|
|
941
1184
|
const forcedResult = await this.#produceForcedSummary({
|
|
942
|
-
source,
|
|
1185
|
+
source,
|
|
1186
|
+
toolCalls,
|
|
1187
|
+
toolSchemas,
|
|
1188
|
+
ctx,
|
|
1189
|
+
phaseRouter,
|
|
1190
|
+
execStartTime,
|
|
1191
|
+
prompt,
|
|
943
1192
|
});
|
|
944
1193
|
forcedResult.reasoningTrace = reasoning.trace;
|
|
945
1194
|
forcedResult.reasoningQuality = reasoning.getQualityMetrics();
|
|
@@ -950,32 +1199,55 @@ export class ChatAgent {
|
|
|
950
1199
|
* 强制退出后的摘要生成 — 独立方法,避免主循环代码膨胀
|
|
951
1200
|
* @private
|
|
952
1201
|
*/
|
|
953
|
-
async #produceForcedSummary({
|
|
1202
|
+
async #produceForcedSummary({
|
|
1203
|
+
source,
|
|
1204
|
+
toolCalls,
|
|
1205
|
+
toolSchemas,
|
|
1206
|
+
ctx,
|
|
1207
|
+
phaseRouter,
|
|
1208
|
+
execStartTime,
|
|
1209
|
+
prompt,
|
|
1210
|
+
}) {
|
|
954
1211
|
const iterations = phaseRouter?.totalIterations || 0;
|
|
955
|
-
|
|
1212
|
+
const isSystem = source === 'system';
|
|
1213
|
+
this.#logger.info(
|
|
1214
|
+
`[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls, source=${source})`
|
|
1215
|
+
);
|
|
956
1216
|
|
|
957
|
-
const candidateCount = toolCalls.filter(
|
|
958
|
-
tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
1217
|
+
const candidateCount = toolCalls.filter(
|
|
1218
|
+
(tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
959
1219
|
).length;
|
|
960
1220
|
|
|
961
1221
|
let finalReply;
|
|
962
1222
|
|
|
963
|
-
// 如果熔断器已打开,跳过 AI
|
|
1223
|
+
// 如果熔断器已打开,跳过 AI 调用直接合成摘要(避免无用的失败 + 计数累积)
|
|
964
1224
|
const isCircuitOpen = this.#aiProvider._circuitState === 'OPEN';
|
|
965
1225
|
if (isCircuitOpen) {
|
|
966
|
-
this.#logger.warn(
|
|
1226
|
+
this.#logger.warn(
|
|
1227
|
+
`[ChatAgent] circuit breaker is OPEN — skipping AI summary, using synthetic ${isSystem ? 'digest' : 'summary'}`
|
|
1228
|
+
);
|
|
967
1229
|
}
|
|
968
1230
|
|
|
1231
|
+
// ── 收集工具调用摘要(user / system 共用) ──
|
|
1232
|
+
const submitSummary = toolCalls
|
|
1233
|
+
.filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
1234
|
+
.map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
|
|
1235
|
+
.join('\n');
|
|
1236
|
+
|
|
1237
|
+
// ── 收集工具调用上下文(user 源需要更丰富的上下文来生成自然语言总结) ──
|
|
1238
|
+
const toolContextSummary = isSystem ? '' : this.#buildToolContextForUserSummary(toolCalls);
|
|
1239
|
+
|
|
969
1240
|
try {
|
|
970
|
-
if (isCircuitOpen)
|
|
1241
|
+
if (isCircuitOpen) {
|
|
1242
|
+
throw new Error('circuit open — skip to synthetic summary');
|
|
1243
|
+
}
|
|
971
1244
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
.map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
|
|
975
|
-
.join('\n');
|
|
1245
|
+
let summaryPrompt;
|
|
1246
|
+
let systemPrompt;
|
|
976
1247
|
|
|
977
|
-
|
|
978
|
-
|
|
1248
|
+
if (isSystem) {
|
|
1249
|
+
// ── system 源: 输出 dimensionDigest JSON(供 Bootstrap 管线消费) ──
|
|
1250
|
+
summaryPrompt = `你已完成 ${iterations} 轮工具调用(共 ${toolCalls.length} 次),提交了 ${candidateCount} 个候选。
|
|
979
1251
|
${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
980
1252
|
**必须**输出 dimensionDigest JSON(用 \`\`\`json 包裹):
|
|
981
1253
|
\`\`\`json
|
|
@@ -992,21 +1264,36 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
992
1264
|
}
|
|
993
1265
|
}
|
|
994
1266
|
\`\`\`
|
|
995
|
-
> remainingTasks: 列出本次未来得及处理的信号/主题。已全部覆盖则留空 \`[]
|
|
996
|
-
|
|
1267
|
+
> remainingTasks: 列出本次未来得及处理的信号/主题。已全部覆盖则留空 \`[]\`。`;
|
|
1268
|
+
systemPrompt = '直接输出 dimensionDigest JSON 总结,不要调用工具。';
|
|
1269
|
+
} else {
|
|
1270
|
+
// ── user 源: 输出人类可读的 Markdown 结构化总结(前端 AI Chat 展示) ──
|
|
1271
|
+
const userQuestion = prompt ? `用户的原始问题:「${prompt.slice(0, 500)}」\n\n` : '';
|
|
1272
|
+
summaryPrompt = `${userQuestion}你刚才通过 ${toolCalls.length} 次工具调用分析了项目代码。以下是你调用过的工具和获取到的关键信息:
|
|
1273
|
+
|
|
1274
|
+
${toolContextSummary}
|
|
1275
|
+
|
|
1276
|
+
请基于以上收集到的信息,用**清晰易读的 Markdown** 格式撰写分析总结,直接回答用户的问题。
|
|
1277
|
+
|
|
1278
|
+
要求:
|
|
1279
|
+
- 使用二级/三级标题组织内容
|
|
1280
|
+
- 要有具体的代码文件路径、类名、模式名称等细节
|
|
1281
|
+
- 关键发现用列表项罗列
|
|
1282
|
+
- 如果发现了架构模式或最佳实践,用简短代码块举例
|
|
1283
|
+
- 语言自然流畅,像一份技术分析报告`;
|
|
1284
|
+
systemPrompt =
|
|
1285
|
+
'你是项目分析助手。请用纯 Markdown 格式输出结构清晰的分析总结,只输出人类可读的自然语言文档,不要输出 JSON 格式的数据。';
|
|
1286
|
+
}
|
|
997
1287
|
|
|
998
1288
|
// 用空 messages 避免累积上下文导致 400
|
|
999
|
-
const summaryResult = await this.#aiProvider.chatWithTools(
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
maxTokens: 8192,
|
|
1008
|
-
},
|
|
1009
|
-
);
|
|
1289
|
+
const summaryResult = await this.#aiProvider.chatWithTools(summaryPrompt, {
|
|
1290
|
+
messages: [],
|
|
1291
|
+
toolSchemas,
|
|
1292
|
+
toolChoice: 'none',
|
|
1293
|
+
systemPrompt,
|
|
1294
|
+
temperature: isSystem ? 0.3 : 0.5,
|
|
1295
|
+
maxTokens: 8192,
|
|
1296
|
+
});
|
|
1010
1297
|
// 累计 token 用量
|
|
1011
1298
|
if (summaryResult.usage) {
|
|
1012
1299
|
this.#currentTokenUsage.input += summaryResult.usage.inputTokens || 0;
|
|
@@ -1015,11 +1302,13 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1015
1302
|
finalReply = this.#cleanFinalAnswer(summaryResult.text || '');
|
|
1016
1303
|
} catch (err) {
|
|
1017
1304
|
this.#logger.warn(`[ChatAgent] forced summary AI call failed: ${err.message}`);
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1305
|
+
|
|
1306
|
+
if (isSystem) {
|
|
1307
|
+
// ── system 源兜底: 合成 dimensionDigest JSON ──
|
|
1308
|
+
const titles = toolCalls
|
|
1309
|
+
.filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
1310
|
+
.map((tc) => tc.params?.title || 'untitled');
|
|
1311
|
+
finalReply = `\`\`\`json
|
|
1023
1312
|
{
|
|
1024
1313
|
"dimensionDigest": {
|
|
1025
1314
|
"summary": "通过 ${toolCalls.length} 次工具调用分析了项目代码,提交了 ${candidateCount} 个候选。",
|
|
@@ -1030,13 +1319,136 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1030
1319
|
}
|
|
1031
1320
|
}
|
|
1032
1321
|
\`\`\``;
|
|
1322
|
+
} else {
|
|
1323
|
+
// ── user 源兜底: 合成人类可读的 Markdown 摘要 ──
|
|
1324
|
+
const toolNames = [...new Set(toolCalls.map((tc) => tc.tool))];
|
|
1325
|
+
const filesRead = toolCalls
|
|
1326
|
+
.filter((tc) => tc.tool === 'read_project_file')
|
|
1327
|
+
.flatMap((tc) => {
|
|
1328
|
+
if (tc.params?.filePaths) {
|
|
1329
|
+
return tc.params.filePaths;
|
|
1330
|
+
}
|
|
1331
|
+
if (tc.params?.filePath) {
|
|
1332
|
+
return [tc.params.filePath];
|
|
1333
|
+
}
|
|
1334
|
+
return [];
|
|
1335
|
+
})
|
|
1336
|
+
.slice(0, 10);
|
|
1337
|
+
const searches = toolCalls
|
|
1338
|
+
.filter((tc) => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code')
|
|
1339
|
+
.map((tc) => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
|
|
1340
|
+
.filter(Boolean)
|
|
1341
|
+
.slice(0, 5);
|
|
1342
|
+
|
|
1343
|
+
finalReply = `## 分析总结\n\n通过 **${toolCalls.length} 次工具调用**探索了项目代码。\n\n`;
|
|
1344
|
+
if (searches.length > 0) {
|
|
1345
|
+
finalReply += `### 搜索的关键词\n${searches.map((s) => `- \`${s}\``).join('\n')}\n\n`;
|
|
1346
|
+
}
|
|
1347
|
+
if (filesRead.length > 0) {
|
|
1348
|
+
finalReply += `### 读取的文件\n${filesRead.map((f) => `- \`${f}\``).join('\n')}\n\n`;
|
|
1349
|
+
}
|
|
1350
|
+
finalReply += `### 使用的工具\n${toolNames.map((t) => `- ${t}`).join('\n')}\n\n`;
|
|
1351
|
+
finalReply += `> ⚠️ AI 服务异常,未能生成完整分析。请稍后重试或缩小分析范围。`;
|
|
1352
|
+
}
|
|
1033
1353
|
}
|
|
1034
1354
|
|
|
1035
1355
|
const totalDuration = Date.now() - execStartTime;
|
|
1036
|
-
this.#logger.info(
|
|
1356
|
+
this.#logger.info(
|
|
1357
|
+
`[ChatAgent] ✅ forced summary — ${finalReply.length} chars, ${totalDuration}ms total`
|
|
1358
|
+
);
|
|
1037
1359
|
return { reply: finalReply, toolCalls, hasContext: toolCalls.length > 0 };
|
|
1038
1360
|
}
|
|
1039
1361
|
|
|
1362
|
+
/**
|
|
1363
|
+
* 从工具调用记录中提取上下文摘要(供 user 源强制总结使用)
|
|
1364
|
+
* @private
|
|
1365
|
+
*/
|
|
1366
|
+
#buildToolContextForUserSummary(toolCalls) {
|
|
1367
|
+
const sections = [];
|
|
1368
|
+
|
|
1369
|
+
// 目录结构探索
|
|
1370
|
+
const structureCalls = toolCalls.filter((tc) => tc.tool === 'list_project_structure');
|
|
1371
|
+
if (structureCalls.length > 0) {
|
|
1372
|
+
const dirs = structureCalls.map((tc) => tc.params?.directory || '/').slice(0, 5);
|
|
1373
|
+
sections.push(`**目录探索**: ${dirs.map((d) => `\`${d}\``).join(', ')}`);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// 项目概况
|
|
1377
|
+
const overviewCalls = toolCalls.filter((tc) => tc.tool === 'get_project_overview');
|
|
1378
|
+
if (overviewCalls.length > 0) {
|
|
1379
|
+
sections.push('**项目概况**: 已获取');
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// 代码搜索
|
|
1383
|
+
const searchCalls = toolCalls.filter(
|
|
1384
|
+
(tc) => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code'
|
|
1385
|
+
);
|
|
1386
|
+
if (searchCalls.length > 0) {
|
|
1387
|
+
const queries = searchCalls
|
|
1388
|
+
.map((tc) => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
|
|
1389
|
+
.filter(Boolean)
|
|
1390
|
+
.slice(0, 8);
|
|
1391
|
+
sections.push(
|
|
1392
|
+
`**代码搜索** (${searchCalls.length} 次): ${queries.map((q) => `\`${q}\``).join(', ')}`
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// 文件读取
|
|
1397
|
+
const readCalls = toolCalls.filter((tc) => tc.tool === 'read_project_file');
|
|
1398
|
+
if (readCalls.length > 0) {
|
|
1399
|
+
const files = readCalls
|
|
1400
|
+
.flatMap((tc) => {
|
|
1401
|
+
if (tc.params?.filePaths) {
|
|
1402
|
+
return tc.params.filePaths;
|
|
1403
|
+
}
|
|
1404
|
+
if (tc.params?.filePath) {
|
|
1405
|
+
return [tc.params.filePath];
|
|
1406
|
+
}
|
|
1407
|
+
return [];
|
|
1408
|
+
})
|
|
1409
|
+
.slice(0, 10);
|
|
1410
|
+
sections.push(
|
|
1411
|
+
`**文件读取** (${readCalls.length} 次): ${files.map((f) => `\`${f}\``).join(', ')}`
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// AST 分析
|
|
1416
|
+
const astCalls = toolCalls.filter((tc) =>
|
|
1417
|
+
[
|
|
1418
|
+
'get_class_hierarchy',
|
|
1419
|
+
'get_class_info',
|
|
1420
|
+
'get_protocol_info',
|
|
1421
|
+
'get_method_overrides',
|
|
1422
|
+
'get_category_map',
|
|
1423
|
+
].includes(tc.tool)
|
|
1424
|
+
);
|
|
1425
|
+
if (astCalls.length > 0) {
|
|
1426
|
+
const entities = astCalls
|
|
1427
|
+
.map(
|
|
1428
|
+
(tc) =>
|
|
1429
|
+
tc.params?.className ||
|
|
1430
|
+
tc.params?.name ||
|
|
1431
|
+
tc.params?.protocolName ||
|
|
1432
|
+
tc.params?.rootClass
|
|
1433
|
+
)
|
|
1434
|
+
.filter(Boolean)
|
|
1435
|
+
.slice(0, 5);
|
|
1436
|
+
sections.push(
|
|
1437
|
+
`**AST 结构分析** (${astCalls.length} 次): ${entities.map((e) => `\`${e}\``).join(', ')}`
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// 知识库搜索
|
|
1442
|
+
const kbCalls = toolCalls.filter((tc) =>
|
|
1443
|
+
['search_knowledge', 'search_recipes', 'knowledge_overview'].includes(tc.tool)
|
|
1444
|
+
);
|
|
1445
|
+
if (kbCalls.length > 0) {
|
|
1446
|
+
sections.push(`**知识库查询**: ${kbCalls.length} 次`);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
return sections.length > 0 ? sections.join('\n') : '(工具调用记录为空)';
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1040
1452
|
// ─── Text Parsing 已移除 (v5.0) ────────────────────────
|
|
1041
1453
|
// 所有 Provider 统一走 chatWithTools() 原生函数调用路径。
|
|
1042
1454
|
// 不支持 native tool calling 的 Provider 在基类 chatWithTools()
|
|
@@ -1064,7 +1476,9 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1064
1476
|
* @returns {string} conversationId
|
|
1065
1477
|
*/
|
|
1066
1478
|
createConversation({ category = 'user', title = '' } = {}) {
|
|
1067
|
-
if (!this.#conversations)
|
|
1479
|
+
if (!this.#conversations) {
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1068
1482
|
return this.#conversations.create({ category, title });
|
|
1069
1483
|
}
|
|
1070
1484
|
|
|
@@ -1076,7 +1490,9 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1076
1490
|
* @returns {Array}
|
|
1077
1491
|
*/
|
|
1078
1492
|
getConversations({ category, limit = 20 } = {}) {
|
|
1079
|
-
if (!this.#conversations)
|
|
1493
|
+
if (!this.#conversations) {
|
|
1494
|
+
return [];
|
|
1495
|
+
}
|
|
1080
1496
|
return this.#conversations.list({ category, limit });
|
|
1081
1497
|
}
|
|
1082
1498
|
|
|
@@ -1100,12 +1516,18 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1100
1516
|
}
|
|
1101
1517
|
// 降级到硬编码任务(复杂交互逻辑无法用 DAG 表达的场景)
|
|
1102
1518
|
switch (taskName) {
|
|
1103
|
-
case 'check_and_submit':
|
|
1104
|
-
|
|
1105
|
-
case '
|
|
1106
|
-
|
|
1107
|
-
case '
|
|
1108
|
-
|
|
1519
|
+
case 'check_and_submit':
|
|
1520
|
+
return this.#taskCheckAndSubmit(params);
|
|
1521
|
+
case 'discover_all_relations':
|
|
1522
|
+
return this.#taskDiscoverAllRelations(params);
|
|
1523
|
+
case 'full_enrich':
|
|
1524
|
+
return this.#taskFullEnrich(params);
|
|
1525
|
+
case 'quality_audit':
|
|
1526
|
+
return this.#taskQualityAudit(params);
|
|
1527
|
+
case 'guard_full_scan':
|
|
1528
|
+
return this.#taskGuardFullScan(params);
|
|
1529
|
+
default:
|
|
1530
|
+
throw new Error(`Unknown task: ${taskName}`);
|
|
1109
1531
|
}
|
|
1110
1532
|
}
|
|
1111
1533
|
|
|
@@ -1131,7 +1553,9 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1131
1553
|
*/
|
|
1132
1554
|
async runPipeline(pipelineId, inputs = {}) {
|
|
1133
1555
|
const pipeline = this.#pipelines.get(pipelineId);
|
|
1134
|
-
if (!pipeline)
|
|
1556
|
+
if (!pipeline) {
|
|
1557
|
+
throw new Error(`Pipeline '${pipelineId}' not found`);
|
|
1558
|
+
}
|
|
1135
1559
|
const executor = (toolName, params) => this.executeTool(toolName, params);
|
|
1136
1560
|
return pipeline.execute(executor, inputs);
|
|
1137
1561
|
}
|
|
@@ -1140,7 +1564,7 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1140
1564
|
* 获取已注册的管线列表
|
|
1141
1565
|
*/
|
|
1142
1566
|
getPipelines() {
|
|
1143
|
-
return [...this.#pipelines.values()].map(p => p.describe());
|
|
1567
|
+
return [...this.#pipelines.values()].map((p) => p.describe());
|
|
1144
1568
|
}
|
|
1145
1569
|
|
|
1146
1570
|
/**
|
|
@@ -1155,7 +1579,11 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1155
1579
|
{ name: 'full_enrich', description: '批量 AI 语义补全候选字段' },
|
|
1156
1580
|
{ name: 'quality_audit', description: '批量质量审计全部 Recipe,标记低分项' },
|
|
1157
1581
|
{ name: 'guard_full_scan', description: '用全部 Guard 规则扫描指定代码,生成完整报告' },
|
|
1158
|
-
{
|
|
1582
|
+
{
|
|
1583
|
+
name: 'bootstrap_full_pipeline',
|
|
1584
|
+
description:
|
|
1585
|
+
'冷启动全流程 DAG: bootstrap(纯启发式) → enrich(AI结构补齐) + loadSkill(并行) → refine(AI内容润色)',
|
|
1586
|
+
},
|
|
1159
1587
|
],
|
|
1160
1588
|
pipelines: this.getPipelines(),
|
|
1161
1589
|
};
|
|
@@ -1177,7 +1605,7 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1177
1605
|
});
|
|
1178
1606
|
|
|
1179
1607
|
// Step 2: 如果有高相似度,使用 AI 分析是否真正重复
|
|
1180
|
-
const highSim = (duplicates.similar || []).filter(d => d.similarity >= 0.7);
|
|
1608
|
+
const highSim = (duplicates.similar || []).filter((d) => d.similarity >= 0.7);
|
|
1181
1609
|
let aiVerdict = null;
|
|
1182
1610
|
if (highSim.length > 0 && this.#aiProvider) {
|
|
1183
1611
|
const verdictPrompt = `以下新候选代码与已有 Recipe 高度相似,请判断是否真正重复。
|
|
@@ -1187,23 +1615,28 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
1187
1615
|
- Code: ${(candidate.code || '').substring(0, 1000)}
|
|
1188
1616
|
|
|
1189
1617
|
相似 Recipe:
|
|
1190
|
-
${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
1618
|
+
${highSim.map((s) => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
1191
1619
|
|
|
1192
1620
|
请回答: DUPLICATE(真正重复)/ SIMILAR(相似但不同,建议保留并标注关系)/ UNIQUE(误判,可放心提交)
|
|
1193
1621
|
只回答一个词。`;
|
|
1194
1622
|
try {
|
|
1195
1623
|
const raw = await this.#aiProvider.chat(verdictPrompt, { temperature: 0, maxTokens: 20 });
|
|
1196
1624
|
aiVerdict = (raw || '').trim().toUpperCase().split(/\s/)[0];
|
|
1197
|
-
} catch {
|
|
1625
|
+
} catch {
|
|
1626
|
+
/* ignore */
|
|
1627
|
+
}
|
|
1198
1628
|
}
|
|
1199
1629
|
|
|
1200
1630
|
return {
|
|
1201
1631
|
duplicates: duplicates.similar || [],
|
|
1202
1632
|
highSimilarity: highSim,
|
|
1203
1633
|
aiVerdict,
|
|
1204
|
-
recommendation:
|
|
1205
|
-
|
|
1206
|
-
|
|
1634
|
+
recommendation:
|
|
1635
|
+
highSim.length === 0
|
|
1636
|
+
? 'safe_to_submit'
|
|
1637
|
+
: aiVerdict === 'DUPLICATE'
|
|
1638
|
+
? 'block_duplicate'
|
|
1639
|
+
: 'review_suggested',
|
|
1207
1640
|
};
|
|
1208
1641
|
}
|
|
1209
1642
|
|
|
@@ -1214,14 +1647,27 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1214
1647
|
async #taskDiscoverAllRelations({ batchSize = 20 } = {}) {
|
|
1215
1648
|
const ctx = this.#getToolContext();
|
|
1216
1649
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1217
|
-
if (!knowledgeService)
|
|
1650
|
+
if (!knowledgeService) {
|
|
1651
|
+
throw new Error('KnowledgeService 不可用');
|
|
1652
|
+
}
|
|
1218
1653
|
|
|
1219
|
-
if (!ctx.aiProvider)
|
|
1654
|
+
if (!ctx.aiProvider) {
|
|
1655
|
+
throw new Error('AI Provider 未配置,请先设置 API Key');
|
|
1656
|
+
}
|
|
1220
1657
|
|
|
1221
1658
|
// 获取所有活跃知识条目
|
|
1222
|
-
const { items = [], data = [] } = await knowledgeService.list(
|
|
1659
|
+
const { items = [], data = [] } = await knowledgeService.list(
|
|
1660
|
+
{ lifecycle: 'active' },
|
|
1661
|
+
{ page: 1, pageSize: 500 }
|
|
1662
|
+
);
|
|
1223
1663
|
const recipes = items.length > 0 ? items : data;
|
|
1224
|
-
if (recipes.length < 2)
|
|
1664
|
+
if (recipes.length < 2) {
|
|
1665
|
+
return {
|
|
1666
|
+
discovered: 0,
|
|
1667
|
+
totalPairs: 0,
|
|
1668
|
+
message: `只有 ${recipes.length} 条 Recipe,至少需要 2 条`,
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1225
1671
|
|
|
1226
1672
|
// 按 batch 分组分析
|
|
1227
1673
|
const pairs = [];
|
|
@@ -1241,14 +1687,28 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1241
1687
|
try {
|
|
1242
1688
|
const result = await this.executeTool('discover_relations', {
|
|
1243
1689
|
recipePairs: batch.map(([a, b]) => ({
|
|
1244
|
-
a: {
|
|
1245
|
-
|
|
1690
|
+
a: {
|
|
1691
|
+
id: a.id,
|
|
1692
|
+
title: a.title,
|
|
1693
|
+
category: a.category,
|
|
1694
|
+
language: a.language,
|
|
1695
|
+
code: String(a.content || a.code || '').substring(0, 500),
|
|
1696
|
+
},
|
|
1697
|
+
b: {
|
|
1698
|
+
id: b.id,
|
|
1699
|
+
title: b.title,
|
|
1700
|
+
category: b.category,
|
|
1701
|
+
language: b.language,
|
|
1702
|
+
code: String(b.content || b.code || '').substring(0, 500),
|
|
1703
|
+
},
|
|
1246
1704
|
})),
|
|
1247
1705
|
});
|
|
1248
1706
|
|
|
1249
1707
|
if (result.error) {
|
|
1250
1708
|
batchErrors++;
|
|
1251
|
-
this.#logger.warn(
|
|
1709
|
+
this.#logger.warn(
|
|
1710
|
+
`[DiscoverRelations] Batch ${Math.floor(b / batchSize) + 1} error: ${result.error}`
|
|
1711
|
+
);
|
|
1252
1712
|
continue;
|
|
1253
1713
|
}
|
|
1254
1714
|
if (result.relations) {
|
|
@@ -1257,7 +1717,9 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1257
1717
|
}
|
|
1258
1718
|
} catch (err) {
|
|
1259
1719
|
batchErrors++;
|
|
1260
|
-
this.#logger.warn(
|
|
1720
|
+
this.#logger.warn(
|
|
1721
|
+
`[DiscoverRelations] Batch ${Math.floor(b / batchSize) + 1} threw: ${err.message}`
|
|
1722
|
+
);
|
|
1261
1723
|
}
|
|
1262
1724
|
}
|
|
1263
1725
|
|
|
@@ -1278,21 +1740,26 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1278
1740
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1279
1741
|
|
|
1280
1742
|
const { items = [], data = [] } = await knowledgeService.list(
|
|
1281
|
-
{ lifecycle: status },
|
|
1743
|
+
{ lifecycle: status },
|
|
1744
|
+
{ page: 1, pageSize: maxCount }
|
|
1282
1745
|
);
|
|
1283
1746
|
const candidates = items.length > 0 ? items : data;
|
|
1284
|
-
if (candidates.length === 0)
|
|
1747
|
+
if (candidates.length === 0) {
|
|
1748
|
+
return { enriched: 0, message: 'No candidates to enrich' };
|
|
1749
|
+
}
|
|
1285
1750
|
|
|
1286
1751
|
// 筛选缺失语义字段的候选
|
|
1287
|
-
const needEnrich = candidates.filter(c => {
|
|
1752
|
+
const needEnrich = candidates.filter((c) => {
|
|
1288
1753
|
const m = c.metadata || {};
|
|
1289
1754
|
return !m.rationale || !m.knowledgeType || !m.complexity;
|
|
1290
1755
|
});
|
|
1291
1756
|
|
|
1292
|
-
if (needEnrich.length === 0)
|
|
1757
|
+
if (needEnrich.length === 0) {
|
|
1758
|
+
return { enriched: 0, message: 'All candidates already enriched' };
|
|
1759
|
+
}
|
|
1293
1760
|
|
|
1294
1761
|
const result = await this.executeTool('enrich_candidate', {
|
|
1295
|
-
candidateIds: needEnrich.map(c => c.id).slice(0, 20),
|
|
1762
|
+
candidateIds: needEnrich.map((c) => c.id).slice(0, 20),
|
|
1296
1763
|
});
|
|
1297
1764
|
|
|
1298
1765
|
return result;
|
|
@@ -1307,17 +1774,22 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1307
1774
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1308
1775
|
|
|
1309
1776
|
const { items = [], data = [] } = await knowledgeService.list(
|
|
1310
|
-
{ lifecycle: 'active' },
|
|
1777
|
+
{ lifecycle: 'active' },
|
|
1778
|
+
{ page: 1, pageSize: maxCount }
|
|
1311
1779
|
);
|
|
1312
1780
|
const recipes = items.length > 0 ? items : data;
|
|
1313
|
-
if (recipes.length === 0)
|
|
1781
|
+
if (recipes.length === 0) {
|
|
1782
|
+
return { total: 0, lowQuality: [], message: 'No active recipes' };
|
|
1783
|
+
}
|
|
1314
1784
|
|
|
1315
1785
|
const lowQuality = [];
|
|
1316
1786
|
const gradeDistribution = { A: 0, B: 0, C: 0, D: 0, F: 0 };
|
|
1317
1787
|
|
|
1318
1788
|
for (const recipe of recipes) {
|
|
1319
1789
|
const scoreResult = await this.executeTool('quality_score', { recipe });
|
|
1320
|
-
if (scoreResult.grade)
|
|
1790
|
+
if (scoreResult.grade) {
|
|
1791
|
+
gradeDistribution[scoreResult.grade] = (gradeDistribution[scoreResult.grade] || 0) + 1;
|
|
1792
|
+
}
|
|
1321
1793
|
if (scoreResult.score < threshold) {
|
|
1322
1794
|
lowQuality.push({
|
|
1323
1795
|
id: recipe.id,
|
|
@@ -1345,11 +1817,15 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1345
1817
|
* 对代码运行全部 Guard 规则 + 生成修复建议
|
|
1346
1818
|
*/
|
|
1347
1819
|
async #taskGuardFullScan({ code, language, filePath } = {}) {
|
|
1348
|
-
if (!code)
|
|
1820
|
+
if (!code) {
|
|
1821
|
+
return { error: 'code is required' };
|
|
1822
|
+
}
|
|
1349
1823
|
|
|
1350
1824
|
// Step 1: 静态检查
|
|
1351
1825
|
const checkResult = await this.executeTool('guard_check_code', {
|
|
1352
|
-
code,
|
|
1826
|
+
code,
|
|
1827
|
+
language: language || 'unknown',
|
|
1828
|
+
scope: 'project',
|
|
1353
1829
|
});
|
|
1354
1830
|
|
|
1355
1831
|
// Step 2: 如果有违规且 AI 可用,生成修复建议
|
|
@@ -1358,7 +1834,10 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1358
1834
|
try {
|
|
1359
1835
|
const violationSummary = (checkResult.violations || [])
|
|
1360
1836
|
.slice(0, 5)
|
|
1361
|
-
.map(
|
|
1837
|
+
.map(
|
|
1838
|
+
(v) =>
|
|
1839
|
+
`- [${v.severity}] ${v.message || v.ruleName} (line ${v.line || v.matches?.[0]?.line || '?'})`
|
|
1840
|
+
)
|
|
1362
1841
|
.join('\n');
|
|
1363
1842
|
|
|
1364
1843
|
const prompt = `以下代码存在 Guard 规则违规。请为每个违规提供修复建议。
|
|
@@ -1373,10 +1852,15 @@ ${code.substring(0, 3000)}
|
|
|
1373
1852
|
|
|
1374
1853
|
请用 JSON 数组格式返回建议: [{"violation": "...", "suggestion": "...", "fixExample": "..."}]`;
|
|
1375
1854
|
|
|
1376
|
-
suggestions =
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1855
|
+
suggestions =
|
|
1856
|
+
(await this.#aiProvider.chatWithStructuredOutput(prompt, {
|
|
1857
|
+
openChar: '[',
|
|
1858
|
+
closeChar: ']',
|
|
1859
|
+
temperature: 0.3,
|
|
1860
|
+
})) || [];
|
|
1861
|
+
} catch {
|
|
1862
|
+
/* AI suggestions optional */
|
|
1863
|
+
}
|
|
1380
1864
|
}
|
|
1381
1865
|
|
|
1382
1866
|
return {
|
|
@@ -1403,18 +1887,20 @@ ${code.substring(0, 3000)}
|
|
|
1403
1887
|
// ── bootstrap_full_pipeline (v6 简化版) ──────────────────
|
|
1404
1888
|
// 只做启发式 Phase 1-5.5 (含 ChatAgent per-dimension production)
|
|
1405
1889
|
// 不再需要 enrich/refine 后置步骤
|
|
1406
|
-
this.registerPipeline(
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1890
|
+
this.registerPipeline(
|
|
1891
|
+
new TaskPipeline('bootstrap_full_pipeline', [
|
|
1892
|
+
{
|
|
1893
|
+
name: 'bootstrap',
|
|
1894
|
+
tool: 'bootstrap_knowledge',
|
|
1895
|
+
params: {
|
|
1896
|
+
maxFiles: (ctx) => ctx._inputs.maxFiles || 500,
|
|
1897
|
+
skipGuard: (ctx) => ctx._inputs.skipGuard || false,
|
|
1898
|
+
contentMaxLines: (ctx) => ctx._inputs.contentMaxLines || 120,
|
|
1899
|
+
loadSkills: true,
|
|
1900
|
+
},
|
|
1415
1901
|
},
|
|
1416
|
-
|
|
1417
|
-
|
|
1902
|
+
])
|
|
1903
|
+
);
|
|
1418
1904
|
}
|
|
1419
1905
|
|
|
1420
1906
|
// ─── Native Tool Calling 内部方法 ──────────────────────
|
|
@@ -1434,9 +1920,11 @@ ${code.substring(0, 3000)}
|
|
|
1434
1920
|
let soulSection = '';
|
|
1435
1921
|
try {
|
|
1436
1922
|
if (fs.existsSync(SOUL_PATH)) {
|
|
1437
|
-
soulSection =
|
|
1923
|
+
soulSection = `\n${fs.readFileSync(SOUL_PATH, 'utf-8').trim()}\n`;
|
|
1438
1924
|
}
|
|
1439
|
-
} catch {
|
|
1925
|
+
} catch {
|
|
1926
|
+
/* SOUL.md not available */
|
|
1927
|
+
}
|
|
1440
1928
|
|
|
1441
1929
|
return `${soulSection}
|
|
1442
1930
|
你是 AutoSnippet 项目的统一 AI 中心。项目内所有 AI 推理和分析都通过你执行。
|
|
@@ -1446,8 +1934,7 @@ ${this.#projectBriefingCache}${this.#memory?.toPromptSection({ source: 'user' })
|
|
|
1446
1934
|
1. 当需要查询数据时,直接调用相应工具。
|
|
1447
1935
|
2. 工具参数严格按照工具声明中的 schema 传递。
|
|
1448
1936
|
3. 对于代码分析任务,先 search_project_code 搜索,再 read_project_file 读取。
|
|
1449
|
-
4.
|
|
1450
|
-
5. 当工具返回错误时,尝试不同参数或方法。`;
|
|
1937
|
+
4. 当工具返回错误时,尝试不同参数或方法。`;
|
|
1451
1938
|
}
|
|
1452
1939
|
|
|
1453
1940
|
// Bootstrap 系统模式: LLM 以领域大脑的能力处理任务
|
|
@@ -1490,7 +1977,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1490
1977
|
* 清理最终回答(去除 Thought/preamble + MEMORY 标签)
|
|
1491
1978
|
*/
|
|
1492
1979
|
#cleanFinalAnswer(response) {
|
|
1493
|
-
if (!response)
|
|
1980
|
+
if (!response) {
|
|
1981
|
+
return '';
|
|
1982
|
+
}
|
|
1494
1983
|
return response
|
|
1495
1984
|
.replace(/^(Final Answer|最终回答|Answer)\s*[::]\s*/i, '')
|
|
1496
1985
|
.replace(/\[MEMORY:\w+\]\s*[\s\S]*?\s*\[\/MEMORY\]/g, '')
|
|
@@ -1537,9 +2026,10 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1537
2026
|
*/
|
|
1538
2027
|
#loadSkillsFromDir(dir, skillMap) {
|
|
1539
2028
|
try {
|
|
1540
|
-
const dirs = fs
|
|
1541
|
-
.
|
|
1542
|
-
.
|
|
2029
|
+
const dirs = fs
|
|
2030
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
2031
|
+
.filter((d) => d.isDirectory())
|
|
2032
|
+
.map((d) => d.name);
|
|
1543
2033
|
for (const name of dirs) {
|
|
1544
2034
|
const skillPath = path.join(dir, name, 'SKILL.md');
|
|
1545
2035
|
let summary = '';
|
|
@@ -1553,15 +2043,19 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1553
2043
|
for (const line of lines) {
|
|
1554
2044
|
const trimmed = line.trim();
|
|
1555
2045
|
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('---')) {
|
|
1556
|
-
summary = trimmed.length > 80 ? trimmed.substring(0, 80)
|
|
2046
|
+
summary = trimmed.length > 80 ? `${trimmed.substring(0, 80)}...` : trimmed;
|
|
1557
2047
|
break;
|
|
1558
2048
|
}
|
|
1559
2049
|
}
|
|
1560
2050
|
}
|
|
1561
|
-
} catch {
|
|
2051
|
+
} catch {
|
|
2052
|
+
/* SKILL.md not found */
|
|
2053
|
+
}
|
|
1562
2054
|
skillMap.set(name, { name, summary });
|
|
1563
2055
|
}
|
|
1564
|
-
} catch {
|
|
2056
|
+
} catch {
|
|
2057
|
+
/* directory not found */
|
|
2058
|
+
}
|
|
1565
2059
|
}
|
|
1566
2060
|
|
|
1567
2061
|
/**
|
|
@@ -1571,13 +2065,16 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1571
2065
|
async #buildProjectBriefing() {
|
|
1572
2066
|
try {
|
|
1573
2067
|
const db = this.#container?.get('database');
|
|
1574
|
-
if (!db)
|
|
2068
|
+
if (!db) {
|
|
2069
|
+
return '';
|
|
2070
|
+
}
|
|
1575
2071
|
// knowledgeType → kind 映射:
|
|
1576
2072
|
// rule: code-standard, code-style, best-practice, boundary-constraint
|
|
1577
2073
|
// pattern: code-pattern, architecture, solution
|
|
1578
2074
|
// fact: code-relation, inheritance, call-chain, data-flow, module-dependency
|
|
1579
2075
|
// V3: knowledge_entries 统一表(candidates 已合并,lifecycle 替代 status)
|
|
1580
|
-
const stats = db
|
|
2076
|
+
const stats = db
|
|
2077
|
+
.prepare(`
|
|
1581
2078
|
SELECT
|
|
1582
2079
|
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active') as recipeCount,
|
|
1583
2080
|
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledgeType IN ('code-standard','code-style','best-practice','boundary-constraint')) as ruleCount,
|
|
@@ -1586,7 +2083,8 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1586
2083
|
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledgeType = 'boundary-constraint') as guardRuleCount,
|
|
1587
2084
|
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'pending') as pendingCandidates,
|
|
1588
2085
|
(SELECT COUNT(*) FROM knowledge_entries) as totalCandidates
|
|
1589
|
-
`)
|
|
2086
|
+
`)
|
|
2087
|
+
.get();
|
|
1590
2088
|
if (!stats || stats.recipeCount === 0) {
|
|
1591
2089
|
return '\n## 项目状态\n⚠️ 知识库为空。建议先执行冷启动(bootstrap_knowledge)。\n';
|
|
1592
2090
|
}
|
|
@@ -1608,11 +2106,17 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1608
2106
|
try {
|
|
1609
2107
|
const db = container?.get?.('database');
|
|
1610
2108
|
if (db) {
|
|
1611
|
-
import('./ProjectSemanticMemory.js')
|
|
1612
|
-
|
|
1613
|
-
|
|
2109
|
+
import('./ProjectSemanticMemory.js')
|
|
2110
|
+
.then(({ ProjectSemanticMemory }) => {
|
|
2111
|
+
this.#semanticMemory = new ProjectSemanticMemory(db, { logger: this.#logger });
|
|
2112
|
+
})
|
|
2113
|
+
.catch(() => {
|
|
2114
|
+
/* Semantic Memory not available */
|
|
2115
|
+
});
|
|
1614
2116
|
}
|
|
1615
|
-
} catch {
|
|
2117
|
+
} catch {
|
|
2118
|
+
/* container.get failed, degrade silently */
|
|
2119
|
+
}
|
|
1616
2120
|
}
|
|
1617
2121
|
|
|
1618
2122
|
/**
|
|
@@ -1625,7 +2129,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1625
2129
|
* source 隔离: 标记 memory 来源,避免系统分析污染用户记忆
|
|
1626
2130
|
*/
|
|
1627
2131
|
#extractMemory(prompt, reply) {
|
|
1628
|
-
if (!this.#memory && !this.#semanticMemory)
|
|
2132
|
+
if (!this.#memory && !this.#semanticMemory) {
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
1629
2135
|
const source = this.#currentSource || 'user';
|
|
1630
2136
|
|
|
1631
2137
|
try {
|
|
@@ -1638,7 +2144,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1638
2144
|
/remember\s+(to|that)/i,
|
|
1639
2145
|
/our\s+(convention|standard|rule)\s+is/i,
|
|
1640
2146
|
];
|
|
1641
|
-
if (prefPatterns.some(p => p.test(prompt))) {
|
|
2147
|
+
if (prefPatterns.some((p) => p.test(prompt))) {
|
|
1642
2148
|
const entry = {
|
|
1643
2149
|
type: 'preference',
|
|
1644
2150
|
content: prompt.substring(0, 200),
|
|
@@ -1656,7 +2162,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1656
2162
|
/let'?s\s+(go\s+with|use|adopt)/i,
|
|
1657
2163
|
/approved|confirmed|decided/i,
|
|
1658
2164
|
];
|
|
1659
|
-
if (decisionPatterns.some(p => p.test(prompt))) {
|
|
2165
|
+
if (decisionPatterns.some((p) => p.test(prompt))) {
|
|
1660
2166
|
const entry = {
|
|
1661
2167
|
type: 'decision',
|
|
1662
2168
|
content: prompt.substring(0, 200),
|
|
@@ -1687,7 +2193,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1687
2193
|
}
|
|
1688
2194
|
}
|
|
1689
2195
|
}
|
|
1690
|
-
} catch {
|
|
2196
|
+
} catch {
|
|
2197
|
+
/* memory write failure is non-critical */
|
|
2198
|
+
}
|
|
1691
2199
|
}
|
|
1692
2200
|
|
|
1693
2201
|
/**
|
|
@@ -1695,7 +2203,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1695
2203
|
* 当对话消息数超过 12 条时触发 AI 摘要压缩
|
|
1696
2204
|
*/
|
|
1697
2205
|
async #autoSummarize(conversationId) {
|
|
1698
|
-
if (!this.#conversations || !this.#aiProvider)
|
|
2206
|
+
if (!this.#conversations || !this.#aiProvider) {
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
1699
2209
|
try {
|
|
1700
2210
|
const messages = this.#conversations.load(conversationId, { tokenBudget: Infinity });
|
|
1701
2211
|
if (messages.length >= 12) {
|
|
@@ -1735,17 +2245,23 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1735
2245
|
* 截断长文本
|
|
1736
2246
|
*/
|
|
1737
2247
|
#truncate(text, maxLen = 4000) {
|
|
1738
|
-
if (!text || text.length <= maxLen)
|
|
1739
|
-
|
|
2248
|
+
if (!text || text.length <= maxLen) {
|
|
2249
|
+
return text;
|
|
2250
|
+
}
|
|
2251
|
+
return `${text.substring(0, maxLen)}\n...(truncated, ${text.length - maxLen} chars omitted)`;
|
|
1740
2252
|
}
|
|
1741
2253
|
|
|
1742
2254
|
/**
|
|
1743
2255
|
* 精简工具结果(避免过长的 observation)
|
|
1744
2256
|
*/
|
|
1745
2257
|
#summarizeResult(result) {
|
|
1746
|
-
if (!result)
|
|
2258
|
+
if (!result) {
|
|
2259
|
+
return null;
|
|
2260
|
+
}
|
|
1747
2261
|
const str = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1748
|
-
if (str.length <= 500)
|
|
2262
|
+
if (str.length <= 500) {
|
|
2263
|
+
return result;
|
|
2264
|
+
}
|
|
1749
2265
|
// 返回截断版
|
|
1750
2266
|
if (typeof result === 'object') {
|
|
1751
2267
|
if (Array.isArray(result)) {
|
|
@@ -1757,7 +2273,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1757
2273
|
for (const k of keys) {
|
|
1758
2274
|
const v = result[k];
|
|
1759
2275
|
if (typeof v === 'string' && v.length > 200) {
|
|
1760
|
-
summary[k] = v.substring(0, 200)
|
|
2276
|
+
summary[k] = `${v.substring(0, 200)}...`;
|
|
1761
2277
|
} else if (Array.isArray(v)) {
|
|
1762
2278
|
summary[k] = { _count: v.length, first2: v.slice(0, 2) };
|
|
1763
2279
|
} else {
|