noteconnection 1.8.0 → 1.9.0

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.
Files changed (195) hide show
  1. package/README.md +257 -1803
  2. package/dist/src/agent_workspace.contract.parity.test.js +105 -421
  3. package/dist/src/agent_workspace.frontend.test.js +410 -11
  4. package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
  5. package/dist/src/appimage.patchelf.test.js +113 -0
  6. package/dist/src/appimage.portability.test.js +156 -0
  7. package/dist/src/backend/FileLoader.js +18 -4
  8. package/dist/src/backend/FileLoader.test.js +125 -0
  9. package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
  10. package/dist/src/backend/GraphBuilder.js +66 -18
  11. package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
  12. package/dist/src/backend/GraphMetrics.js +0 -3
  13. package/dist/src/backend/ResourceIdentity.js +155 -0
  14. package/dist/src/backend/ResourceIdentity.test.js +113 -0
  15. package/dist/src/backend/algorithms/CycleDetection.js +0 -1
  16. package/dist/src/backend/algorithms/HybridEngine.js +0 -3
  17. package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
  18. package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
  19. package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
  20. package/dist/src/backend/build_dag.js +0 -1
  21. package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
  22. package/dist/src/capacitor.runtime.contract.test.js +11 -2
  23. package/dist/src/core/Graph.js +138 -42
  24. package/dist/src/core/Graph.test.js +74 -0
  25. package/dist/src/core/NoteConnection.js +2 -2
  26. package/dist/src/core/PathBridge.js +265 -2
  27. package/dist/src/core/ResourceReference.js +17 -0
  28. package/dist/src/desktop.godot.pack.test.js +142 -0
  29. package/dist/src/desktop.runtime.graph.test.js +66 -0
  30. package/dist/src/export/WorkspaceExportBundle.js +44 -0
  31. package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
  32. package/dist/src/foundation.release.evidence.contract.test.js +127 -9
  33. package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
  34. package/dist/src/frontend/README.md +257 -1803
  35. package/dist/src/frontend/agent_workspace.js +665 -7
  36. package/dist/src/frontend/index.html +31 -2
  37. package/dist/src/frontend/knowledge_projection_contract.js +230 -0
  38. package/dist/src/frontend/knowledge_projection_store.js +214 -0
  39. package/dist/src/frontend/libs/path_core.js +158 -42
  40. package/dist/src/frontend/locales/en.json +32 -0
  41. package/dist/src/frontend/locales/zh.json +32 -0
  42. package/dist/src/frontend/mobile_budget_runtime.js +33 -0
  43. package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
  44. package/dist/src/frontend/mobile_identity_contract.js +221 -0
  45. package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
  46. package/dist/src/frontend/path.html +6 -2
  47. package/dist/src/frontend/simulationWorker.js +1 -1
  48. package/dist/src/frontend/source_manager.js +110 -4
  49. package/dist/src/frontend/storage_provider.js +661 -71
  50. package/dist/src/frontend/styles.css +107 -13
  51. package/dist/src/frontend/workspace_panes.js +111 -7
  52. package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
  53. package/dist/src/graph.answer.operator.contract.test.js +72 -0
  54. package/dist/src/index.js +6 -2
  55. package/dist/src/index.knowledge-root.test.js +118 -0
  56. package/dist/src/indexing/IndexLifecycle.js +22 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +24 -0
  58. package/dist/src/knowledge.api.contract.test.js +4 -2
  59. package/dist/src/knowledge_projection.contract.test.js +134 -0
  60. package/dist/src/knowledge_projection_store.test.js +200 -0
  61. package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
  62. package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
  64. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
  68. package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
  69. package/dist/src/learning/PublicAnswerContract.test.js +85 -0
  70. package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
  71. package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
  72. package/dist/src/learning/agentConversationExecution.js +211 -0
  73. package/dist/src/learning/agentConversationExecution.test.js +209 -0
  74. package/dist/src/learning/agentConversationSerialization.js +266 -0
  75. package/dist/src/learning/agentConversationSerialization.test.js +165 -0
  76. package/dist/src/learning/agentResponseBudget.js +179 -0
  77. package/dist/src/learning/agentResponseBudget.test.js +97 -0
  78. package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
  79. package/dist/src/learning/answerReleaseReview.js +1191 -250
  80. package/dist/src/learning/answerReleaseReview.test.js +1293 -7
  81. package/dist/src/learning/answerTaskPlan.js +473 -0
  82. package/dist/src/learning/conversationComposer.js +627 -386
  83. package/dist/src/learning/conversationComposer.test.js +1403 -0
  84. package/dist/src/learning/evidenceContextAssembler.js +312 -120
  85. package/dist/src/learning/graphAnswerCoverage.js +37 -0
  86. package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
  87. package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
  88. package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
  89. package/dist/src/learning/graphAnswerFacts.js +94 -0
  90. package/dist/src/learning/graphAnswerFacts.test.js +39 -0
  91. package/dist/src/learning/graphAnswerPlan.js +908 -0
  92. package/dist/src/learning/graphAnswerPlan.test.js +907 -0
  93. package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
  94. package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
  95. package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
  96. package/dist/src/learning/graphClaimMatcher.js +104 -0
  97. package/dist/src/learning/graphConditionedContext.js +109 -0
  98. package/dist/src/learning/graphConditionedContext.test.js +99 -0
  99. package/dist/src/learning/graphContextAssembler.js +3 -13
  100. package/dist/src/learning/graphContextAssembler.test.js +2 -2
  101. package/dist/src/learning/graphExpansionPolicy.js +22 -0
  102. package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
  103. package/dist/src/learning/identityMetadata.test.js +26 -0
  104. package/dist/src/learning/index.js +1 -0
  105. package/dist/src/learning/mobileAnswerProjection.js +104 -0
  106. package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
  107. package/dist/src/learning/queryBackend.js +34 -10
  108. package/dist/src/learning/queryBackend.test.js +21 -0
  109. package/dist/src/learning/ragContextPack.js +115 -16
  110. package/dist/src/learning/ragContextPack.test.js +151 -0
  111. package/dist/src/learning/ragEvidenceQuality.js +96 -0
  112. package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
  113. package/dist/src/learning/ragPublicText.js +67 -4
  114. package/dist/src/learning/ragPublicText.test.js +57 -0
  115. package/dist/src/learning/ragSufficiencyJudge.js +11 -1
  116. package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
  117. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
  118. package/dist/src/learning/requestNormalization.js +106 -0
  119. package/dist/src/learning/requestNormalization.test.js +101 -0
  120. package/dist/src/learning/store.js +116 -12
  121. package/dist/src/learning/store.test.js +299 -0
  122. package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
  123. package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
  124. package/dist/src/learning/workspaceHydration.js +168 -0
  125. package/dist/src/learning/workspaceHydration.test.js +30 -0
  126. package/dist/src/license.policy.contract.test.js +9 -4
  127. package/dist/src/markdown/MarkdownGateway.js +4 -0
  128. package/dist/src/markdown/MarkdownGateway.test.js +59 -0
  129. package/dist/src/middleware/SseResponseWriter.js +122 -0
  130. package/dist/src/middleware/SseResponseWriter.test.js +54 -0
  131. package/dist/src/middleware/auth.js +28 -7
  132. package/dist/src/middleware/auth.test.js +33 -0
  133. package/dist/src/middleware/index.js +2 -1
  134. package/dist/src/mobile.android.evidence.contract.test.js +146 -0
  135. package/dist/src/mobile.android.signing.contract.test.js +66 -0
  136. package/dist/src/mobile.artifact.contract.test.js +144 -0
  137. package/dist/src/mobile.budget.contract.test.js +90 -0
  138. package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
  139. package/dist/src/mobile.graph.parity.contract.test.js +207 -0
  140. package/dist/src/mobile.identity.contract.test.js +66 -0
  141. package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
  142. package/dist/src/mobile.identity.corpus.test.js +40 -0
  143. package/dist/src/mobile.native.recovery.contract.test.js +64 -0
  144. package/dist/src/mobile.pipeline.test.js +11 -1
  145. package/dist/src/mobile.profile.contract.test.js +225 -0
  146. package/dist/src/mobile.semantic.parity.test.js +92 -0
  147. package/dist/src/mobile_exact_analyzer.test.js +113 -0
  148. package/dist/src/notemd/ContentGenerator.js +4 -9
  149. package/dist/src/notemd/FileProcessor.js +4 -9
  150. package/dist/src/notemd/NotemdService.js +12 -17
  151. package/dist/src/notemd/Translator.js +4 -9
  152. package/dist/src/notemd/progressReporter.js +9 -0
  153. package/dist/src/notemd.server.integration.test.js +119 -4
  154. package/dist/src/notemd.workflow.pipeline.test.js +1 -1
  155. package/dist/src/pathbridge.handshake.contract.test.js +29 -0
  156. package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
  157. package/dist/src/pathcore.bundle.contract.test.js +8 -0
  158. package/dist/src/platform/ExportProfile.js +28 -0
  159. package/dist/src/platform/MobileBudget.js +11 -0
  160. package/dist/src/platform/PlatformCapabilities.js +14 -0
  161. package/dist/src/platform/PlatformCapabilities.test.js +6 -0
  162. package/dist/src/query_backend.external_http.integration.test.js +2 -2
  163. package/dist/src/release.godot.mirror.contract.test.js +28 -4
  164. package/dist/src/resources/ResourceRegistry.js +40 -0
  165. package/dist/src/resources/ResourceRegistry.test.js +36 -0
  166. package/dist/src/routes/index.js +0 -4
  167. package/dist/src/routes/knowledge.js +46 -33
  168. package/dist/src/routes/knowledgePayload.js +185 -0
  169. package/dist/src/routes/knowledgePayload.test.js +53 -0
  170. package/dist/src/routes/markdown.js +8 -0
  171. package/dist/src/routes/notemd.js +990 -234
  172. package/dist/src/routes/registry.contract.test.js +5 -2
  173. package/dist/src/routes/registry.shadow.contract.test.js +22 -0
  174. package/dist/src/routes/render.js +14 -51
  175. package/dist/src/runtime.capabilities.test.js +18 -2
  176. package/dist/src/server.js +566 -1090
  177. package/dist/src/server.migration.test.js +417 -15
  178. package/dist/src/sidecar.build.lock.contract.test.js +201 -0
  179. package/dist/src/sidecar.freshness.contract.test.js +184 -0
  180. package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
  181. package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
  182. package/dist/src/simulation.worker.offline.test.js +104 -0
  183. package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
  184. package/dist/src/storage.provider.contract.test.js +152 -1
  185. package/dist/src/tauri.frontend.build.contract.test.js +8 -0
  186. package/dist/src/tauri.window.evidence.execution.test.js +68 -0
  187. package/dist/src/utils/RuntimePaths.js +6 -0
  188. package/dist/src/utils/RuntimePaths.test.js +27 -0
  189. package/dist/src/windows.installer.build.test.js +109 -0
  190. package/dist/src/windows.installer.payload.test.js +76 -0
  191. package/dist/src/windows.installer.runtime.test.js +69 -0
  192. package/dist/src/workspace/WorkspaceRegistry.js +13 -0
  193. package/package.json +32 -10
  194. package/dist/src/routes/data.js +0 -267
  195. package/dist/src/routes/diagnostics.js +0 -51
