autosnippet 3.0.1 → 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 +655 -260
- 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,13 +1199,23 @@ 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';
|
|
956
|
-
this.#logger.info(
|
|
1213
|
+
this.#logger.info(
|
|
1214
|
+
`[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls, source=${source})`
|
|
1215
|
+
);
|
|
957
1216
|
|
|
958
|
-
const candidateCount = toolCalls.filter(
|
|
959
|
-
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'
|
|
960
1219
|
).length;
|
|
961
1220
|
|
|
962
1221
|
let finalReply;
|
|
@@ -964,12 +1223,14 @@ export class ChatAgent {
|
|
|
964
1223
|
// 如果熔断器已打开,跳过 AI 调用直接合成摘要(避免无用的失败 + 计数累积)
|
|
965
1224
|
const isCircuitOpen = this.#aiProvider._circuitState === 'OPEN';
|
|
966
1225
|
if (isCircuitOpen) {
|
|
967
|
-
this.#logger.warn(
|
|
1226
|
+
this.#logger.warn(
|
|
1227
|
+
`[ChatAgent] circuit breaker is OPEN — skipping AI summary, using synthetic ${isSystem ? 'digest' : 'summary'}`
|
|
1228
|
+
);
|
|
968
1229
|
}
|
|
969
1230
|
|
|
970
1231
|
// ── 收集工具调用摘要(user / system 共用) ──
|
|
971
1232
|
const submitSummary = toolCalls
|
|
972
|
-
.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
1233
|
+
.filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
973
1234
|
.map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
|
|
974
1235
|
.join('\n');
|
|
975
1236
|
|
|
@@ -977,7 +1238,9 @@ export class ChatAgent {
|
|
|
977
1238
|
const toolContextSummary = isSystem ? '' : this.#buildToolContextForUserSummary(toolCalls);
|
|
978
1239
|
|
|
979
1240
|
try {
|
|
980
|
-
if (isCircuitOpen)
|
|
1241
|
+
if (isCircuitOpen) {
|
|
1242
|
+
throw new Error('circuit open — skip to synthetic summary');
|
|
1243
|
+
}
|
|
981
1244
|
|
|
982
1245
|
let summaryPrompt;
|
|
983
1246
|
let systemPrompt;
|
|
@@ -1018,21 +1281,19 @@ ${toolContextSummary}
|
|
|
1018
1281
|
- 关键发现用列表项罗列
|
|
1019
1282
|
- 如果发现了架构模式或最佳实践,用简短代码块举例
|
|
1020
1283
|
- 语言自然流畅,像一份技术分析报告`;
|
|
1021
|
-
systemPrompt =
|
|
1284
|
+
systemPrompt =
|
|
1285
|
+
'你是项目分析助手。请用纯 Markdown 格式输出结构清晰的分析总结,只输出人类可读的自然语言文档,不要输出 JSON 格式的数据。';
|
|
1022
1286
|
}
|
|
1023
1287
|
|
|
1024
1288
|
// 用空 messages 避免累积上下文导致 400
|
|
1025
|
-
const summaryResult = await this.#aiProvider.chatWithTools(
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
maxTokens: 8192,
|
|
1034
|
-
},
|
|
1035
|
-
);
|
|
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
|
+
});
|
|
1036
1297
|
// 累计 token 用量
|
|
1037
1298
|
if (summaryResult.usage) {
|
|
1038
1299
|
this.#currentTokenUsage.input += summaryResult.usage.inputTokens || 0;
|
|
@@ -1045,8 +1306,8 @@ ${toolContextSummary}
|
|
|
1045
1306
|
if (isSystem) {
|
|
1046
1307
|
// ── system 源兜底: 合成 dimensionDigest JSON ──
|
|
1047
1308
|
const titles = toolCalls
|
|
1048
|
-
.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
1049
|
-
.map(tc => tc.params?.title || 'untitled');
|
|
1309
|
+
.filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
1310
|
+
.map((tc) => tc.params?.title || 'untitled');
|
|
1050
1311
|
finalReply = `\`\`\`json
|
|
1051
1312
|
{
|
|
1052
1313
|
"dimensionDigest": {
|
|
@@ -1060,35 +1321,41 @@ ${toolContextSummary}
|
|
|
1060
1321
|
\`\`\``;
|
|
1061
1322
|
} else {
|
|
1062
1323
|
// ── user 源兜底: 合成人类可读的 Markdown 摘要 ──
|
|
1063
|
-
const toolNames = [...new Set(toolCalls.map(tc => tc.tool))];
|
|
1324
|
+
const toolNames = [...new Set(toolCalls.map((tc) => tc.tool))];
|
|
1064
1325
|
const filesRead = toolCalls
|
|
1065
|
-
.filter(tc => tc.tool === 'read_project_file')
|
|
1066
|
-
.flatMap(tc => {
|
|
1067
|
-
if (tc.params?.filePaths)
|
|
1068
|
-
|
|
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
|
+
}
|
|
1069
1334
|
return [];
|
|
1070
1335
|
})
|
|
1071
1336
|
.slice(0, 10);
|
|
1072
1337
|
const searches = toolCalls
|
|
1073
|
-
.filter(tc => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code')
|
|
1074
|
-
.map(tc => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
|
|
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)
|
|
1075
1340
|
.filter(Boolean)
|
|
1076
1341
|
.slice(0, 5);
|
|
1077
1342
|
|
|
1078
1343
|
finalReply = `## 分析总结\n\n通过 **${toolCalls.length} 次工具调用**探索了项目代码。\n\n`;
|
|
1079
1344
|
if (searches.length > 0) {
|
|
1080
|
-
finalReply += `### 搜索的关键词\n${searches.map(s => `- \`${s}\``).join('\n')}\n\n`;
|
|
1345
|
+
finalReply += `### 搜索的关键词\n${searches.map((s) => `- \`${s}\``).join('\n')}\n\n`;
|
|
1081
1346
|
}
|
|
1082
1347
|
if (filesRead.length > 0) {
|
|
1083
|
-
finalReply += `### 读取的文件\n${filesRead.map(f => `- \`${f}\``).join('\n')}\n\n`;
|
|
1348
|
+
finalReply += `### 读取的文件\n${filesRead.map((f) => `- \`${f}\``).join('\n')}\n\n`;
|
|
1084
1349
|
}
|
|
1085
|
-
finalReply += `### 使用的工具\n${toolNames.map(t => `- ${t}`).join('\n')}\n\n`;
|
|
1350
|
+
finalReply += `### 使用的工具\n${toolNames.map((t) => `- ${t}`).join('\n')}\n\n`;
|
|
1086
1351
|
finalReply += `> ⚠️ AI 服务异常,未能生成完整分析。请稍后重试或缩小分析范围。`;
|
|
1087
1352
|
}
|
|
1088
1353
|
}
|
|
1089
1354
|
|
|
1090
1355
|
const totalDuration = Date.now() - execStartTime;
|
|
1091
|
-
this.#logger.info(
|
|
1356
|
+
this.#logger.info(
|
|
1357
|
+
`[ChatAgent] ✅ forced summary — ${finalReply.length} chars, ${totalDuration}ms total`
|
|
1358
|
+
);
|
|
1092
1359
|
return { reply: finalReply, toolCalls, hasContext: toolCalls.length > 0 };
|
|
1093
1360
|
}
|
|
1094
1361
|
|
|
@@ -1100,55 +1367,79 @@ ${toolContextSummary}
|
|
|
1100
1367
|
const sections = [];
|
|
1101
1368
|
|
|
1102
1369
|
// 目录结构探索
|
|
1103
|
-
const structureCalls = toolCalls.filter(tc => tc.tool === 'list_project_structure');
|
|
1370
|
+
const structureCalls = toolCalls.filter((tc) => tc.tool === 'list_project_structure');
|
|
1104
1371
|
if (structureCalls.length > 0) {
|
|
1105
|
-
const dirs = structureCalls.map(tc => tc.params?.directory || '/').slice(0, 5);
|
|
1106
|
-
sections.push(`**目录探索**: ${dirs.map(d => `\`${d}\``).join(', ')}`);
|
|
1372
|
+
const dirs = structureCalls.map((tc) => tc.params?.directory || '/').slice(0, 5);
|
|
1373
|
+
sections.push(`**目录探索**: ${dirs.map((d) => `\`${d}\``).join(', ')}`);
|
|
1107
1374
|
}
|
|
1108
1375
|
|
|
1109
1376
|
// 项目概况
|
|
1110
|
-
const overviewCalls = toolCalls.filter(tc => tc.tool === 'get_project_overview');
|
|
1377
|
+
const overviewCalls = toolCalls.filter((tc) => tc.tool === 'get_project_overview');
|
|
1111
1378
|
if (overviewCalls.length > 0) {
|
|
1112
1379
|
sections.push('**项目概况**: 已获取');
|
|
1113
1380
|
}
|
|
1114
1381
|
|
|
1115
1382
|
// 代码搜索
|
|
1116
|
-
const searchCalls = toolCalls.filter(
|
|
1383
|
+
const searchCalls = toolCalls.filter(
|
|
1384
|
+
(tc) => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code'
|
|
1385
|
+
);
|
|
1117
1386
|
if (searchCalls.length > 0) {
|
|
1118
1387
|
const queries = searchCalls
|
|
1119
|
-
.map(tc => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
|
|
1388
|
+
.map((tc) => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
|
|
1120
1389
|
.filter(Boolean)
|
|
1121
1390
|
.slice(0, 8);
|
|
1122
|
-
sections.push(
|
|
1391
|
+
sections.push(
|
|
1392
|
+
`**代码搜索** (${searchCalls.length} 次): ${queries.map((q) => `\`${q}\``).join(', ')}`
|
|
1393
|
+
);
|
|
1123
1394
|
}
|
|
1124
1395
|
|
|
1125
1396
|
// 文件读取
|
|
1126
|
-
const readCalls = toolCalls.filter(tc => tc.tool === 'read_project_file');
|
|
1397
|
+
const readCalls = toolCalls.filter((tc) => tc.tool === 'read_project_file');
|
|
1127
1398
|
if (readCalls.length > 0) {
|
|
1128
1399
|
const files = readCalls
|
|
1129
|
-
.flatMap(tc => {
|
|
1130
|
-
if (tc.params?.filePaths)
|
|
1131
|
-
|
|
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
|
+
}
|
|
1132
1407
|
return [];
|
|
1133
1408
|
})
|
|
1134
1409
|
.slice(0, 10);
|
|
1135
|
-
sections.push(
|
|
1410
|
+
sections.push(
|
|
1411
|
+
`**文件读取** (${readCalls.length} 次): ${files.map((f) => `\`${f}\``).join(', ')}`
|
|
1412
|
+
);
|
|
1136
1413
|
}
|
|
1137
1414
|
|
|
1138
1415
|
// AST 分析
|
|
1139
|
-
const astCalls = toolCalls.filter(tc =>
|
|
1140
|
-
[
|
|
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)
|
|
1141
1424
|
);
|
|
1142
1425
|
if (astCalls.length > 0) {
|
|
1143
1426
|
const entities = astCalls
|
|
1144
|
-
.map(
|
|
1427
|
+
.map(
|
|
1428
|
+
(tc) =>
|
|
1429
|
+
tc.params?.className ||
|
|
1430
|
+
tc.params?.name ||
|
|
1431
|
+
tc.params?.protocolName ||
|
|
1432
|
+
tc.params?.rootClass
|
|
1433
|
+
)
|
|
1145
1434
|
.filter(Boolean)
|
|
1146
1435
|
.slice(0, 5);
|
|
1147
|
-
sections.push(
|
|
1436
|
+
sections.push(
|
|
1437
|
+
`**AST 结构分析** (${astCalls.length} 次): ${entities.map((e) => `\`${e}\``).join(', ')}`
|
|
1438
|
+
);
|
|
1148
1439
|
}
|
|
1149
1440
|
|
|
1150
1441
|
// 知识库搜索
|
|
1151
|
-
const kbCalls = toolCalls.filter(tc =>
|
|
1442
|
+
const kbCalls = toolCalls.filter((tc) =>
|
|
1152
1443
|
['search_knowledge', 'search_recipes', 'knowledge_overview'].includes(tc.tool)
|
|
1153
1444
|
);
|
|
1154
1445
|
if (kbCalls.length > 0) {
|
|
@@ -1185,7 +1476,9 @@ ${toolContextSummary}
|
|
|
1185
1476
|
* @returns {string} conversationId
|
|
1186
1477
|
*/
|
|
1187
1478
|
createConversation({ category = 'user', title = '' } = {}) {
|
|
1188
|
-
if (!this.#conversations)
|
|
1479
|
+
if (!this.#conversations) {
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1189
1482
|
return this.#conversations.create({ category, title });
|
|
1190
1483
|
}
|
|
1191
1484
|
|
|
@@ -1197,7 +1490,9 @@ ${toolContextSummary}
|
|
|
1197
1490
|
* @returns {Array}
|
|
1198
1491
|
*/
|
|
1199
1492
|
getConversations({ category, limit = 20 } = {}) {
|
|
1200
|
-
if (!this.#conversations)
|
|
1493
|
+
if (!this.#conversations) {
|
|
1494
|
+
return [];
|
|
1495
|
+
}
|
|
1201
1496
|
return this.#conversations.list({ category, limit });
|
|
1202
1497
|
}
|
|
1203
1498
|
|
|
@@ -1221,12 +1516,18 @@ ${toolContextSummary}
|
|
|
1221
1516
|
}
|
|
1222
1517
|
// 降级到硬编码任务(复杂交互逻辑无法用 DAG 表达的场景)
|
|
1223
1518
|
switch (taskName) {
|
|
1224
|
-
case 'check_and_submit':
|
|
1225
|
-
|
|
1226
|
-
case '
|
|
1227
|
-
|
|
1228
|
-
case '
|
|
1229
|
-
|
|
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}`);
|
|
1230
1531
|
}
|
|
1231
1532
|
}
|
|
1232
1533
|
|
|
@@ -1252,7 +1553,9 @@ ${toolContextSummary}
|
|
|
1252
1553
|
*/
|
|
1253
1554
|
async runPipeline(pipelineId, inputs = {}) {
|
|
1254
1555
|
const pipeline = this.#pipelines.get(pipelineId);
|
|
1255
|
-
if (!pipeline)
|
|
1556
|
+
if (!pipeline) {
|
|
1557
|
+
throw new Error(`Pipeline '${pipelineId}' not found`);
|
|
1558
|
+
}
|
|
1256
1559
|
const executor = (toolName, params) => this.executeTool(toolName, params);
|
|
1257
1560
|
return pipeline.execute(executor, inputs);
|
|
1258
1561
|
}
|
|
@@ -1261,7 +1564,7 @@ ${toolContextSummary}
|
|
|
1261
1564
|
* 获取已注册的管线列表
|
|
1262
1565
|
*/
|
|
1263
1566
|
getPipelines() {
|
|
1264
|
-
return [...this.#pipelines.values()].map(p => p.describe());
|
|
1567
|
+
return [...this.#pipelines.values()].map((p) => p.describe());
|
|
1265
1568
|
}
|
|
1266
1569
|
|
|
1267
1570
|
/**
|
|
@@ -1276,7 +1579,11 @@ ${toolContextSummary}
|
|
|
1276
1579
|
{ name: 'full_enrich', description: '批量 AI 语义补全候选字段' },
|
|
1277
1580
|
{ name: 'quality_audit', description: '批量质量审计全部 Recipe,标记低分项' },
|
|
1278
1581
|
{ name: 'guard_full_scan', description: '用全部 Guard 规则扫描指定代码,生成完整报告' },
|
|
1279
|
-
{
|
|
1582
|
+
{
|
|
1583
|
+
name: 'bootstrap_full_pipeline',
|
|
1584
|
+
description:
|
|
1585
|
+
'冷启动全流程 DAG: bootstrap(纯启发式) → enrich(AI结构补齐) + loadSkill(并行) → refine(AI内容润色)',
|
|
1586
|
+
},
|
|
1280
1587
|
],
|
|
1281
1588
|
pipelines: this.getPipelines(),
|
|
1282
1589
|
};
|
|
@@ -1298,7 +1605,7 @@ ${toolContextSummary}
|
|
|
1298
1605
|
});
|
|
1299
1606
|
|
|
1300
1607
|
// Step 2: 如果有高相似度,使用 AI 分析是否真正重复
|
|
1301
|
-
const highSim = (duplicates.similar || []).filter(d => d.similarity >= 0.7);
|
|
1608
|
+
const highSim = (duplicates.similar || []).filter((d) => d.similarity >= 0.7);
|
|
1302
1609
|
let aiVerdict = null;
|
|
1303
1610
|
if (highSim.length > 0 && this.#aiProvider) {
|
|
1304
1611
|
const verdictPrompt = `以下新候选代码与已有 Recipe 高度相似,请判断是否真正重复。
|
|
@@ -1308,23 +1615,28 @@ ${toolContextSummary}
|
|
|
1308
1615
|
- Code: ${(candidate.code || '').substring(0, 1000)}
|
|
1309
1616
|
|
|
1310
1617
|
相似 Recipe:
|
|
1311
|
-
${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
1618
|
+
${highSim.map((s) => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
1312
1619
|
|
|
1313
1620
|
请回答: DUPLICATE(真正重复)/ SIMILAR(相似但不同,建议保留并标注关系)/ UNIQUE(误判,可放心提交)
|
|
1314
1621
|
只回答一个词。`;
|
|
1315
1622
|
try {
|
|
1316
1623
|
const raw = await this.#aiProvider.chat(verdictPrompt, { temperature: 0, maxTokens: 20 });
|
|
1317
1624
|
aiVerdict = (raw || '').trim().toUpperCase().split(/\s/)[0];
|
|
1318
|
-
} catch {
|
|
1625
|
+
} catch {
|
|
1626
|
+
/* ignore */
|
|
1627
|
+
}
|
|
1319
1628
|
}
|
|
1320
1629
|
|
|
1321
1630
|
return {
|
|
1322
1631
|
duplicates: duplicates.similar || [],
|
|
1323
1632
|
highSimilarity: highSim,
|
|
1324
1633
|
aiVerdict,
|
|
1325
|
-
recommendation:
|
|
1326
|
-
|
|
1327
|
-
|
|
1634
|
+
recommendation:
|
|
1635
|
+
highSim.length === 0
|
|
1636
|
+
? 'safe_to_submit'
|
|
1637
|
+
: aiVerdict === 'DUPLICATE'
|
|
1638
|
+
? 'block_duplicate'
|
|
1639
|
+
: 'review_suggested',
|
|
1328
1640
|
};
|
|
1329
1641
|
}
|
|
1330
1642
|
|
|
@@ -1335,14 +1647,27 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1335
1647
|
async #taskDiscoverAllRelations({ batchSize = 20 } = {}) {
|
|
1336
1648
|
const ctx = this.#getToolContext();
|
|
1337
1649
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1338
|
-
if (!knowledgeService)
|
|
1650
|
+
if (!knowledgeService) {
|
|
1651
|
+
throw new Error('KnowledgeService 不可用');
|
|
1652
|
+
}
|
|
1339
1653
|
|
|
1340
|
-
if (!ctx.aiProvider)
|
|
1654
|
+
if (!ctx.aiProvider) {
|
|
1655
|
+
throw new Error('AI Provider 未配置,请先设置 API Key');
|
|
1656
|
+
}
|
|
1341
1657
|
|
|
1342
1658
|
// 获取所有活跃知识条目
|
|
1343
|
-
const { items = [], data = [] } = await knowledgeService.list(
|
|
1659
|
+
const { items = [], data = [] } = await knowledgeService.list(
|
|
1660
|
+
{ lifecycle: 'active' },
|
|
1661
|
+
{ page: 1, pageSize: 500 }
|
|
1662
|
+
);
|
|
1344
1663
|
const recipes = items.length > 0 ? items : data;
|
|
1345
|
-
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
|
+
}
|
|
1346
1671
|
|
|
1347
1672
|
// 按 batch 分组分析
|
|
1348
1673
|
const pairs = [];
|
|
@@ -1362,14 +1687,28 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1362
1687
|
try {
|
|
1363
1688
|
const result = await this.executeTool('discover_relations', {
|
|
1364
1689
|
recipePairs: batch.map(([a, b]) => ({
|
|
1365
|
-
a: {
|
|
1366
|
-
|
|
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
|
+
},
|
|
1367
1704
|
})),
|
|
1368
1705
|
});
|
|
1369
1706
|
|
|
1370
1707
|
if (result.error) {
|
|
1371
1708
|
batchErrors++;
|
|
1372
|
-
this.#logger.warn(
|
|
1709
|
+
this.#logger.warn(
|
|
1710
|
+
`[DiscoverRelations] Batch ${Math.floor(b / batchSize) + 1} error: ${result.error}`
|
|
1711
|
+
);
|
|
1373
1712
|
continue;
|
|
1374
1713
|
}
|
|
1375
1714
|
if (result.relations) {
|
|
@@ -1378,7 +1717,9 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1378
1717
|
}
|
|
1379
1718
|
} catch (err) {
|
|
1380
1719
|
batchErrors++;
|
|
1381
|
-
this.#logger.warn(
|
|
1720
|
+
this.#logger.warn(
|
|
1721
|
+
`[DiscoverRelations] Batch ${Math.floor(b / batchSize) + 1} threw: ${err.message}`
|
|
1722
|
+
);
|
|
1382
1723
|
}
|
|
1383
1724
|
}
|
|
1384
1725
|
|
|
@@ -1399,21 +1740,26 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1399
1740
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1400
1741
|
|
|
1401
1742
|
const { items = [], data = [] } = await knowledgeService.list(
|
|
1402
|
-
{ lifecycle: status },
|
|
1743
|
+
{ lifecycle: status },
|
|
1744
|
+
{ page: 1, pageSize: maxCount }
|
|
1403
1745
|
);
|
|
1404
1746
|
const candidates = items.length > 0 ? items : data;
|
|
1405
|
-
if (candidates.length === 0)
|
|
1747
|
+
if (candidates.length === 0) {
|
|
1748
|
+
return { enriched: 0, message: 'No candidates to enrich' };
|
|
1749
|
+
}
|
|
1406
1750
|
|
|
1407
1751
|
// 筛选缺失语义字段的候选
|
|
1408
|
-
const needEnrich = candidates.filter(c => {
|
|
1752
|
+
const needEnrich = candidates.filter((c) => {
|
|
1409
1753
|
const m = c.metadata || {};
|
|
1410
1754
|
return !m.rationale || !m.knowledgeType || !m.complexity;
|
|
1411
1755
|
});
|
|
1412
1756
|
|
|
1413
|
-
if (needEnrich.length === 0)
|
|
1757
|
+
if (needEnrich.length === 0) {
|
|
1758
|
+
return { enriched: 0, message: 'All candidates already enriched' };
|
|
1759
|
+
}
|
|
1414
1760
|
|
|
1415
1761
|
const result = await this.executeTool('enrich_candidate', {
|
|
1416
|
-
candidateIds: needEnrich.map(c => c.id).slice(0, 20),
|
|
1762
|
+
candidateIds: needEnrich.map((c) => c.id).slice(0, 20),
|
|
1417
1763
|
});
|
|
1418
1764
|
|
|
1419
1765
|
return result;
|
|
@@ -1428,17 +1774,22 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1428
1774
|
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1429
1775
|
|
|
1430
1776
|
const { items = [], data = [] } = await knowledgeService.list(
|
|
1431
|
-
{ lifecycle: 'active' },
|
|
1777
|
+
{ lifecycle: 'active' },
|
|
1778
|
+
{ page: 1, pageSize: maxCount }
|
|
1432
1779
|
);
|
|
1433
1780
|
const recipes = items.length > 0 ? items : data;
|
|
1434
|
-
if (recipes.length === 0)
|
|
1781
|
+
if (recipes.length === 0) {
|
|
1782
|
+
return { total: 0, lowQuality: [], message: 'No active recipes' };
|
|
1783
|
+
}
|
|
1435
1784
|
|
|
1436
1785
|
const lowQuality = [];
|
|
1437
1786
|
const gradeDistribution = { A: 0, B: 0, C: 0, D: 0, F: 0 };
|
|
1438
1787
|
|
|
1439
1788
|
for (const recipe of recipes) {
|
|
1440
1789
|
const scoreResult = await this.executeTool('quality_score', { recipe });
|
|
1441
|
-
if (scoreResult.grade)
|
|
1790
|
+
if (scoreResult.grade) {
|
|
1791
|
+
gradeDistribution[scoreResult.grade] = (gradeDistribution[scoreResult.grade] || 0) + 1;
|
|
1792
|
+
}
|
|
1442
1793
|
if (scoreResult.score < threshold) {
|
|
1443
1794
|
lowQuality.push({
|
|
1444
1795
|
id: recipe.id,
|
|
@@ -1466,11 +1817,15 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1466
1817
|
* 对代码运行全部 Guard 规则 + 生成修复建议
|
|
1467
1818
|
*/
|
|
1468
1819
|
async #taskGuardFullScan({ code, language, filePath } = {}) {
|
|
1469
|
-
if (!code)
|
|
1820
|
+
if (!code) {
|
|
1821
|
+
return { error: 'code is required' };
|
|
1822
|
+
}
|
|
1470
1823
|
|
|
1471
1824
|
// Step 1: 静态检查
|
|
1472
1825
|
const checkResult = await this.executeTool('guard_check_code', {
|
|
1473
|
-
code,
|
|
1826
|
+
code,
|
|
1827
|
+
language: language || 'unknown',
|
|
1828
|
+
scope: 'project',
|
|
1474
1829
|
});
|
|
1475
1830
|
|
|
1476
1831
|
// Step 2: 如果有违规且 AI 可用,生成修复建议
|
|
@@ -1479,7 +1834,10 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1479
1834
|
try {
|
|
1480
1835
|
const violationSummary = (checkResult.violations || [])
|
|
1481
1836
|
.slice(0, 5)
|
|
1482
|
-
.map(
|
|
1837
|
+
.map(
|
|
1838
|
+
(v) =>
|
|
1839
|
+
`- [${v.severity}] ${v.message || v.ruleName} (line ${v.line || v.matches?.[0]?.line || '?'})`
|
|
1840
|
+
)
|
|
1483
1841
|
.join('\n');
|
|
1484
1842
|
|
|
1485
1843
|
const prompt = `以下代码存在 Guard 规则违规。请为每个违规提供修复建议。
|
|
@@ -1494,10 +1852,15 @@ ${code.substring(0, 3000)}
|
|
|
1494
1852
|
|
|
1495
1853
|
请用 JSON 数组格式返回建议: [{"violation": "...", "suggestion": "...", "fixExample": "..."}]`;
|
|
1496
1854
|
|
|
1497
|
-
suggestions =
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1855
|
+
suggestions =
|
|
1856
|
+
(await this.#aiProvider.chatWithStructuredOutput(prompt, {
|
|
1857
|
+
openChar: '[',
|
|
1858
|
+
closeChar: ']',
|
|
1859
|
+
temperature: 0.3,
|
|
1860
|
+
})) || [];
|
|
1861
|
+
} catch {
|
|
1862
|
+
/* AI suggestions optional */
|
|
1863
|
+
}
|
|
1501
1864
|
}
|
|
1502
1865
|
|
|
1503
1866
|
return {
|
|
@@ -1524,18 +1887,20 @@ ${code.substring(0, 3000)}
|
|
|
1524
1887
|
// ── bootstrap_full_pipeline (v6 简化版) ──────────────────
|
|
1525
1888
|
// 只做启发式 Phase 1-5.5 (含 ChatAgent per-dimension production)
|
|
1526
1889
|
// 不再需要 enrich/refine 后置步骤
|
|
1527
|
-
this.registerPipeline(
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
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
|
+
},
|
|
1536
1901
|
},
|
|
1537
|
-
|
|
1538
|
-
|
|
1902
|
+
])
|
|
1903
|
+
);
|
|
1539
1904
|
}
|
|
1540
1905
|
|
|
1541
1906
|
// ─── Native Tool Calling 内部方法 ──────────────────────
|
|
@@ -1555,9 +1920,11 @@ ${code.substring(0, 3000)}
|
|
|
1555
1920
|
let soulSection = '';
|
|
1556
1921
|
try {
|
|
1557
1922
|
if (fs.existsSync(SOUL_PATH)) {
|
|
1558
|
-
soulSection =
|
|
1923
|
+
soulSection = `\n${fs.readFileSync(SOUL_PATH, 'utf-8').trim()}\n`;
|
|
1559
1924
|
}
|
|
1560
|
-
} catch {
|
|
1925
|
+
} catch {
|
|
1926
|
+
/* SOUL.md not available */
|
|
1927
|
+
}
|
|
1561
1928
|
|
|
1562
1929
|
return `${soulSection}
|
|
1563
1930
|
你是 AutoSnippet 项目的统一 AI 中心。项目内所有 AI 推理和分析都通过你执行。
|
|
@@ -1567,8 +1934,7 @@ ${this.#projectBriefingCache}${this.#memory?.toPromptSection({ source: 'user' })
|
|
|
1567
1934
|
1. 当需要查询数据时,直接调用相应工具。
|
|
1568
1935
|
2. 工具参数严格按照工具声明中的 schema 传递。
|
|
1569
1936
|
3. 对于代码分析任务,先 search_project_code 搜索,再 read_project_file 读取。
|
|
1570
|
-
4.
|
|
1571
|
-
5. 当工具返回错误时,尝试不同参数或方法。`;
|
|
1937
|
+
4. 当工具返回错误时,尝试不同参数或方法。`;
|
|
1572
1938
|
}
|
|
1573
1939
|
|
|
1574
1940
|
// Bootstrap 系统模式: LLM 以领域大脑的能力处理任务
|
|
@@ -1611,7 +1977,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1611
1977
|
* 清理最终回答(去除 Thought/preamble + MEMORY 标签)
|
|
1612
1978
|
*/
|
|
1613
1979
|
#cleanFinalAnswer(response) {
|
|
1614
|
-
if (!response)
|
|
1980
|
+
if (!response) {
|
|
1981
|
+
return '';
|
|
1982
|
+
}
|
|
1615
1983
|
return response
|
|
1616
1984
|
.replace(/^(Final Answer|最终回答|Answer)\s*[::]\s*/i, '')
|
|
1617
1985
|
.replace(/\[MEMORY:\w+\]\s*[\s\S]*?\s*\[\/MEMORY\]/g, '')
|
|
@@ -1658,9 +2026,10 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1658
2026
|
*/
|
|
1659
2027
|
#loadSkillsFromDir(dir, skillMap) {
|
|
1660
2028
|
try {
|
|
1661
|
-
const dirs = fs
|
|
1662
|
-
.
|
|
1663
|
-
.
|
|
2029
|
+
const dirs = fs
|
|
2030
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
2031
|
+
.filter((d) => d.isDirectory())
|
|
2032
|
+
.map((d) => d.name);
|
|
1664
2033
|
for (const name of dirs) {
|
|
1665
2034
|
const skillPath = path.join(dir, name, 'SKILL.md');
|
|
1666
2035
|
let summary = '';
|
|
@@ -1674,15 +2043,19 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1674
2043
|
for (const line of lines) {
|
|
1675
2044
|
const trimmed = line.trim();
|
|
1676
2045
|
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('---')) {
|
|
1677
|
-
summary = trimmed.length > 80 ? trimmed.substring(0, 80)
|
|
2046
|
+
summary = trimmed.length > 80 ? `${trimmed.substring(0, 80)}...` : trimmed;
|
|
1678
2047
|
break;
|
|
1679
2048
|
}
|
|
1680
2049
|
}
|
|
1681
2050
|
}
|
|
1682
|
-
} catch {
|
|
2051
|
+
} catch {
|
|
2052
|
+
/* SKILL.md not found */
|
|
2053
|
+
}
|
|
1683
2054
|
skillMap.set(name, { name, summary });
|
|
1684
2055
|
}
|
|
1685
|
-
} catch {
|
|
2056
|
+
} catch {
|
|
2057
|
+
/* directory not found */
|
|
2058
|
+
}
|
|
1686
2059
|
}
|
|
1687
2060
|
|
|
1688
2061
|
/**
|
|
@@ -1692,13 +2065,16 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1692
2065
|
async #buildProjectBriefing() {
|
|
1693
2066
|
try {
|
|
1694
2067
|
const db = this.#container?.get('database');
|
|
1695
|
-
if (!db)
|
|
2068
|
+
if (!db) {
|
|
2069
|
+
return '';
|
|
2070
|
+
}
|
|
1696
2071
|
// knowledgeType → kind 映射:
|
|
1697
2072
|
// rule: code-standard, code-style, best-practice, boundary-constraint
|
|
1698
2073
|
// pattern: code-pattern, architecture, solution
|
|
1699
2074
|
// fact: code-relation, inheritance, call-chain, data-flow, module-dependency
|
|
1700
2075
|
// V3: knowledge_entries 统一表(candidates 已合并,lifecycle 替代 status)
|
|
1701
|
-
const stats = db
|
|
2076
|
+
const stats = db
|
|
2077
|
+
.prepare(`
|
|
1702
2078
|
SELECT
|
|
1703
2079
|
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active') as recipeCount,
|
|
1704
2080
|
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledgeType IN ('code-standard','code-style','best-practice','boundary-constraint')) as ruleCount,
|
|
@@ -1707,7 +2083,8 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1707
2083
|
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledgeType = 'boundary-constraint') as guardRuleCount,
|
|
1708
2084
|
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'pending') as pendingCandidates,
|
|
1709
2085
|
(SELECT COUNT(*) FROM knowledge_entries) as totalCandidates
|
|
1710
|
-
`)
|
|
2086
|
+
`)
|
|
2087
|
+
.get();
|
|
1711
2088
|
if (!stats || stats.recipeCount === 0) {
|
|
1712
2089
|
return '\n## 项目状态\n⚠️ 知识库为空。建议先执行冷启动(bootstrap_knowledge)。\n';
|
|
1713
2090
|
}
|
|
@@ -1729,11 +2106,17 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1729
2106
|
try {
|
|
1730
2107
|
const db = container?.get?.('database');
|
|
1731
2108
|
if (db) {
|
|
1732
|
-
import('./ProjectSemanticMemory.js')
|
|
1733
|
-
|
|
1734
|
-
|
|
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
|
+
});
|
|
1735
2116
|
}
|
|
1736
|
-
} catch {
|
|
2117
|
+
} catch {
|
|
2118
|
+
/* container.get failed, degrade silently */
|
|
2119
|
+
}
|
|
1737
2120
|
}
|
|
1738
2121
|
|
|
1739
2122
|
/**
|
|
@@ -1746,7 +2129,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1746
2129
|
* source 隔离: 标记 memory 来源,避免系统分析污染用户记忆
|
|
1747
2130
|
*/
|
|
1748
2131
|
#extractMemory(prompt, reply) {
|
|
1749
|
-
if (!this.#memory && !this.#semanticMemory)
|
|
2132
|
+
if (!this.#memory && !this.#semanticMemory) {
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
1750
2135
|
const source = this.#currentSource || 'user';
|
|
1751
2136
|
|
|
1752
2137
|
try {
|
|
@@ -1759,7 +2144,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1759
2144
|
/remember\s+(to|that)/i,
|
|
1760
2145
|
/our\s+(convention|standard|rule)\s+is/i,
|
|
1761
2146
|
];
|
|
1762
|
-
if (prefPatterns.some(p => p.test(prompt))) {
|
|
2147
|
+
if (prefPatterns.some((p) => p.test(prompt))) {
|
|
1763
2148
|
const entry = {
|
|
1764
2149
|
type: 'preference',
|
|
1765
2150
|
content: prompt.substring(0, 200),
|
|
@@ -1777,7 +2162,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1777
2162
|
/let'?s\s+(go\s+with|use|adopt)/i,
|
|
1778
2163
|
/approved|confirmed|decided/i,
|
|
1779
2164
|
];
|
|
1780
|
-
if (decisionPatterns.some(p => p.test(prompt))) {
|
|
2165
|
+
if (decisionPatterns.some((p) => p.test(prompt))) {
|
|
1781
2166
|
const entry = {
|
|
1782
2167
|
type: 'decision',
|
|
1783
2168
|
content: prompt.substring(0, 200),
|
|
@@ -1808,7 +2193,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1808
2193
|
}
|
|
1809
2194
|
}
|
|
1810
2195
|
}
|
|
1811
|
-
} catch {
|
|
2196
|
+
} catch {
|
|
2197
|
+
/* memory write failure is non-critical */
|
|
2198
|
+
}
|
|
1812
2199
|
}
|
|
1813
2200
|
|
|
1814
2201
|
/**
|
|
@@ -1816,7 +2203,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1816
2203
|
* 当对话消息数超过 12 条时触发 AI 摘要压缩
|
|
1817
2204
|
*/
|
|
1818
2205
|
async #autoSummarize(conversationId) {
|
|
1819
|
-
if (!this.#conversations || !this.#aiProvider)
|
|
2206
|
+
if (!this.#conversations || !this.#aiProvider) {
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
1820
2209
|
try {
|
|
1821
2210
|
const messages = this.#conversations.load(conversationId, { tokenBudget: Infinity });
|
|
1822
2211
|
if (messages.length >= 12) {
|
|
@@ -1856,17 +2245,23 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1856
2245
|
* 截断长文本
|
|
1857
2246
|
*/
|
|
1858
2247
|
#truncate(text, maxLen = 4000) {
|
|
1859
|
-
if (!text || text.length <= maxLen)
|
|
1860
|
-
|
|
2248
|
+
if (!text || text.length <= maxLen) {
|
|
2249
|
+
return text;
|
|
2250
|
+
}
|
|
2251
|
+
return `${text.substring(0, maxLen)}\n...(truncated, ${text.length - maxLen} chars omitted)`;
|
|
1861
2252
|
}
|
|
1862
2253
|
|
|
1863
2254
|
/**
|
|
1864
2255
|
* 精简工具结果(避免过长的 observation)
|
|
1865
2256
|
*/
|
|
1866
2257
|
#summarizeResult(result) {
|
|
1867
|
-
if (!result)
|
|
2258
|
+
if (!result) {
|
|
2259
|
+
return null;
|
|
2260
|
+
}
|
|
1868
2261
|
const str = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1869
|
-
if (str.length <= 500)
|
|
2262
|
+
if (str.length <= 500) {
|
|
2263
|
+
return result;
|
|
2264
|
+
}
|
|
1870
2265
|
// 返回截断版
|
|
1871
2266
|
if (typeof result === 'object') {
|
|
1872
2267
|
if (Array.isArray(result)) {
|
|
@@ -1878,7 +2273,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1878
2273
|
for (const k of keys) {
|
|
1879
2274
|
const v = result[k];
|
|
1880
2275
|
if (typeof v === 'string' && v.length > 200) {
|
|
1881
|
-
summary[k] = v.substring(0, 200)
|
|
2276
|
+
summary[k] = `${v.substring(0, 200)}...`;
|
|
1882
2277
|
} else if (Array.isArray(v)) {
|
|
1883
2278
|
summary[k] = { _count: v.length, first2: v.slice(0, 2) };
|
|
1884
2279
|
} else {
|