@@ -5,7 +5,376 @@ exports.collectAgentConversationAtomIds = collectAgentConversationAtomIds;
5
5
  exports.buildScopedConversationReply = buildScopedConversationReply;
6
6
  const answerReleaseReview_1 = require("./answerReleaseReview");
7
7
  const graphContextAssembler_1 = require("./graphContextAssembler");
8
+ const graphAnswerPlan_1 = require("./graphAnswerPlan");
9
+ const graphAnswerCoverage_1 = require("./graphAnswerCoverage");
10
+ const graphAnswerFacts_1 = require("./graphAnswerFacts");
8
11
  const ragPublicText_1 = require("./ragPublicText");
12
+ const graphAnswerQualityPolicy_1 = require("./graphAnswerQualityPolicy");
13
+ const answerTaskPlan_1 = require("./answerTaskPlan");
14
+ const LEGACY_FULL_RESPONSE_MAX_CHARS = 24000;
15
+ const LEGACY_FULL_RESPONSE_MAX_FRAGMENTS = 24;
16
+ const FULL_REPORT_FRAGMENT_ROLES = new Set([
17
+ 'direct_support',
18
+ 'parent_context',
19
+ 'adjacent_context',
20
+ 'graph_neighbor_support',
21
+ 'conflict',
22
+ ]);
23
+ function isFullResponseMode(params) {
24
+ return params.responseMode === 'full';
25
+ }
26
+ function normalizeSourcePathForComparison(value) {
27
+ return String(value || '')
28
+ .trim()
29
+ .replace(/\\/gu, '/')
30
+ .replace(/\/{2,}/gu, '/')
31
+ .replace(/\/+$/gu, '')
32
+ .toLowerCase();
33
+ }
34
+ function resolveFullReportAnchorSource(params) {
35
+ const anchorAtomId = normalizeWhitespace(String(params.graphContext?.anchorAtomId || ''));
36
+ const anchorDocumentId = normalizeWhitespace(String(params.graphContext?.anchorDocumentId || ''));
37
+ const knowledgePoints = Array.isArray(params.knowledgePoints) ? params.knowledgePoints : [];
38
+ const anchorPoint = knowledgePoints.find((point) => {
39
+ const pointAtomId = normalizeWhitespace(String(point.atomId || ''));
40
+ const pointAtomIds = Array.isArray(point.atomIds) ? point.atomIds.map((atomId) => normalizeWhitespace(String(atomId || ''))) : [];
41
+ return Boolean(anchorAtomId && (pointAtomId === anchorAtomId || pointAtomIds.includes(anchorAtomId)));
42
+ })
43
+ || knowledgePoints.find((point) => anchorDocumentId && normalizeWhitespace(String(point.documentId || '')) === anchorDocumentId)
44
+ || knowledgePoints[0];
45
+ const documentId = anchorDocumentId || normalizeWhitespace(String(anchorPoint?.documentId || ''));
46
+ let sourcePath = normalizeSourcePathForComparison(String(anchorPoint?.sourcePath || ''));
47
+ if (!sourcePath && documentId) {
48
+ const sourceFragment = (params.ragContextPack?.fragments || []).find((fragment) => (fragment.role !== 'graph_neighbor_support'
49
+ && normalizeWhitespace(String(fragment.documentId || '')) === documentId
50
+ && normalizeSourcePathForComparison(String(fragment.sourcePath || ''))));
51
+ sourcePath = normalizeSourcePathForComparison(String(sourceFragment?.sourcePath || ''));
52
+ }
53
+ return { documentId, sourcePath };
54
+ }
55
+ function isFullReportGraphNeighborFromAnchorSource(fragment, anchorSource) {
56
+ if (fragment.role !== 'graph_neighbor_support') {
57
+ return true;
58
+ }
59
+ const fragmentDocumentId = normalizeWhitespace(String(fragment.documentId || ''));
60
+ const fragmentSourcePath = normalizeSourcePathForComparison(String(fragment.sourcePath || ''));
61
+ if (!anchorSource.documentId && !anchorSource.sourcePath) {
62
+ return false;
63
+ }
64
+ if (anchorSource.documentId && fragmentDocumentId !== anchorSource.documentId) {
65
+ return false;
66
+ }
67
+ if (anchorSource.sourcePath && fragmentSourcePath !== anchorSource.sourcePath) {
68
+ return false;
69
+ }
70
+ return Boolean(fragmentDocumentId || fragmentSourcePath);
71
+ }
72
+ function hasFullReportBodyOutsideMermaid(fragment) {
73
+ const source = stripFullReportMermaid(String(fragment.text || ''))
74
+ .replace(/^#{1,6}\s+[^\n]*$/gmu, '')
75
+ .trim();
76
+ return Boolean(source) && !isFullReportInternalBlock(source);
77
+ }
78
+ function fullReportProductCaps(params) {
79
+ const budget = params.responseBudget;
80
+ const productCapDisabled = budget?.productCapDisabled === true;
81
+ if (!budget) {
82
+ return {
83
+ maxFragments: LEGACY_FULL_RESPONSE_MAX_FRAGMENTS,
84
+ maxChars: LEGACY_FULL_RESPONSE_MAX_CHARS,
85
+ productCapDisabled: false,
86
+ };
87
+ }
88
+ return {
89
+ maxFragments: productCapDisabled
90
+ ? budget.runtimeGovernor.maxFragmentsProcessed
91
+ : (budget.rag?.maxFragments || LEGACY_FULL_RESPONSE_MAX_FRAGMENTS),
92
+ maxChars: productCapDisabled
93
+ ? budget.runtimeGovernor.maxReportChars
94
+ : (budget.reportMaxChars || LEGACY_FULL_RESPONSE_MAX_CHARS),
95
+ productCapDisabled,
96
+ };
97
+ }
98
+ function selectFullReportFragments(params) {
99
+ const fragments = params.ragContextPack?.fragments || [];
100
+ const anchorSource = resolveFullReportAnchorSource(params);
101
+ const caps = fullReportProductCaps(params);
102
+ let truncated = false;
103
+ let truncationReason;
104
+ const candidates = fragments
105
+ .filter((fragment) => {
106
+ const title = normalizeWhitespace(String(fragment.title || '')).toLowerCase();
107
+ const headingPath = (Array.isArray(fragment.headingPath) ? fragment.headingPath : [])
108
+ .map((heading) => normalizeWhitespace(String(heading || '')).toLowerCase())
109
+ .join(' ');
110
+ const mermaidMarked = title.includes('mermaid') || headingPath.includes('mermaid');
111
+ return !title.includes('preamble')
112
+ && (!mermaidMarked || hasFullReportBodyOutsideMermaid(fragment))
113
+ && !headingPath.includes('preamble')
114
+ && (!headingPath.includes('mermaid') || hasFullReportBodyOutsideMermaid(fragment));
115
+ })
116
+ .filter((fragment) => (FULL_REPORT_FRAGMENT_ROLES.has(fragment.role)
117
+ && isFullReportGraphNeighborFromAnchorSource(fragment, anchorSource)));
118
+ const selected = [];
119
+ const selectedSections = new Set();
120
+ let usedChars = 0;
121
+ const sectionKey = (fragment) => {
122
+ const path = Array.isArray(fragment.headingPath) ? fragment.headingPath : [];
123
+ const leaf = normalizeWhitespace(String(path[path.length - 1] || fragment.title || ''))
124
+ .replace(/^#+\s*/u, '')
125
+ .replace(/\s*\((?:mermaid|code|diagram)\s+block\)\s*$/iu, '')
126
+ .toLowerCase();
127
+ return `${fragment.documentId}:${leaf}`;
128
+ };
129
+ const appendCandidate = (fragment) => {
130
+ if (selected.length >= caps.maxFragments || usedChars >= caps.maxChars) {
131
+ truncated = true;
132
+ truncationReason = caps.productCapDisabled
133
+ ? (selected.length >= caps.maxFragments ? 'runtime_fragment_limit' : 'runtime_report_chars_limit')
134
+ : (selected.length >= caps.maxFragments ? 'product_fragment_limit' : 'product_report_chars_limit');
135
+ return;
136
+ }
137
+ const text = String(fragment.text || '').trim();
138
+ if (!text
139
+ || !FULL_REPORT_FRAGMENT_ROLES.has(fragment.role)
140
+ || !isFullReportGraphNeighborFromAnchorSource(fragment, anchorSource)
141
+ || (fragment.truncated && !hasBalancedMathDelimiters(text))) {
142
+ return;
143
+ }
144
+ const remaining = caps.maxChars - usedChars;
145
+ if (text.length > remaining) {
146
+ return;
147
+ }
148
+ const key = sectionKey(fragment);
149
+ if (selectedSections.has(key)) {
150
+ return;
151
+ }
152
+ selected.push({
153
+ ...fragment,
154
+ text,
155
+ charCount: text.length,
156
+ });
157
+ usedChars += text.length;
158
+ selectedSections.add(key);
159
+ };
160
+ const rolePriority = {
161
+ parent_context: 0,
162
+ adjacent_context: 1,
163
+ graph_neighbor_support: 2,
164
+ direct_support: 3,
165
+ conflict: 4,
166
+ };
167
+ const grouped = new Map();
168
+ candidates.forEach((fragment) => {
169
+ const key = sectionKey(fragment);
170
+ const group = grouped.get(key) || [];
171
+ group.push(fragment);
172
+ grouped.set(key, group);
173
+ });
174
+ const sectionCandidates = Array.from(grouped.values())
175
+ .map((group) => group.sort((left, right) => {
176
+ const roleDelta = (rolePriority[left.role] ?? 9) - (rolePriority[right.role] ?? 9);
177
+ if (roleDelta !== 0) {
178
+ return roleDelta;
179
+ }
180
+ const boundaryDelta = Number(right.sourceBoundary === 'full_document') - Number(left.sourceBoundary === 'full_document');
181
+ if (boundaryDelta !== 0) {
182
+ return boundaryDelta;
183
+ }
184
+ const lengthDelta = String(right.text || '').length - String(left.text || '').length;
185
+ if (lengthDelta !== 0) {
186
+ return lengthDelta;
187
+ }
188
+ const leftLine = Number.isFinite(Number(left.startLine)) ? Number(left.startLine) : Number.MAX_SAFE_INTEGER;
189
+ const rightLine = Number.isFinite(Number(right.startLine)) ? Number(right.startLine) : Number.MAX_SAFE_INTEGER;
190
+ return leftLine - rightLine;
191
+ }))
192
+ .sort((left, right) => {
193
+ const leftFragment = left[0];
194
+ const rightFragment = right[0];
195
+ const leftLine = Number.isFinite(Number(leftFragment?.startLine)) ? Number(leftFragment?.startLine) : Number.MAX_SAFE_INTEGER;
196
+ const rightLine = Number.isFinite(Number(rightFragment?.startLine)) ? Number(rightFragment?.startLine) : Number.MAX_SAFE_INTEGER;
197
+ if (leftFragment?.documentId !== rightFragment?.documentId) {
198
+ return String(leftFragment?.documentId || '').localeCompare(String(rightFragment?.documentId || ''));
199
+ }
200
+ return leftLine - rightLine;
201
+ });
202
+ sectionCandidates.forEach((group) => appendCandidate(group[0]));
203
+ if (selected.length < sectionCandidates.length && !truncated) {
204
+ truncated = true;
205
+ truncationReason = caps.productCapDisabled ? 'runtime_report_chars_limit' : 'product_report_chars_limit';
206
+ }
207
+ return { fragments: selected, truncated, truncationReason };
208
+ }
209
+ function hasBalancedMathDelimiters(value) {
210
+ const source = String(value || '');
211
+ const displayCount = (source.match(/(?<!\\)\$\$/gu) || []).length;
212
+ if (displayCount % 2 !== 0) {
213
+ return false;
214
+ }
215
+ const inlineSource = source.replace(/(?<!\\)\$\$/gu, '');
216
+ return ((inlineSource.match(/(?<!\\)\$/gu) || []).length % 2) === 0;
217
+ }
218
+ function isFullReportInternalBlock(value) {
219
+ const normalized = normalizeWhitespace(value);
220
+ return !normalized
221
+ || /(?:遵从.{0,20}(?:指示|要求)|仅基于标题|所有推理过程|推理过程以英文|最终输出|输出为简体中文|本技术文档旨在|本文档旨在|we will|based only on the title|final output|all reasoning)/iu.test(normalized)
222
+ || /(?:grounded by|key evidence|missDiagnostics|workspaceReadiness|matchedAtomCount|titleLikeQueries|retrieval_candidates_below_threshold|rag context pack|planner)/iu.test(normalized);
223
+ }
224
+ function stripFullReportMermaid(value) {
225
+ return String(value || '')
226
+ .replace(/```mermaid[\s\S]*?(?:```|$)/giu, '')
227
+ .replace(/```(?:graphviz|dot)[\s\S]*?(?:```|$)/giu, '')
228
+ .trim();
229
+ }
230
+ function sanitizeFullReportBlock(value) {
231
+ const source = String(value || '').replace(/\r\n?/gu, '\n').trim();
232
+ if (!source) {
233
+ return '';
234
+ }
235
+ const lines = source.split('\n');
236
+ const sanitizedLines = [];
237
+ lines.forEach((line) => {
238
+ const normalizedLine = line.trim();
239
+ if (!normalizedLine) {
240
+ return;
241
+ }
242
+ if (/^#{1,6}\s+/u.test(normalizedLine) || /^\s*\|/u.test(normalizedLine)) {
243
+ if (!isFullReportInternalBlock(normalizedLine)) {
244
+ sanitizedLines.push(normalizedLine);
245
+ }
246
+ return;
247
+ }
248
+ const clauses = normalizedLine
249
+ .split(/(?<=[.!?。!?;;])(?:\s+|(?=[\p{L}]))/u)
250
+ .map((clause) => clause.trim())
251
+ .filter((clause) => clause && !isFullReportInternalBlock(clause));
252
+ if (clauses.length > 0) {
253
+ sanitizedLines.push(clauses.join(' '));
254
+ }
255
+ });
256
+ return sanitizedLines.join('\n').trim();
257
+ }
258
+ function isStandaloneFullReportHeading(value) {
259
+ return /^#{1,6}\s+\S[^\n]*$/u.test(String(value || '').trim());
260
+ }
261
+ function splitFullReportHeadingBody(value, headingPath) {
262
+ const normalized = String(value || '').trim();
263
+ const headingName = normalizeWhitespace(String(headingPath[headingPath.length - 1] || ''))
264
+ .replace(/^#+\s*/u, '')
265
+ .trim();
266
+ if (!normalized || !headingName || !/^#{1,6}\s+/u.test(normalized)) {
267
+ return { heading: '', body: normalized };
268
+ }
269
+ const escapedHeadingName = headingName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
270
+ const headingMatch = normalized.match(new RegExp(`^(#{1,6})\\s+${escapedHeadingName}(?:\\s+|$)([\\s\\S]*)$`, 'iu'));
271
+ if (!headingMatch) {
272
+ return { heading: '', body: normalized };
273
+ }
274
+ return {
275
+ heading: `${headingMatch[1]} ${headingName}`,
276
+ body: String(headingMatch[2] || '').trim(),
277
+ };
278
+ }
279
+ function appendFullReportBlock(lines, block, currentLength, dedupeValue = block, maxChars = LEGACY_FULL_RESPONSE_MAX_CHARS, onLimit) {
280
+ const normalized = sanitizeFullReportBlock(block);
281
+ if (!normalized || isFullReportInternalBlock(normalized) || !hasBalancedMathDelimiters(normalized)) {
282
+ return false;
283
+ }
284
+ const separatorLength = lines.length > 0 ? 2 : 0;
285
+ if (currentLength.value + separatorLength + normalized.length > maxChars) {
286
+ onLimit?.('report_chars_limit');
287
+ return false;
288
+ }
289
+ const key = normalizeWhitespace(dedupeValue).toLowerCase();
290
+ if (!key || currentLength.keys.has(key)) {
291
+ return false;
292
+ }
293
+ lines.push(normalized);
294
+ currentLength.value += separatorLength + normalized.length;
295
+ currentLength.keys.add(key);
296
+ return true;
297
+ }
298
+ function buildFullTechnicalReportResult(params, graphContext, graphAnswerPlan) {
299
+ const useChinese = useChineseAnswerLanguage(params);
300
+ const selection = selectFullReportFragments(params);
301
+ const caps = fullReportProductCaps(params);
302
+ if (selection.fragments.length <= 0) {
303
+ return {
304
+ answer: buildScopedConversationAnswer({ ...params, responseMode: 'slim' }, graphContext, graphAnswerPlan),
305
+ state: {
306
+ truncated: selection.truncated,
307
+ truncationReason: selection.truncationReason,
308
+ selectedFragmentCount: 0,
309
+ acceptedBlockCount: 0,
310
+ },
311
+ };
312
+ }
313
+ const reportBlocks = [];
314
+ const reportLength = { value: 0, keys: new Set() };
315
+ let truncated = selection.truncated;
316
+ let truncationReason = selection.truncationReason;
317
+ const markLimit = (reason) => {
318
+ truncated = true;
319
+ if (!truncationReason) {
320
+ truncationReason = caps.productCapDisabled ? `runtime_${reason}` : `product_${reason}`;
321
+ }
322
+ };
323
+ selection.fragments.forEach((fragment) => {
324
+ const source = stripFullReportMermaid(fragment.text);
325
+ let pendingHeading = '';
326
+ source
327
+ .split(/\n{2,}/u)
328
+ .map((block) => block.trim())
329
+ .filter(Boolean)
330
+ .forEach((block) => {
331
+ const normalizedBlock = block
332
+ .replace(/^#{1,6}\s+Water Glass\s*$/imu, '')
333
+ .trim();
334
+ if (!normalizedBlock) {
335
+ return;
336
+ }
337
+ const headingBody = splitFullReportHeadingBody(normalizedBlock, Array.isArray(fragment.headingPath) ? fragment.headingPath : []);
338
+ if (headingBody.heading && headingBody.body) {
339
+ appendFullReportBlock(reportBlocks, `${headingBody.heading}\n\n${headingBody.body}`, reportLength, headingBody.body, caps.maxChars, markLimit);
340
+ pendingHeading = '';
341
+ return;
342
+ }
343
+ if (isStandaloneFullReportHeading(normalizedBlock)) {
344
+ pendingHeading = normalizedBlock;
345
+ return;
346
+ }
347
+ const sectionBlock = pendingHeading
348
+ ? `${pendingHeading}\n\n${normalizedBlock}`
349
+ : normalizedBlock;
350
+ appendFullReportBlock(reportBlocks, sectionBlock, reportLength, pendingHeading ? normalizedBlock : sectionBlock, caps.maxChars, markLimit);
351
+ pendingHeading = '';
352
+ });
353
+ });
354
+ const directClaims = graphAnswerPlan.claims
355
+ .filter((claim) => claim.required || claim.role === 'definition')
356
+ .map((claim) => normalizeWhitespace((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)(claim.statement)))
357
+ .filter(Boolean);
358
+ if (directClaims.length > 0 && !directClaims.some((claim) => reportBlocks.some((block) => normalizeWhitespace(block).toLowerCase().includes(claim.toLowerCase())))) {
359
+ const heading = useChinese ? '## 核心定义' : '## Definition';
360
+ appendFullReportBlock(reportBlocks, heading, reportLength, heading, caps.maxChars, markLimit);
361
+ directClaims.forEach((claim) => appendFullReportBlock(reportBlocks, claim, reportLength, claim, caps.maxChars, markLimit));
362
+ }
363
+ const answer = reportBlocks.join('\n\n').trim()
364
+ || buildScopedConversationAnswer({ ...params, responseMode: 'slim' }, graphContext, graphAnswerPlan);
365
+ return {
366
+ answer,
367
+ state: {
368
+ truncated,
369
+ truncationReason,
370
+ selectedFragmentCount: selection.fragments.length,
371
+ acceptedBlockCount: reportBlocks.length,
372
+ },
373
+ };
374
+ }
375
+ function buildFullTechnicalReport(params, graphContext, graphAnswerPlan) {
376
+ return buildFullTechnicalReportResult(params, graphContext, graphAnswerPlan).answer;
377
+ }
9
378
  function normalizeWhitespace(value) {
10
379
  return String(value || '').replace(/\s+/g, ' ').trim();
11
380
  }
@@ -427,50 +796,23 @@ function classifyScopedConversationIntent(message) {
427
796
  }
428
797
  return 'generic';
429
798
  }
430
- function resolveRagAnswerProfile(message) {
431
- const intent = classifyScopedConversationIntent(message);
432
- if (intent === 'compare') {
433
- return {
434
- directSupportSentenceCount: 4,
435
- documentContextSentenceCount: 1,
436
- graphNeighborSentenceCount: 2,
437
- publicSentenceCount: 6,
438
- };
439
- }
440
- if (intent === 'how_to') {
441
- return {
442
- directSupportSentenceCount: 3,
443
- documentContextSentenceCount: 1,
444
- graphNeighborSentenceCount: 2,
445
- publicSentenceCount: 6,
446
- };
447
- }
448
- if (intent === 'causal_explain') {
449
- return {
450
- directSupportSentenceCount: 2,
451
- documentContextSentenceCount: 2,
452
- graphNeighborSentenceCount: 2,
453
- publicSentenceCount: 6,
454
- };
455
- }
456
- if (intent === 'generic') {
457
- return {
458
- directSupportSentenceCount: 2,
459
- documentContextSentenceCount: 2,
460
- graphNeighborSentenceCount: 1,
461
- publicSentenceCount: 6,
462
- };
463
- }
464
- return {
465
- directSupportSentenceCount: 1,
466
- documentContextSentenceCount: 2,
467
- graphNeighborSentenceCount: 1,
468
- publicSentenceCount: 6,
469
- };
470
- }
471
799
  function containsCjk(value) {
472
800
  return /[\u3040-\u30ff\u3400-\u9fff\uf900-\ufaff]/u.test(String(value || ''));
473
801
  }
802
+ function useChineseAnswerLanguage(params) {
803
+ if (params.answerLanguage === 'zh') {
804
+ return true;
805
+ }
806
+ if (params.answerLanguage === 'en') {
807
+ return false;
808
+ }
809
+ return containsCjk([
810
+ params.message,
811
+ params.knowledgePoints[0]?.title,
812
+ params.knowledgePoints[0]?.summary,
813
+ params.graphContext?.anchorTitle,
814
+ ].filter(Boolean).join(' '));
815
+ }
474
816
  function stripConversationAnswerTerminalPunctuation(value) {
475
817
  return normalizeWhitespace(String(value || '').replace(/[.!?\u3002\uFF01\uFF1F]+$/u, ''));
476
818
  }
@@ -517,170 +859,24 @@ function normalizeGraphAnswerDisplayTitle(value) {
517
859
  .replace(/\s*\((?:mermaid|code|diagram)\s+block\)\s*$/iu, '')
518
860
  .trim());
519
861
  }
520
- function normalizeGraphAnswerComparableTitle(value) {
521
- return normalizeGraphAnswerDisplayTitle(value).toLowerCase();
522
- }
523
- function collectGraphWindowTitles(graphContext, windowKey, limit) {
524
- if (!graphContext || !Array.isArray(graphContext[windowKey])) {
525
- return [];
526
- }
527
- const anchorAtomId = normalizeWhitespace(String(graphContext.anchorAtomId || '').trim());
528
- const anchorTitle = normalizeGraphAnswerComparableTitle(graphContext.anchorTitle);
529
- const seen = new Set();
530
- const titles = [];
531
- for (const node of graphContext[windowKey] || []) {
532
- const atomId = normalizeWhitespace(String(node && node.atomId || '').trim());
533
- const title = normalizeGraphAnswerDisplayTitle(String(node && node.title || ''));
534
- const comparableTitle = normalizeGraphAnswerComparableTitle(title);
535
- if (!title || (atomId && atomId === anchorAtomId) || (comparableTitle && comparableTitle === anchorTitle)) {
536
- continue;
537
- }
538
- const key = comparableTitle || atomId;
539
- if (seen.has(key)) {
540
- continue;
541
- }
542
- seen.add(key);
543
- titles.push(title);
544
- if (titles.length >= limit) {
545
- break;
546
- }
547
- }
548
- return titles;
549
- }
550
862
  function buildGraphProfileAnswerSentence(graphContext, useChinese) {
551
863
  if (!graphContext) {
552
864
  return '';
553
865
  }
554
- const anchorProfile = graphContext.anchorGraphProfile && typeof graphContext.anchorGraphProfile === 'object'
555
- ? graphContext.anchorGraphProfile
556
- : null;
557
- const anchorTitle = normalizeWhitespace(String(graphContext.anchorTitle || anchorProfile?.title || '').trim());
558
- const predecessorTitles = collectGraphWindowTitles(graphContext, 'predecessorWindow', 2);
559
- const successorTitles = collectGraphWindowTitles(graphContext, 'successorWindow', 2);
560
- const degreeParts = [];
561
- const inDegree = anchorProfile ? Number(anchorProfile.inDegree) : NaN;
562
- const outDegree = anchorProfile ? Number(anchorProfile.outDegree) : NaN;
563
- if (Number.isFinite(inDegree)) {
564
- degreeParts.push(useChinese ? `入度为 ${inDegree}` : `${inDegree} incoming`);
565
- }
566
- if (Number.isFinite(outDegree)) {
567
- degreeParts.push(useChinese ? `出度为 ${outDegree}` : `${outDegree} outgoing`);
568
- }
569
- if (degreeParts.length <= 0 && predecessorTitles.length <= 0 && successorTitles.length <= 0) {
570
- return '';
571
- }
572
- if (useChinese) {
573
- const fragments = [];
574
- if (degreeParts.length > 0) {
575
- fragments.push(`${anchorTitle || '当前锚点'}在当前图中的${degreeParts.join(',')}`);
576
- }
577
- if (predecessorTitles.length > 0) {
578
- fragments.push(`紧邻前置节点包括 ${predecessorTitles.join('、')}`);
579
- }
580
- if (successorTitles.length > 0) {
581
- fragments.push(`后续分支包括 ${successorTitles.join('、')}`);
582
- }
583
- return fragments.join(',');
584
- }
585
- const fragments = [];
586
- if (degreeParts.length > 0) {
587
- fragments.push(`${anchorTitle || 'The current anchor'} has ${degreeParts.join(' and ')} links in the current graph`);
588
- }
589
- if (predecessorTitles.length > 0 && successorTitles.length > 0) {
590
- fragments.push(`its immediate predecessors include ${predecessorTitles.join(', ')}, and likely next nodes include ${successorTitles.join(', ')}`);
591
- }
592
- else if (predecessorTitles.length > 0) {
593
- fragments.push(`its immediate predecessors include ${predecessorTitles.join(', ')}`);
594
- }
595
- else if (successorTitles.length > 0) {
596
- fragments.push(`its likely next nodes include ${successorTitles.join(', ')}`);
597
- }
598
- return fragments.join('; ');
599
- }
600
- const RAG_ANSWER_QUERY_STOPWORDS = new Set([
601
- 'a', 'about', 'an', 'and', 'are', 'as', 'at', 'be', 'between', 'by', 'compare',
602
- 'contrast', 'difference', 'differences', 'do', 'does', 'from', 'how',
603
- 'in', 'is', 'it', 'me', 'of', 'on', 'or', 'plan', 'step', 'steps', 'tell', 'the', 'to', 'versus', 'vs', 'what',
604
- 'which', 'with',
605
- ]);
606
- function extractRagAnswerQueryTerms(message) {
607
- const terms = (String(message || '').toLowerCase().match(/[\p{L}\p{N}]+/gu) || [])
608
- .map((term) => term.trim())
609
- .filter((term) => term.length >= 2 && !RAG_ANSWER_QUERY_STOPWORDS.has(term));
610
- return Array.from(new Set(terms));
611
- }
612
- function normalizeMermaidEvidenceLabel(value) {
613
- return normalizeWhitespace(String(value || '')
614
- .replace(/<br\s*\/?>/giu, ' ')
615
- .replace(/\\n/gu, ' ')
616
- .replace(/[`*_~#|{}]/gu, ' ')
617
- .replace(/\s{2,}/gu, ' ')
618
- .trim());
619
- }
620
- function extractMermaidEvidenceLabels(fenceText) {
621
- const normalizedFence = String(fenceText || '').trim();
622
- if (!/^mermaid\b/iu.test(normalizedFence)) {
623
- return '';
624
- }
625
- const mermaidBody = normalizedFence.replace(/^mermaid\b/iu, '').trim();
626
- const labels = [];
627
- const appendLabel = (value) => {
628
- const label = normalizeMermaidEvidenceLabel(value);
629
- if (label && !labels.includes(label)) {
630
- labels.push(label);
631
- }
632
- };
633
- mermaidBody.replace(/\[([^\]]+)\]/gu, (_match, label) => {
634
- appendLabel(label);
635
- return '';
866
+ const facts = (0, graphAnswerFacts_1.collectGraphAnswerFacts)(graphContext, {
867
+ anchorAtomId: graphContext.anchorAtomId,
868
+ anchorTitle: graphContext.anchorTitle,
869
+ normalizeTitle: (value) => normalizeGraphAnswerDisplayTitle(String(value || '')),
636
870
  });
637
- mermaidBody.replace(/"([^"]+)"/gu, (_match, label) => {
638
- appendLabel(label);
871
+ if (!facts) {
639
872
  return '';
640
- });
641
- return labels.join('. ');
642
- }
643
- function cleanRagEvidenceText(value) {
644
- return normalizeWhitespace(String(value || '')
645
- .replace(/```([\s\S]*?)(?:```|$)/gu, (_match, fenceText) => (` ${extractMermaidEvidenceLabels(fenceText)} `))
646
- .split(/\r?\n/u)
647
- .filter((line) => !/^#{1,6}\s+/u.test(line.trim()))
648
- .join(' ')
649
- .replace(/\[[^\]]+\]\([^)]*\)/gu, ' ')
650
- .replace(/!\[[^\]]*\]\([^)]*\)/gu, ' ')
651
- .replace(/[*_~`>#|]/gu, ' ')
652
- .replace(/\s{2,}/gu, ' '));
653
- }
654
- function splitRagEvidenceSentences(fragment) {
655
- const cleaned = cleanRagEvidenceText(fragment.text);
656
- if (!cleaned) {
657
- return [];
658
- }
659
- const sentences = [];
660
- let start = 0;
661
- for (let index = 0; index < cleaned.length; index += 1) {
662
- const char = cleaned[index];
663
- const isDecimalPoint = char === '.'
664
- && /\d/u.test(cleaned[index - 1] || '')
665
- && /\d/u.test(cleaned[index + 1] || '');
666
- const isBoundary = !isDecimalPoint && /[.!?\u3002\uFF01\uFF1F]/u.test(char);
667
- if (!isBoundary) {
668
- continue;
669
- }
670
- sentences.push(cleaned.slice(start, index + 1));
671
- start = index + 1;
672
873
  }
673
- if (start < cleaned.length) {
674
- sentences.push(cleaned.slice(start));
675
- }
676
- return (sentences.length > 0 ? sentences : [cleaned])
677
- .map((sentence) => normalizeWhitespace(sentence))
678
- .filter((sentence) => sentence.length >= 16);
874
+ return (0, graphAnswerFacts_1.formatGraphAnswerProfileSentence)(facts, useChinese);
679
875
  }
680
876
  function sentenceComparableKey(value) {
681
877
  return stripConversationAnswerTerminalPunctuation(value).toLowerCase();
682
878
  }
683
- function appendRagEvidenceSentence(sentences, candidate, useChinese) {
879
+ function appendPlannedEvidenceSentence(sentences, candidate, useChinese) {
684
880
  const normalized = normalizeConversationAnswerSentence((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)(candidate), useChinese);
685
881
  if (!normalized) {
686
882
  return;
@@ -690,101 +886,14 @@ function appendRagEvidenceSentence(sentences, candidate, useChinese) {
690
886
  const existingKey = sentenceComparableKey(sentence);
691
887
  return existingKey === candidateKey
692
888
  || (candidateKey.length >= 32 && existingKey.includes(candidateKey))
693
- || (existingKey.length >= 32 && candidateKey.includes(existingKey));
889
+ || (existingKey.length >= 32 && candidateKey.includes(existingKey))
890
+ || (0, graphAnswerQualityPolicy_1.isPolaritySafeSemanticDuplicate)(existingKey, candidateKey);
694
891
  });
695
892
  if (!alreadyCovered) {
696
893
  sentences.push(normalized);
697
894
  }
698
895
  }
699
- function collectSentenceQueryTerms(sentence, queryTerms) {
700
- const lower = sentence.toLowerCase();
701
- return new Set(queryTerms.filter((term) => lower.includes(term)));
702
- }
703
- function collectFragmentLeafHeadingQueryTerms(fragment, queryTerms) {
704
- const headingPath = Array.isArray(fragment.headingPath) ? fragment.headingPath : [];
705
- const leafHeading = normalizeWhitespace(String(headingPath[headingPath.length - 1] || ''));
706
- if (!leafHeading) {
707
- return new Set();
708
- }
709
- return collectSentenceQueryTerms(leafHeading, queryTerms);
710
- }
711
- function rankRagEvidenceSentenceCandidates(candidates, limit, options = {}) {
712
- const remaining = candidates.slice();
713
- const ranked = [];
714
- const coveredTerms = new Set();
715
- while (remaining.length > 0 && ranked.length < limit) {
716
- let bestIndex = 0;
717
- let bestScore = Number.NEGATIVE_INFINITY;
718
- remaining.forEach((candidate, index) => {
719
- const totalTermCount = candidate.queryTerms.size;
720
- const headingTermCount = options.useLeafHeadingScore ? candidate.headingQueryTerms.size : 0;
721
- const uncoveredTermCount = Array.from(candidate.queryTerms)
722
- .filter((term) => !coveredTerms.has(term))
723
- .length;
724
- const score = uncoveredTermCount * 4
725
- + (uncoveredTermCount > 0 ? totalTermCount : 0)
726
- + (totalTermCount > 0 ? candidate.fragmentQueryTermCount / 2 : 0)
727
- + headingTermCount * 6
728
- - candidate.order / 10000;
729
- if (score > bestScore) {
730
- bestScore = score;
731
- bestIndex = index;
732
- }
733
- });
734
- const [selected] = remaining.splice(bestIndex, 1);
735
- ranked.push(selected);
736
- selected.queryTerms.forEach((term) => coveredTerms.add(term));
737
- }
738
- return ranked;
739
- }
740
- function selectRagEvidenceSentences(fragments, roles, limit, queryTerms = [], options = {}) {
741
- const selected = [];
742
- const candidates = [];
743
- let sentenceOrder = 0;
744
- fragments
745
- .filter((fragment) => roles.has(fragment.role))
746
- .forEach((fragment) => {
747
- const sentences = splitRagEvidenceSentences(fragment);
748
- const headingQueryTerms = collectFragmentLeafHeadingQueryTerms(fragment, queryTerms);
749
- const fragmentQueryTerms = new Set(sentences.flatMap((sentence) => Array.from(collectSentenceQueryTerms(sentence, queryTerms))));
750
- sentences.forEach((sentence) => {
751
- if (options.rejectSentence?.(sentence)) {
752
- sentenceOrder += 1;
753
- return;
754
- }
755
- const comparable = sentenceComparableKey(sentence);
756
- if (!candidates.some((existing) => sentenceComparableKey(existing.sentence) === comparable)) {
757
- candidates.push({
758
- sentence,
759
- queryTerms: collectSentenceQueryTerms(sentence, queryTerms),
760
- headingQueryTerms,
761
- fragmentQueryTermCount: fragmentQueryTerms.size,
762
- order: sentenceOrder,
763
- });
764
- }
765
- sentenceOrder += 1;
766
- });
767
- });
768
- const maxHeadingTermCount = candidates.reduce((max, candidate) => Math.max(max, candidate.headingQueryTerms.size), 0);
769
- const selectionCandidates = options.preferBestLeafHeadingMatch && maxHeadingTermCount > 0
770
- ? candidates.filter((candidate) => candidate.headingQueryTerms.size === maxHeadingTermCount)
771
- : candidates;
772
- const hasQuerySignal = queryTerms.length > 0 && selectionCandidates.some((candidate) => (candidate.queryTerms.size > 0 || candidate.headingQueryTerms.size > 0));
773
- const orderedCandidates = hasQuerySignal
774
- ? rankRagEvidenceSentenceCandidates(selectionCandidates, limit, options)
775
- : selectionCandidates;
776
- orderedCandidates.forEach((candidate) => {
777
- if (selected.length >= limit) {
778
- return;
779
- }
780
- const comparable = sentenceComparableKey(candidate.sentence);
781
- if (!selected.some((existing) => sentenceComparableKey(existing) === comparable)) {
782
- selected.push(candidate.sentence);
783
- }
784
- });
785
- return selected;
786
- }
787
- function buildRagAugmentedConversationAnswer(params, graphContext, useChinese) {
896
+ function buildCoverageDrivenRagAnswer(params, graphContext, graphAnswerPlan, useChinese) {
788
897
  const pack = params.ragContextPack;
789
898
  if (!pack || !Array.isArray(pack.fragments) || pack.fragments.length <= 0) {
790
899
  return '';
@@ -792,63 +901,62 @@ function buildRagAugmentedConversationAnswer(params, graphContext, useChinese) {
792
901
  if (params.ragSufficiencyReview?.status === 'insufficient') {
793
902
  return '';
794
903
  }
795
- const profile = resolveRagAnswerProfile(params.message);
796
- const intent = classifyScopedConversationIntent(params.message);
797
- const answerQueryTerms = extractRagAnswerQueryTerms(params.message);
798
- const rejectCompareProcedureSentence = intent === 'compare'
799
- ? (sentence) => (0, ragPublicText_1.shouldRejectCompareProcedureEvidenceClause)(sentence, params.message)
800
- : undefined;
801
904
  const answerSentences = [];
802
- selectRagEvidenceSentences(pack.fragments, new Set(['direct_support']), profile.directSupportSentenceCount, answerQueryTerms, {
803
- rejectSentence: rejectCompareProcedureSentence,
804
- })
805
- .forEach((sentence) => appendRagEvidenceSentence(answerSentences, sentence, useChinese));
806
- const hasConflictEvidence = params.ragSufficiencyReview?.degradationState === 'conflict'
807
- || (params.ragSufficiencyReview?.reasons || []).some((reason) => String(reason || '').includes('conflict_evidence_present'));
808
- if (hasConflictEvidence) {
809
- selectRagEvidenceSentences(pack.fragments, new Set(['conflict']), 3, answerQueryTerms)
810
- .forEach((sentence) => appendRagEvidenceSentence(answerSentences, sentence, useChinese));
811
- }
812
- selectRagEvidenceSentences(pack.fragments, new Set(['parent_context', 'adjacent_context']), profile.documentContextSentenceCount, answerQueryTerms, {
813
- useLeafHeadingScore: intent !== 'compare',
814
- preferBestLeafHeadingMatch: intent !== 'compare',
815
- rejectSentence: rejectCompareProcedureSentence,
816
- })
817
- .forEach((sentence) => appendRagEvidenceSentence(answerSentences, sentence, useChinese));
818
- selectRagEvidenceSentences(pack.fragments, new Set(['graph_neighbor_support']), profile.graphNeighborSentenceCount, answerQueryTerms, {
819
- rejectSentence: rejectCompareProcedureSentence,
820
- })
821
- .forEach((sentence) => appendRagEvidenceSentence(answerSentences, sentence, useChinese));
905
+ const hasLearningRouteTask = Boolean(graphAnswerPlan.answerTaskPlan?.subtasks.some((subtask) => subtask.kind === 'learning_route'));
906
+ graphAnswerPlan.claims
907
+ .forEach((claim) => appendPlannedEvidenceSentence(answerSentences, claim.statement, useChinese));
908
+ if (answerSentences.length <= 0) {
909
+ return '';
910
+ }
822
911
  if (params.ragSufficiencyReview?.status === 'borderline') {
823
- appendRagEvidenceSentence(answerSentences, useChinese
912
+ appendPlannedEvidenceSentence(answerSentences, useChinese
824
913
  ? '当前证据覆盖仍然有限,因此这只能作为基于已命中材料的部分回答'
825
914
  : 'The available evidence is still partial, so this answer stays within the matched material', useChinese);
826
915
  }
827
- appendConversationAnswerSentence(answerSentences, buildGraphConnectionPathAnswerSentence(graphContext, useChinese), useChinese);
828
- appendConversationAnswerSentence(answerSentences, buildGraphProfileAnswerSentence(graphContext, useChinese), useChinese);
829
- return answerSentences.slice(0, profile.publicSentenceCount).join(useChinese ? '' : ' ');
916
+ if (!hasLearningRouteTask) {
917
+ appendConversationAnswerSentence(answerSentences, buildGraphConnectionPathAnswerSentence(graphContext, useChinese), useChinese);
918
+ appendConversationAnswerSentence(answerSentences, buildGraphProfileAnswerSentence(graphContext, useChinese), useChinese);
919
+ }
920
+ return (0, answerTaskPlan_1.formatTaskAwareAnswer)(answerSentences, graphAnswerPlan.answerTaskPlan, useChinese);
830
921
  }
831
- function buildScopedConversationAnswer(params, graphContext) {
922
+ function buildScopedConversationAnswer(params, graphContext, graphAnswerPlan) {
923
+ if (isFullResponseMode(params)) {
924
+ return buildFullTechnicalReport(params, graphContext, graphAnswerPlan);
925
+ }
926
+ const useChinese = useChineseAnswerLanguage(params);
832
927
  if (params.knowledgePoints.length <= 0) {
833
- const readinessMessage = String(params.usedScope.readiness?.message || '').trim();
834
- const missMessage = String(params.usedScope.missDiagnostics?.message || '').trim();
928
+ const query = normalizeWhitespace(String(params.message || '')) || (useChinese ? '当前问题' : 'your query');
835
929
  if (params.recalledMemories.length > 0) {
836
- return `No scoped knowledge points matched "${params.message || 'your query'}", but I recovered ${params.recalledMemories.length} relevant conversation memory note(s). ${missMessage || readinessMessage || 'Refine the corpus scope or use the recalled memory as a follow-up anchor.'}`;
930
+ return useChinese
931
+ ? `当前范围没有匹配“${query}”的知识点,但已找回 ${params.recalledMemories.length} 条相关会话记忆。请调整知识范围,或将这条记忆作为后续检索锚点。`
932
+ : `No scoped knowledge points matched "${query}", but I recovered ${params.recalledMemories.length} relevant conversation memory note(s). Refine the corpus scope or use the recalled memory as a follow-up anchor.`;
837
933
  }
838
- return `No scoped knowledge points matched "${params.message || 'your query'}". ${missMessage || readinessMessage || 'Refine the scope, add more notes to the corpus, or broaden the query terms.'}`;
934
+ return useChinese
935
+ ? `当前范围没有匹配“${query}”的知识点。请调整范围、补充笔记,或使用更具体的检索词。`
936
+ : `No scoped knowledge points matched "${query}". Refine the scope, add more notes to the corpus, or broaden the query terms.`;
839
937
  }
840
938
  const leadingPoint = params.knowledgePoints[0];
841
- const useChinese = containsCjk([
842
- params.message,
843
- leadingPoint.title,
844
- leadingPoint.summary,
845
- graphContext && graphContext.anchorTitle,
846
- ].filter(Boolean).join(' '));
847
939
  const answerSentences = [];
848
- const ragAnswer = buildRagAugmentedConversationAnswer(params, graphContext, useChinese);
940
+ const ragAnswer = buildCoverageDrivenRagAnswer(params, graphContext, graphAnswerPlan, useChinese);
849
941
  if (ragAnswer) {
850
942
  return ragAnswer;
851
943
  }
944
+ if (graphAnswerPlan.claims.length > 0) {
945
+ const hasLearningRouteTask = Boolean(graphAnswerPlan.answerTaskPlan?.subtasks.some((subtask) => subtask.kind === 'learning_route'));
946
+ graphAnswerPlan.claims.forEach((claim) => {
947
+ appendConversationAnswerSentence(answerSentences, claim.statement, useChinese);
948
+ });
949
+ if (params.ragSufficiencyReview?.status === 'borderline') {
950
+ appendConversationAnswerSentence(answerSentences, useChinese
951
+ ? '当前证据覆盖仍然有限,因此回答只陈述已有材料能够支持的内容'
952
+ : 'The evidence coverage is still partial, so the answer stays within the retrieved material', useChinese);
953
+ }
954
+ if (!hasLearningRouteTask) {
955
+ appendConversationAnswerSentence(answerSentences, buildGraphConnectionPathAnswerSentence(graphContext, useChinese), useChinese);
956
+ appendConversationAnswerSentence(answerSentences, buildGraphProfileAnswerSentence(graphContext, useChinese), useChinese);
957
+ }
958
+ return (0, answerTaskPlan_1.formatTaskAwareAnswer)(answerSentences, graphAnswerPlan.answerTaskPlan, useChinese);
959
+ }
852
960
  const directSentence = selectScopedConversationDirectSentence(params.message, leadingPoint);
853
961
  if (directSentence) {
854
962
  appendConversationAnswerSentence(answerSentences, directSentence, useChinese);
@@ -868,94 +976,150 @@ function buildScopedConversationAnswer(params, graphContext) {
868
976
  if (answerSentences.length < 2 && params.knowledgePoints.length > 1) {
869
977
  appendConversationAnswerSentence(answerSentences, selectScopedConversationDirectSentence(params.message, params.knowledgePoints[1]), useChinese);
870
978
  }
871
- return answerSentences.slice(0, 3).join(useChinese ? '' : ' ');
979
+ return answerSentences.join(useChinese ? '' : ' ');
872
980
  }
873
981
  function buildScopedConversationOverviewMarkdown(params, graphContext) {
982
+ const useChinese = useChineseAnswerLanguage(params);
874
983
  const strongestPoint = params.knowledgePoints[0];
875
984
  const lines = [
876
- '## Answer Context',
985
+ useChinese ? '## 回答上下文' : '## Answer Context',
877
986
  '',
878
987
  ];
879
988
  if (strongestPoint) {
880
- lines.push(`Best scoped anchor: **${strongestPoint.title}**.`, '');
989
+ lines.push(useChinese
990
+ ? `当前最佳范围锚点:**${strongestPoint.title}**。`
991
+ : `Best scoped anchor: **${strongestPoint.title}**.`, '');
881
992
  }
882
993
  else {
883
- lines.push('No scoped knowledge point produced a strong match for the current request.', '');
884
- }
885
- lines.push(`- Relevant knowledge points: **${params.knowledgePoints.length}**`, `- Citations returned: **${params.citations.length}**`, `- Scoped memories recalled: **${params.recalledMemories.length}**`);
994
+ lines.push(useChinese
995
+ ? '当前请求没有得到足够强的范围内知识点匹配。'
996
+ : 'No scoped knowledge point produced a strong match for the current request.', '');
997
+ }
998
+ lines.push(...(useChinese
999
+ ? [
1000
+ `- 相关知识点:**${params.knowledgePoints.length}**`,
1001
+ `- 返回引用:**${params.citations.length}**`,
1002
+ `- 已召回范围内记忆:**${params.recalledMemories.length}**`,
1003
+ ]
1004
+ : [
1005
+ `- Relevant knowledge points: **${params.knowledgePoints.length}**`,
1006
+ `- Citations returned: **${params.citations.length}**`,
1007
+ `- Scoped memories recalled: **${params.recalledMemories.length}**`,
1008
+ ]));
886
1009
  if (graphContext && graphContext.relationKinds.length > 0) {
887
- lines.push(`- Graph-supported relations: **${graphContext.relationKinds.join(', ')}**`);
1010
+ lines.push(useChinese
1011
+ ? `- 图谱支持关系:**${graphContext.relationKinds.join(', ')}**`
1012
+ : `- Graph-supported relations: **${graphContext.relationKinds.join(', ')}**`);
888
1013
  }
889
1014
  if (graphContext) {
890
- lines.push(`- Temporal validity: **${graphContext.temporalValidity.allPointsValid ? 'valid' : 'warning'}**`);
1015
+ lines.push(useChinese
1016
+ ? `- 时序有效性:**${graphContext.temporalValidity.allPointsValid ? '有效' : '警告'}**`
1017
+ : `- Temporal validity: **${graphContext.temporalValidity.allPointsValid ? 'valid' : 'warning'}**`);
891
1018
  }
892
1019
  if (graphContext && Array.isArray(graphContext.connectionPaths) && graphContext.connectionPaths.length > 0) {
893
- lines.push(`- Explicit connection paths: **${graphContext.connectionPaths.length}**`);
1020
+ lines.push(useChinese
1021
+ ? `- 显式连接路径:**${graphContext.connectionPaths.length}**`
1022
+ : `- Explicit connection paths: **${graphContext.connectionPaths.length}**`);
894
1023
  }
895
1024
  if (graphContext && Array.isArray(graphContext.predecessorWindow) && graphContext.predecessorWindow.length > 0) {
896
- lines.push(`- Immediate predecessors: **${graphContext.predecessorWindow.length}**`);
1025
+ lines.push(useChinese
1026
+ ? `- 直接前置节点:**${graphContext.predecessorWindow.length}**`
1027
+ : `- Immediate predecessors: **${graphContext.predecessorWindow.length}**`);
897
1028
  }
898
1029
  if (graphContext && Array.isArray(graphContext.successorWindow) && graphContext.successorWindow.length > 0) {
899
- lines.push(`- Immediate successors: **${graphContext.successorWindow.length}**`);
1030
+ lines.push(useChinese
1031
+ ? `- 直接后继节点:**${graphContext.successorWindow.length}**`
1032
+ : `- Immediate successors: **${graphContext.successorWindow.length}**`);
900
1033
  }
901
1034
  return lines.join('\n');
902
1035
  }
903
1036
  function buildScopedConversationExplanationMarkdown(params, graphContext) {
1037
+ const useChinese = useChineseAnswerLanguage(params);
904
1038
  if (params.knowledgePoints.length <= 0) {
905
- return '## Explanation\n\nThe current scope did not return a strong enough knowledge point to explain the request directly.';
1039
+ return useChinese
1040
+ ? '## 说明\n\n当前范围没有返回足够强的知识点来直接解释该请求。'
1041
+ : '## Explanation\n\nThe current scope did not return a strong enough knowledge point to explain the request directly.';
906
1042
  }
907
1043
  const intent = classifyScopedConversationIntent(params.message);
908
1044
  const strongestPoint = params.knowledgePoints[0];
909
1045
  const explanationLines = [
910
- '## Explanation',
1046
+ useChinese ? '## 说明' : '## Explanation',
911
1047
  '',
912
1048
  ];
913
1049
  if (intent === 'compare') {
914
- explanationLines.push(`Use **${strongestPoint.title}** as the comparison baseline inside the current scope.`);
1050
+ explanationLines.push(useChinese
1051
+ ? `将 **${strongestPoint.title}** 作为当前范围内的比较基准。`
1052
+ : `Use **${strongestPoint.title}** as the comparison baseline inside the current scope.`);
915
1053
  }
916
1054
  else if (intent === 'how_to') {
917
- explanationLines.push(`Use **${strongestPoint.title}** as the starting anchor for the next concrete steps.`);
1055
+ explanationLines.push(useChinese
1056
+ ? `将 **${strongestPoint.title}** 作为后续具体步骤的起始锚点。`
1057
+ : `Use **${strongestPoint.title}** as the starting anchor for the next concrete steps.`);
918
1058
  }
919
1059
  else if (intent === 'explain') {
920
- explanationLines.push(`**${strongestPoint.title}** is the current best scoped anchor for the explanation.`);
1060
+ explanationLines.push(useChinese
1061
+ ? `**${strongestPoint.title}** 是当前用于说明的最佳范围锚点。`
1062
+ : `**${strongestPoint.title}** is the current best scoped anchor for the explanation.`);
921
1063
  }
922
1064
  else {
923
- explanationLines.push(`**${strongestPoint.title}** is the current best scoped anchor.`);
1065
+ explanationLines.push(useChinese
1066
+ ? `**${strongestPoint.title}** 是当前最佳范围锚点。`
1067
+ : `**${strongestPoint.title}** is the current best scoped anchor.`);
924
1068
  }
925
1069
  const summary = normalizeWhitespace(String(strongestPoint.summary || strongestPoint.evidenceSnippet || '').trim());
926
1070
  if (summary) {
927
1071
  explanationLines.push('', summary);
928
1072
  }
929
1073
  if (graphContext && graphContext.relationKinds.length > 0) {
930
- explanationLines.push('', `Graph support around **${graphContext.anchorTitle}** includes: ${graphContext.relationKinds.join(', ')}.`);
1074
+ explanationLines.push('', useChinese
1075
+ ? `**${graphContext.anchorTitle}** 周边的图谱支持关系包括:${graphContext.relationKinds.join(', ')}。`
1076
+ : `Graph support around **${graphContext.anchorTitle}** includes: ${graphContext.relationKinds.join(', ')}.`);
931
1077
  }
932
1078
  if (graphContext && Array.isArray(graphContext.knowledgePointRelations) && graphContext.knowledgePointRelations.length > 0) {
933
1079
  const relationPreview = graphContext.knowledgePointRelations
934
1080
  .slice(0, 2)
935
1081
  .map((relation) => `${relation.sourceTitle} -> ${relation.relationKind} -> ${relation.targetTitle}`)
936
1082
  .join('; ');
937
- explanationLines.push('', `Direct graph links inside the current result set: ${relationPreview}.`);
1083
+ explanationLines.push('', useChinese
1084
+ ? `当前结果集内的直接图链接:${relationPreview}。`
1085
+ : `Direct graph links inside the current result set: ${relationPreview}.`);
938
1086
  }
939
1087
  if (graphContext && Array.isArray(graphContext.connectionPaths) && graphContext.connectionPaths.length > 0) {
940
- explanationLines.push('', `Explicit graph path: ${formatGraphConnectionPath(graphContext.connectionPaths[0])}.`);
1088
+ explanationLines.push('', useChinese
1089
+ ? `显式图路径:${formatGraphConnectionPath(graphContext.connectionPaths[0])}。`
1090
+ : `Explicit graph path: ${formatGraphConnectionPath(graphContext.connectionPaths[0])}.`);
941
1091
  }
942
1092
  if (graphContext) {
943
- const predecessorTitles = collectGraphWindowTitles(graphContext, 'predecessorWindow', 3);
1093
+ const predecessorTitles = (0, graphAnswerFacts_1.collectGraphAnswerWindowTitles)(graphContext, 'predecessorWindow', {
1094
+ anchorAtomId: graphContext.anchorAtomId,
1095
+ anchorTitle: graphContext.anchorTitle,
1096
+ normalizeTitle: (value) => normalizeGraphAnswerDisplayTitle(String(value || '')),
1097
+ }, 3);
944
1098
  if (predecessorTitles.length > 0) {
945
- explanationLines.push('', 'Immediate predecessor window: ' + predecessorTitles.join(', ') + '.');
1099
+ explanationLines.push('', useChinese
1100
+ ? '直接前置窗口:' + predecessorTitles.join('、') + '。'
1101
+ : 'Immediate predecessor window: ' + predecessorTitles.join(', ') + '.');
946
1102
  }
947
1103
  }
948
1104
  if (graphContext) {
949
- const successorTitles = collectGraphWindowTitles(graphContext, 'successorWindow', 3);
1105
+ const successorTitles = (0, graphAnswerFacts_1.collectGraphAnswerWindowTitles)(graphContext, 'successorWindow', {
1106
+ anchorAtomId: graphContext.anchorAtomId,
1107
+ anchorTitle: graphContext.anchorTitle,
1108
+ normalizeTitle: (value) => normalizeGraphAnswerDisplayTitle(String(value || '')),
1109
+ }, 3);
950
1110
  if (successorTitles.length > 0) {
951
- explanationLines.push('', 'Immediate successor window: ' + successorTitles.join(', ') + '.');
1111
+ explanationLines.push('', useChinese
1112
+ ? '直接后继窗口:' + successorTitles.join('、') + '。'
1113
+ : 'Immediate successor window: ' + successorTitles.join(', ') + '.');
952
1114
  }
953
1115
  }
954
1116
  if (graphContext && graphContext.temporalValidity.allPointsValid === false) {
955
1117
  const reasonSummary = graphContext.temporalValidity.warningReasons.length > 0
956
1118
  ? graphContext.temporalValidity.warningReasons.join(', ')
957
1119
  : 'temporal validity checks reported a warning';
958
- explanationLines.push('', `Temporal validity warning: ${reasonSummary}.`);
1120
+ explanationLines.push('', useChinese
1121
+ ? `时序有效性警告:${reasonSummary}。`
1122
+ : `Temporal validity warning: ${reasonSummary}.`);
959
1123
  }
960
1124
  const supersedesCount = graphContext
961
1125
  ? (Array.isArray(graphContext.temporalValidity.details)
@@ -963,48 +1127,70 @@ function buildScopedConversationExplanationMarkdown(params, graphContext) {
963
1127
  : 0)
964
1128
  : 0;
965
1129
  if (supersedesCount > 0) {
966
- explanationLines.push('', `Temporal lineage indicates this anchor supersedes ${supersedesCount} earlier revision${supersedesCount === 1 ? '' : 's'}.`);
1130
+ explanationLines.push('', useChinese
1131
+ ? `时序谱系表明该锚点替代了 ${supersedesCount} 个更早版本。`
1132
+ : `Temporal lineage indicates this anchor supersedes ${supersedesCount} earlier revision${supersedesCount === 1 ? '' : 's'}.`);
967
1133
  }
968
1134
  const supportingTitles = params.knowledgePoints
969
1135
  .slice(1, 3)
970
1136
  .map((point) => normalizeWhitespace(String(point.title || '').trim()))
971
1137
  .filter(Boolean);
972
1138
  if (supportingTitles.length > 0) {
973
- explanationLines.push('', intent === 'compare'
974
- ? `Supporting comparison nodes: ${supportingTitles.join(', ')}.`
975
- : `Supporting scoped nodes: ${supportingTitles.join(', ')}.`);
1139
+ explanationLines.push('', useChinese
1140
+ ? (intent === 'compare'
1141
+ ? `支持比较节点:${supportingTitles.join('、')}。`
1142
+ : `支持范围节点:${supportingTitles.join('、')}。`)
1143
+ : (intent === 'compare'
1144
+ ? `Supporting comparison nodes: ${supportingTitles.join(', ')}.`
1145
+ : `Supporting scoped nodes: ${supportingTitles.join(', ')}.`));
976
1146
  }
977
1147
  if (params.recalledMemories.length > 0) {
978
- explanationLines.push('', `Scoped memory recall contributed ${params.recalledMemories.length} prior note(s) to this explanation.`);
1148
+ explanationLines.push('', useChinese
1149
+ ? `范围内记忆召回为该说明提供了 ${params.recalledMemories.length} 条先前笔记。`
1150
+ : `Scoped memory recall contributed ${params.recalledMemories.length} prior note(s) to this explanation.`);
979
1151
  }
980
1152
  if (params.citations.length > 0) {
981
- explanationLines.push('', `The explanation is grounded by ${params.citations.length} citation(s) from the current scope.`);
1153
+ explanationLines.push('', useChinese
1154
+ ? `该说明由当前范围内的 ${params.citations.length} 条引用支撑。`
1155
+ : `The explanation is grounded by ${params.citations.length} citation(s) from the current scope.`);
982
1156
  }
983
1157
  return explanationLines.join('\n');
984
1158
  }
985
1159
  function buildScopedConversationEvidenceMarkdown(params) {
986
1160
  const evidenceLines = params.citations.slice(0, 3).map((citation, index) => (`${index + 1}. **${citation.title}** (${citation.sourcePath}${citation.startLine ? `:${citation.startLine}` : ''})\n - ${citation.snippet}`));
987
1161
  if (evidenceLines.length <= 0) {
988
- return '## Evidence Summary\n\nNo scoped citations were returned.';
1162
+ return useChineseAnswerLanguage(params)
1163
+ ? '## 证据摘要\n\n当前范围没有返回引用。'
1164
+ : '## Evidence Summary\n\nNo scoped citations were returned.';
989
1165
  }
990
1166
  return [
991
- '## Evidence Summary',
1167
+ useChineseAnswerLanguage(params) ? '## 证据摘要' : '## Evidence Summary',
992
1168
  '',
993
1169
  ...evidenceLines,
994
1170
  ].join('\n');
995
1171
  }
996
1172
  function buildScopedConversationMemoryNotice(params) {
1173
+ const useChinese = useChineseAnswerLanguage(params);
997
1174
  if (params.recalledMemories.length <= 0) {
998
- return 'No scoped memory note was recalled for this turn.';
1175
+ return useChinese
1176
+ ? '本回合未召回范围内记忆。'
1177
+ : 'No scoped memory note was recalled for this turn.';
999
1178
  }
1000
1179
  if (params.recalledMemories.length === 1) {
1001
- return '1 scoped memory note was recalled and merged into the answer context.';
1180
+ return useChinese
1181
+ ? '已召回 1 条范围内记忆并合并到回答上下文。'
1182
+ : '1 scoped memory note was recalled and merged into the answer context.';
1002
1183
  }
1003
- return `${params.recalledMemories.length} scoped memory notes were recalled and merged into the answer context.`;
1184
+ return useChinese
1185
+ ? `已召回 ${params.recalledMemories.length} 条范围内记忆并合并到回答上下文。`
1186
+ : `${params.recalledMemories.length} scoped memory notes were recalled and merged into the answer context.`;
1004
1187
  }
1005
- function buildScopedConversationActionGuideMarkdown(params, graphContext) {
1188
+ function buildScopedConversationActionGuideMarkdown(params, graphContext, answerTaskPlan) {
1189
+ const useChinese = useChineseAnswerLanguage(params);
1006
1190
  if (params.knowledgePoints.length <= 0) {
1007
- return '## Next Actions\n\nNo actionable scoped knowledge card is available for this turn.';
1191
+ return useChinese
1192
+ ? '## 下一步行动\n\n当前回合没有可执行的限定知识卡片。'
1193
+ : '## Next Actions\n\nNo actionable scoped knowledge card is available for this turn.';
1008
1194
  }
1009
1195
  const intent = classifyScopedConversationIntent(params.message);
1010
1196
  const topTitles = params.knowledgePoints
@@ -1012,15 +1198,23 @@ function buildScopedConversationActionGuideMarkdown(params, graphContext) {
1012
1198
  .map((point) => `- ${point.title}`);
1013
1199
  const actionHints = params.memoryActions
1014
1200
  .slice(0, 2)
1015
- .map((action) => normalizeWhitespace(String(action.reason || '').trim()))
1201
+ .map((action) => useChinese
1202
+ ? (action.kind === 'persist_session_memory' ? '- 记录本回合的用户关注点。' : '- 根据当前证据评估是否需要保存学习记忆。')
1203
+ : normalizeWhitespace(String(action.reason || '').trim()))
1016
1204
  .filter(Boolean)
1017
1205
  .map((reason) => `- ${reason}`);
1018
1206
  const graphActionHints = [];
1207
+ const learningRoute = answerTaskPlan?.learningRoute || [];
1208
+ if (answerTaskPlan?.subtasks.some((subtask) => subtask.kind === 'learning_route') && learningRoute.length > 0) {
1209
+ graphActionHints.push(useChinese
1210
+ ? `- 建议按以下顺序学习:${learningRoute.map((node) => `${node.order}. ${node.title}`).join(' -> ')}。`
1211
+ : `- Follow the evidence-backed learning order: ${learningRoute.map((node) => `${node.order}. ${node.title}`).join(' -> ')}.`);
1212
+ }
1019
1213
  if (graphContext && graphContext.relationKinds.includes('prerequisite')) {
1020
- graphActionHints.push('- Inspect prerequisite-linked concepts in focus mode before guided learning.');
1214
+ graphActionHints.push(useChinese ? '- 在进入引导学习前,先在聚焦模式查看前置概念。' : '- Inspect prerequisite-linked concepts in focus mode before guided learning.');
1021
1215
  }
1022
1216
  if (graphContext && graphContext.temporalValidity.allPointsValid === false) {
1023
- graphActionHints.push('- Validate whether a fresher or superseding note should replace this anchor before promotion.');
1217
+ graphActionHints.push(useChinese ? '- 提升答案前,确认是否存在更新或替代当前锚点的笔记。' : '- Validate whether a fresher or superseding note should replace this anchor before promotion.');
1024
1218
  }
1025
1219
  const supersedesCount = graphContext
1026
1220
  ? (Array.isArray(graphContext.temporalValidity.details)
@@ -1028,11 +1222,13 @@ function buildScopedConversationActionGuideMarkdown(params, graphContext) {
1028
1222
  : 0)
1029
1223
  : 0;
1030
1224
  if (supersedesCount > 0) {
1031
- graphActionHints.push('- Trace the superseded lineage before promoting this answer.');
1225
+ graphActionHints.push(useChinese ? '- 提升答案前,追踪已被替代的知识谱系。' : '- Trace the superseded lineage before promoting this answer.');
1032
1226
  }
1033
1227
  if (graphContext && Array.isArray(graphContext.knowledgePointRelations) && graphContext.knowledgePointRelations.length > 0) {
1034
1228
  const firstRelation = graphContext.knowledgePointRelations[0];
1035
- graphActionHints.push(`- Follow the direct graph path between ${firstRelation.sourceTitle} and ${firstRelation.targetTitle} before branching to external support nodes.`);
1229
+ graphActionHints.push(useChinese
1230
+ ? `- 在扩展到外部支持节点前,沿 ${firstRelation.sourceTitle} 与 ${firstRelation.targetTitle} 之间的直接图路径查看。`
1231
+ : `- Follow the direct graph path between ${firstRelation.sourceTitle} and ${firstRelation.targetTitle} before branching to external support nodes.`);
1036
1232
  }
1037
1233
  if (graphContext && Array.isArray(graphContext.connectionPaths) && graphContext.connectionPaths.length > 0) {
1038
1234
  const firstConnectionPath = graphContext.connectionPaths[0];
@@ -1040,33 +1236,45 @@ function buildScopedConversationActionGuideMarkdown(params, graphContext) {
1040
1236
  ? firstConnectionPath.pathTitles.map((title) => normalizeWhitespace(String(title || '').trim())).filter(Boolean)
1041
1237
  : [];
1042
1238
  if (titles.length > 1) {
1043
- graphActionHints.push(`- Review the path order: ${titles.join(' -> ')}.`);
1239
+ graphActionHints.push(useChinese ? `- 检查路径顺序:${titles.join(' -> ')}。` : `- Review the path order: ${titles.join(' -> ')}.`);
1044
1240
  }
1045
1241
  }
1046
1242
  if (graphContext) {
1047
- const predecessorTitles = collectGraphWindowTitles(graphContext, 'predecessorWindow', 2);
1243
+ const predecessorTitles = (0, graphAnswerFacts_1.collectGraphAnswerWindowTitles)(graphContext, 'predecessorWindow', {
1244
+ anchorAtomId: graphContext.anchorAtomId,
1245
+ anchorTitle: graphContext.anchorTitle,
1246
+ normalizeTitle: (value) => normalizeGraphAnswerDisplayTitle(String(value || '')),
1247
+ }, 2);
1048
1248
  if (predecessorTitles.length > 0) {
1049
- graphActionHints.push('- Inspect prerequisite context from ' + predecessorTitles.join(', ') + ' before expanding the answer.');
1249
+ graphActionHints.push(useChinese
1250
+ ? '- 扩展答案前,先查看前置上下文:' + predecessorTitles.join('、') + '。'
1251
+ : '- Inspect prerequisite context from ' + predecessorTitles.join(', ') + ' before expanding the answer.');
1050
1252
  }
1051
1253
  }
1052
1254
  if (graphContext) {
1053
- const successorTitles = collectGraphWindowTitles(graphContext, 'successorWindow', 2);
1255
+ const successorTitles = (0, graphAnswerFacts_1.collectGraphAnswerWindowTitles)(graphContext, 'successorWindow', {
1256
+ anchorAtomId: graphContext.anchorAtomId,
1257
+ anchorTitle: graphContext.anchorTitle,
1258
+ normalizeTitle: (value) => normalizeGraphAnswerDisplayTitle(String(value || '')),
1259
+ }, 2);
1054
1260
  if (successorTitles.length > 0) {
1055
- graphActionHints.push('- Use likely next-step nodes such as ' + successorTitles.join(', ') + ' to continue follow-through after this answer.');
1261
+ graphActionHints.push(useChinese
1262
+ ? '- 可沿后续节点继续:' + successorTitles.join('、') + '。'
1263
+ : '- Use likely next-step nodes such as ' + successorTitles.join(', ') + ' to continue follow-through after this answer.');
1056
1264
  }
1057
1265
  }
1058
1266
  return [
1059
- '## Next Actions',
1267
+ useChinese ? '## 下一步行动' : '## Next Actions',
1060
1268
  '',
1061
1269
  intent === 'compare'
1062
- ? 'Use the scoped knowledge cards below to inspect the strongest nodes side by side before deciding which distinctions matter most:'
1270
+ ? (useChinese ? '先查看下方限定知识卡片中的高相关节点,再判断关键差异:' : 'Use the scoped knowledge cards below to inspect the strongest nodes side by side before deciding which distinctions matter most:')
1063
1271
  : intent === 'how_to'
1064
- ? 'Use the scoped knowledge cards below to move from explanation into concrete guided-learning or focus-mode steps:'
1065
- : 'Use the scoped knowledge cards below to continue with focus mode or guided learning for the highest-signal nodes:',
1272
+ ? (useChinese ? '使用下方限定知识卡片,将解释推进为具体的引导学习或聚焦步骤:' : 'Use the scoped knowledge cards below to move from explanation into concrete guided-learning or focus-mode steps:')
1273
+ : (useChinese ? '使用下方限定知识卡片,继续聚焦或引导学习高相关节点:' : 'Use the scoped knowledge cards below to continue with focus mode or guided learning for the highest-signal nodes:'),
1066
1274
  ...topTitles,
1067
- ...(graphActionHints.length > 0 ? ['', 'Graph-aware follow-through:', ...graphActionHints] : []),
1275
+ ...(graphActionHints.length > 0 ? ['', useChinese ? '图谱辅助行动:' : 'Graph-aware follow-through:', ...graphActionHints] : []),
1068
1276
  ...(actionHints.length > 0
1069
- ? ['', 'Suggested follow-through from the current turn:', ...actionHints]
1277
+ ? ['', useChinese ? '本回合建议行动:' : 'Suggested follow-through from the current turn:', ...actionHints]
1070
1278
  : []),
1071
1279
  ].join('\n');
1072
1280
  }
@@ -1408,10 +1616,22 @@ function buildKnowledgeRun(params, graphContext) {
1408
1616
  function buildScopedConversationReply(params) {
1409
1617
  const blocks = [];
1410
1618
  const graphContext = params.graphContext || (0, graphContextAssembler_1.buildAgentConversationGraphContextFromKnowledgePoints)(params.knowledgePoints);
1411
- const draftAnswer = buildScopedConversationAnswer(params, graphContext);
1619
+ const auditGraphAnswerPlan = (0, graphAnswerPlan_1.buildGraphAnswerPlan)({
1620
+ message: params.message,
1621
+ knowledgePoints: params.knowledgePoints,
1622
+ graphContext,
1623
+ ragContextPack: params.ragContextPack,
1624
+ });
1625
+ const fullReportAssembly = params.responseMode === 'full'
1626
+ ? buildFullTechnicalReportResult(params, graphContext, auditGraphAnswerPlan)
1627
+ : undefined;
1628
+ const draftAnswer = fullReportAssembly?.answer
1629
+ || buildScopedConversationAnswer(params, graphContext, auditGraphAnswerPlan);
1412
1630
  const knowledgeRun = buildKnowledgeRun(params, graphContext);
1413
1631
  const answerReleaseReview = (0, answerReleaseReview_1.reviewAnswerRelease)({
1414
1632
  message: params.message,
1633
+ answerLanguage: params.answerLanguage,
1634
+ responseMode: params.responseMode === 'full' ? 'full' : 'slim',
1415
1635
  draftAnswer,
1416
1636
  knowledgePoints: params.knowledgePoints,
1417
1637
  citations: params.citations,
@@ -1419,20 +1639,38 @@ function buildScopedConversationReply(params) {
1419
1639
  graphContext,
1420
1640
  ragContextPack: params.ragContextPack,
1421
1641
  ragSufficiencyReview: params.ragSufficiencyReview,
1642
+ graphAnswerPlan: auditGraphAnswerPlan,
1422
1643
  reviewedAt: params.generatedAt,
1423
1644
  });
1424
1645
  knowledgeRun.answerReleaseReview = answerReleaseReview;
1425
1646
  const answer = answerReleaseReview.publicAnswer;
1647
+ const publicGraphAnswerPlan = answerReleaseReview.publicGraphAnswerPlan;
1648
+ const releasedGraphAnswerPlan = publicGraphAnswerPlan || {
1649
+ ...auditGraphAnswerPlan,
1650
+ leadClaimId: '',
1651
+ claims: [],
1652
+ requiredRoles: [],
1653
+ depth: 'compact',
1654
+ };
1655
+ const graphAnswerCoverage = answerReleaseReview.graphAnswerCoverage
1656
+ || (0, graphAnswerCoverage_1.reviewGraphAnswerCoverage)(answer, publicGraphAnswerPlan || null);
1657
+ knowledgeRun.graphAnswerPlan = releasedGraphAnswerPlan;
1658
+ knowledgeRun.graphAnswerCoverage = graphAnswerCoverage;
1659
+ knowledgeRun.answerTaskPlan = releasedGraphAnswerPlan.answerTaskPlan;
1660
+ knowledgeRun.answerTaskCoverage = answerReleaseReview.answerTaskCoverage;
1661
+ const useChinese = useChineseAnswerLanguage(params);
1426
1662
  const overviewMarkdown = buildScopedConversationOverviewMarkdown(params, graphContext);
1427
1663
  const explanationMarkdown = buildScopedConversationExplanationMarkdown(params, graphContext);
1428
1664
  const evidenceMarkdown = buildScopedConversationEvidenceMarkdown(params);
1429
1665
  const memoryNotice = buildScopedConversationMemoryNotice(params);
1430
- const actionGuideMarkdown = buildScopedConversationActionGuideMarkdown(params, graphContext);
1666
+ const actionGuideMarkdown = buildScopedConversationActionGuideMarkdown(params, graphContext, releasedGraphAnswerPlan.answerTaskPlan);
1431
1667
  blocks.push({
1432
1668
  blockId: params.nextBlockId(),
1433
1669
  type: 'structured_answer',
1434
- title: 'Grounded Answer',
1670
+ title: useChinese ? '可信回答' : 'Grounded Answer',
1435
1671
  directAnswer: answer,
1672
+ answerTaskPlan: releasedGraphAnswerPlan.answerTaskPlan,
1673
+ answerTaskCoverage: answerReleaseReview.answerTaskCoverage,
1436
1674
  overviewMarkdown,
1437
1675
  explanationMarkdown,
1438
1676
  evidenceMarkdown,
@@ -1444,7 +1682,7 @@ function buildScopedConversationReply(params) {
1444
1682
  blocks.push({
1445
1683
  blockId: params.nextBlockId(),
1446
1684
  type: 'knowledge_run_summary',
1447
- title: 'Knowledge Run',
1685
+ title: useChinese ? '知识运行' : 'Knowledge Run',
1448
1686
  knowledgeRun,
1449
1687
  });
1450
1688
  if (memoryNotice) {
@@ -1458,7 +1696,7 @@ function buildScopedConversationReply(params) {
1458
1696
  blocks.push({
1459
1697
  blockId: params.nextBlockId(),
1460
1698
  type: 'citations',
1461
- title: 'Citations',
1699
+ title: useChinese ? '引用' : 'Citations',
1462
1700
  citations: params.citations.map((citation) => ({ ...citation })),
1463
1701
  });
1464
1702
  }
@@ -1466,7 +1704,7 @@ function buildScopedConversationReply(params) {
1466
1704
  blocks.push({
1467
1705
  blockId: params.nextBlockId(),
1468
1706
  type: 'knowledge_actions',
1469
- title: 'Knowledge Actions',
1707
+ title: useChinese ? '知识操作' : 'Knowledge Actions',
1470
1708
  atomIds: collectAgentConversationAtomIds(params.knowledgePoints),
1471
1709
  });
1472
1710
  }
@@ -1476,5 +1714,8 @@ function buildScopedConversationReply(params) {
1476
1714
  knowledgeRun,
1477
1715
  graphContext,
1478
1716
  answerReleaseReview,
1717
+ graphAnswerPlan: releasedGraphAnswerPlan,
1718
+ graphAnswerCoverage,
1719
+ ...(fullReportAssembly ? { fullReportAssembly: fullReportAssembly.state } : {}),
1479
1720
  };
1480
1721
  }