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
@@ -384,6 +384,51 @@ describe('conversationComposer', () => {
384
384
  decision: 'abstain',
385
385
  }));
386
386
  });
387
+ test('uses the explicit Chinese answer language for an English no-match query', () => {
388
+ let blockCounter = 0;
389
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
390
+ message: 'what is water glass?',
391
+ answerLanguage: 'zh',
392
+ knowledgePoints: [],
393
+ citations: [],
394
+ recalledMemories: [],
395
+ memoryActions: [],
396
+ usedScope: {
397
+ ...globalScope,
398
+ source: 'scoped',
399
+ workspaceId: 'waterglass',
400
+ corpusId: 'waterglass',
401
+ sourcePathPrefixes: ['Knowledge_Base/waterglass'],
402
+ readiness: {
403
+ status: 'ready',
404
+ message: 'The scoped learning workspace is ready.',
405
+ workspaceId: 'waterglass',
406
+ corpusId: 'waterglass',
407
+ activeResourceCount: 1,
408
+ activeProjectionCount: 1,
409
+ indexedUnitCount: 1,
410
+ indexedSegmentCount: 4,
411
+ matchedDocumentCount: 1,
412
+ },
413
+ missDiagnostics: {
414
+ reason: 'retrieval_candidates_below_threshold',
415
+ message: 'No evidence-bearing candidates were returned.',
416
+ query: 'what is water glass?',
417
+ normalizedQuery: 'what is water glass?',
418
+ titleLikeQueries: ['water glass'],
419
+ titleHitDocumentIds: [],
420
+ indexedScopeAtomCount: 4,
421
+ },
422
+ },
423
+ nextBlockId: () => `language_empty_scope_${++blockCounter}`,
424
+ });
425
+ const structuredAnswer = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
426
+ expect(reply.answer).toContain('当前范围');
427
+ expect(reply.answer).toContain('我暂时不能');
428
+ expect(reply.answer).not.toContain('No scoped knowledge points matched');
429
+ expect(structuredAnswer && 'title' in structuredAnswer ? structuredAnswer.title : '').toBe('可信回答');
430
+ expect(reply.knowledgeRun.answerReleaseReview?.publicAnswer).toBe(reply.answer);
431
+ });
387
432
  test('aggregates graph context across multiple knowledge points and preserves temporal edge details', () => {
388
433
  const knowledgePoints = [
389
434
  {
@@ -996,6 +1041,502 @@ describe('conversationComposer', () => {
996
1041
  const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
997
1042
  expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').toBe(reply.answer);
998
1043
  });
1044
+ test('full response mode releases a bounded technical report while slim remains concise', () => {
1045
+ const item = makeQueryItem({
1046
+ atom: {
1047
+ id: 'atom_full_water_glass',
1048
+ documentId: 'doc_full_water_glass',
1049
+ sourcePath: 'Knowledge_Base/test/full-water-glass.md',
1050
+ title: 'Water Glass',
1051
+ content: 'A water glass is a transparent drinking vessel that contains water.',
1052
+ },
1053
+ evidence: {
1054
+ id: 'evidence_full_water_glass',
1055
+ snippet: 'A water glass is a transparent drinking vessel that contains water.',
1056
+ startLine: 4,
1057
+ endLine: 4,
1058
+ },
1059
+ score: 0.96,
1060
+ });
1061
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1062
+ const citations = knowledgePoints[0].citations || [];
1063
+ const ragContextPack = {
1064
+ query: 'what is water glass?',
1065
+ generatedAt: '2026-09-03T00:00:00.000Z',
1066
+ sourceBoundary: 'full_document',
1067
+ budget: {
1068
+ maxFragments: 8,
1069
+ maxCharsPerFragment: 1600,
1070
+ maxTotalChars: 6400,
1071
+ },
1072
+ fragments: [
1073
+ {
1074
+ fragmentId: 'rag_full_definition',
1075
+ role: 'parent_context',
1076
+ text: [
1077
+ '# Water Glass',
1078
+ '',
1079
+ 'A water glass is a transparent drinking vessel that contains water.',
1080
+ '',
1081
+ '## Thermal model',
1082
+ '',
1083
+ 'The temperature field follows:',
1084
+ '',
1085
+ '$$\n\\frac{\\partial T}{\\partial t} = \\alpha \\nabla^2 T\n$$',
1086
+ ].join('\n'),
1087
+ atomId: item.atom.id,
1088
+ documentId: item.atom.documentId,
1089
+ sourcePath: item.atom.sourcePath,
1090
+ title: item.atom.title,
1091
+ headingPath: ['Water Glass'],
1092
+ startLine: 1,
1093
+ endLine: 10,
1094
+ charCount: 260,
1095
+ tokenEstimate: 65,
1096
+ truncated: false,
1097
+ citationIds: ['evidence_full_water_glass'],
1098
+ sourceBoundary: 'full_document',
1099
+ },
1100
+ {
1101
+ fragmentId: 'rag_full_optics',
1102
+ role: 'parent_context',
1103
+ text: [
1104
+ '## Optical model',
1105
+ '',
1106
+ 'At an interface, refraction follows Snell law:',
1107
+ '',
1108
+ '$$\nn_1 \\sin(\\theta_1) = n_2 \\sin(\\theta_2)\n$$',
1109
+ ].join('\n'),
1110
+ atomId: item.atom.id,
1111
+ documentId: item.atom.documentId,
1112
+ sourcePath: item.atom.sourcePath,
1113
+ title: item.atom.title,
1114
+ headingPath: ['Water Glass', 'Optical model'],
1115
+ startLine: 12,
1116
+ endLine: 18,
1117
+ charCount: 150,
1118
+ tokenEstimate: 38,
1119
+ truncated: false,
1120
+ citationIds: ['evidence_full_water_glass'],
1121
+ sourceBoundary: 'full_document',
1122
+ },
1123
+ {
1124
+ fragmentId: 'rag_full_comparison',
1125
+ role: 'parent_context',
1126
+ text: [
1127
+ '## Material comparison',
1128
+ '',
1129
+ '```mermaid',
1130
+ 'graph LR',
1131
+ ' A[Glass] --> B[Water]',
1132
+ '```',
1133
+ '',
1134
+ 'The comparison model relates material conductivity to heat loss.',
1135
+ '',
1136
+ '| Material | Thermal conductivity |',
1137
+ '| --- | --- |',
1138
+ '| Soda-lime glass | 1.0 W/(m·K) |',
1139
+ '| PET plastic | 0.2 W/(m·K) |',
1140
+ ].join('\n'),
1141
+ atomId: item.atom.id,
1142
+ documentId: item.atom.documentId,
1143
+ sourcePath: item.atom.sourcePath,
1144
+ title: 'Material comparison (mermaid block)',
1145
+ headingPath: ['Water Glass', 'Material comparison'],
1146
+ startLine: 20,
1147
+ endLine: 26,
1148
+ charCount: 150,
1149
+ tokenEstimate: 38,
1150
+ truncated: false,
1151
+ citationIds: ['evidence_full_water_glass'],
1152
+ sourceBoundary: 'full_document',
1153
+ },
1154
+ {
1155
+ fragmentId: 'rag_full_orphan_heading',
1156
+ role: 'parent_context',
1157
+ text: '### References',
1158
+ atomId: item.atom.id,
1159
+ documentId: item.atom.documentId,
1160
+ sourcePath: item.atom.sourcePath,
1161
+ title: item.atom.title,
1162
+ headingPath: ['Water Glass', 'References'],
1163
+ startLine: 30,
1164
+ endLine: 30,
1165
+ charCount: 15,
1166
+ tokenEstimate: 4,
1167
+ truncated: false,
1168
+ citationIds: ['evidence_full_water_glass'],
1169
+ sourceBoundary: 'full_document',
1170
+ },
1171
+ {
1172
+ fragmentId: 'rag_full_flattened_section',
1173
+ role: 'direct_support',
1174
+ text: '### Flattened section The fallback must retain this body when a source window is flattened.',
1175
+ atomId: item.atom.id,
1176
+ documentId: item.atom.documentId,
1177
+ sourcePath: item.atom.sourcePath,
1178
+ title: 'Flattened section',
1179
+ headingPath: ['Water Glass', 'Flattened section'],
1180
+ startLine: 31,
1181
+ endLine: 31,
1182
+ charCount: 86,
1183
+ tokenEstimate: 22,
1184
+ truncated: false,
1185
+ citationIds: ['evidence_full_water_glass'],
1186
+ sourceBoundary: 'direct_span_only',
1187
+ },
1188
+ ],
1189
+ sourceDecisions: [],
1190
+ totalCharCount: 560,
1191
+ tokenEstimate: 141,
1192
+ };
1193
+ const ragSufficiencyReview = {
1194
+ reviewedAt: '2026-09-03T00:00:00.000Z',
1195
+ status: 'sufficient',
1196
+ score: 0.98,
1197
+ reasons: [],
1198
+ deterministic: true,
1199
+ degradationState: 'none',
1200
+ };
1201
+ const makeReply = (responseMode) => (0, conversationComposer_1.buildScopedConversationReply)({
1202
+ message: 'what is water glass?',
1203
+ responseMode,
1204
+ knowledgePoints,
1205
+ citations,
1206
+ recalledMemories: [],
1207
+ memoryActions: [],
1208
+ usedScope: globalScope,
1209
+ generatedAt: '2026-09-03T00:00:00.000Z',
1210
+ nextBlockId: (() => {
1211
+ let blockCounter = 0;
1212
+ return () => `assistant_block_${++blockCounter}`;
1213
+ })(),
1214
+ ragContextPack,
1215
+ ragSufficiencyReview,
1216
+ });
1217
+ const slimReply = makeReply('slim');
1218
+ const fullReply = makeReply('full');
1219
+ expect(slimReply.answer).not.toContain('## Material comparison');
1220
+ expect(fullReply.answer).toContain('## Thermal model');
1221
+ expect(fullReply.answer).toContain('## Optical model');
1222
+ expect(fullReply.answer).toContain('## Material comparison');
1223
+ expect(fullReply.answer).toContain('comparison model relates material conductivity');
1224
+ expect(fullReply.answer).toContain('Soda-lime glass');
1225
+ expect(fullReply.answer).not.toMatch(/### References\s*$/u);
1226
+ expect(fullReply.answer).toContain('fallback must retain this body');
1227
+ expect(fullReply.answer).toContain('\\frac{\\partial T}{\\partial t}');
1228
+ expect(fullReply.answer).toContain('n_1 \\sin(\\theta_1)');
1229
+ expect(fullReply.answer.match(/\$\$/gu)?.length).toBe(4);
1230
+ expect(fullReply.answer).not.toContain('Grounded by');
1231
+ expect(fullReply.answerReleaseReview.decision).not.toBe('abstain');
1232
+ });
1233
+ test('uses same-document graph-neighbor fragments as full-report section fallbacks', () => {
1234
+ const item = makeQueryItem({
1235
+ atom: {
1236
+ id: 'atom_graph_fallback_water_glass',
1237
+ documentId: 'doc_graph_fallback_water_glass',
1238
+ sourcePath: 'Knowledge_Base/test/graph-fallback-water-glass.md',
1239
+ title: 'Water Glass',
1240
+ content: 'A water glass is a transparent drinking vessel that contains water.',
1241
+ },
1242
+ evidence: {
1243
+ id: 'evidence_graph_fallback_water_glass',
1244
+ snippet: 'A water glass is a transparent drinking vessel that contains water.',
1245
+ startLine: 3,
1246
+ endLine: 3,
1247
+ },
1248
+ score: 0.95,
1249
+ });
1250
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1251
+ const citations = knowledgePoints[0].citations || [];
1252
+ const makeFragment = (overrides) => ({
1253
+ fragmentId: String(overrides.fragmentId || 'fragment'),
1254
+ role: overrides.role || 'parent_context',
1255
+ text: String(overrides.text || ''),
1256
+ atomId: overrides.atomId || item.atom.id,
1257
+ documentId: String(overrides.documentId || item.atom.documentId),
1258
+ sourcePath: String(overrides.sourcePath || item.atom.sourcePath),
1259
+ title: String(overrides.title || item.atom.title),
1260
+ headingPath: Array.isArray(overrides.headingPath) ? overrides.headingPath : ['Water Glass'],
1261
+ startLine: overrides.startLine || 1,
1262
+ endLine: overrides.endLine || 4,
1263
+ charCount: Number(overrides.charCount || String(overrides.text || '').length),
1264
+ tokenEstimate: Number(overrides.tokenEstimate || 10),
1265
+ truncated: overrides.truncated === true,
1266
+ citationIds: Array.isArray(overrides.citationIds) ? overrides.citationIds : ['evidence_graph_fallback_water_glass'],
1267
+ sourceBoundary: overrides.sourceBoundary || 'full_document',
1268
+ });
1269
+ const ragContextPack = {
1270
+ query: 'what is water glass?',
1271
+ generatedAt: '2026-09-03T00:00:00.000Z',
1272
+ sourceBoundary: 'full_document',
1273
+ budget: {
1274
+ maxFragments: 6,
1275
+ maxCharsPerFragment: 1200,
1276
+ maxTotalChars: 3600,
1277
+ },
1278
+ fragments: [
1279
+ makeFragment({
1280
+ fragmentId: 'rag_graph_fallback_definition',
1281
+ role: 'parent_context',
1282
+ text: '## Definition\n\nA water glass is a transparent drinking vessel that contains water.',
1283
+ headingPath: ['Water Glass', 'Definition'],
1284
+ }),
1285
+ makeFragment({
1286
+ fragmentId: 'rag_graph_fallback_optics',
1287
+ role: 'graph_neighbor_support',
1288
+ text: '## Optical model\n\nRefraction follows Snell law.\n\n$$\nn_1 \\sin(\\theta_1) = n_2 \\sin(\\theta_2)\n$$',
1289
+ headingPath: ['Water Glass', 'Optical model'],
1290
+ }),
1291
+ makeFragment({
1292
+ fragmentId: 'rag_graph_fallback_unrelated',
1293
+ role: 'graph_neighbor_support',
1294
+ documentId: 'doc_unrelated_graph_neighbor',
1295
+ sourcePath: 'Knowledge_Base/test/unrelated.md',
1296
+ text: '## Unrelated model\n\nThis cross-document marker must not leak into the water-glass report.',
1297
+ headingPath: ['Unrelated', 'Unrelated model'],
1298
+ }),
1299
+ ],
1300
+ sourceDecisions: [],
1301
+ totalCharCount: 300,
1302
+ tokenEstimate: 75,
1303
+ };
1304
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1305
+ message: 'what is water glass?',
1306
+ responseMode: 'full',
1307
+ knowledgePoints,
1308
+ citations,
1309
+ recalledMemories: [],
1310
+ memoryActions: [],
1311
+ usedScope: globalScope,
1312
+ generatedAt: '2026-09-03T00:00:00.000Z',
1313
+ nextBlockId: (() => {
1314
+ let blockCounter = 0;
1315
+ return () => `assistant_block_${++blockCounter}`;
1316
+ })(),
1317
+ ragContextPack,
1318
+ ragSufficiencyReview: {
1319
+ reviewedAt: '2026-09-03T00:00:00.000Z',
1320
+ status: 'sufficient',
1321
+ score: 0.95,
1322
+ reasons: [],
1323
+ deterministic: true,
1324
+ degradationState: 'none',
1325
+ },
1326
+ });
1327
+ expect(reply.answer).toContain('## Optical model');
1328
+ expect(reply.answer).toContain('n_1 \\sin(\\theta_1)');
1329
+ expect(reply.answer).not.toContain('cross-document marker');
1330
+ expect(reply.answerReleaseReview.decision).not.toBe('abstain');
1331
+ });
1332
+ test('full report uses the injected max-tier budget instead of the legacy 24k cap', () => {
1333
+ const item = makeQueryItem({
1334
+ atom: {
1335
+ id: 'atom_adaptive_max_report',
1336
+ documentId: 'doc_adaptive_max_report',
1337
+ sourcePath: 'Knowledge_Base/test/adaptive-max-report.md',
1338
+ title: 'Adaptive Max Report',
1339
+ content: 'Adaptive max report content.',
1340
+ },
1341
+ evidence: {
1342
+ id: 'evidence_adaptive_max_report',
1343
+ snippet: 'Adaptive max report content.',
1344
+ startLine: 2,
1345
+ endLine: 2,
1346
+ },
1347
+ score: 0.99,
1348
+ });
1349
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1350
+ const largeSections = Array.from({ length: 18 }, (_, index) => ({
1351
+ fragmentId: `adaptive_section_${index}`,
1352
+ role: 'parent_context',
1353
+ text: `## Section ${index}\n\n${`Section ${index} preserves complete technical evidence. `.repeat(160)}`,
1354
+ atomId: item.atom.id,
1355
+ documentId: item.atom.documentId,
1356
+ sourcePath: item.atom.sourcePath,
1357
+ title: `Section ${index}`,
1358
+ headingPath: ['Adaptive Max Report', `Section ${index}`],
1359
+ startLine: index * 10 + 1,
1360
+ endLine: index * 10 + 8,
1361
+ charCount: 0,
1362
+ tokenEstimate: 0,
1363
+ truncated: false,
1364
+ citationIds: ['evidence_adaptive_max_report'],
1365
+ sourceBoundary: 'full_document',
1366
+ })).map((fragment) => ({
1367
+ ...fragment,
1368
+ charCount: fragment.text.length,
1369
+ tokenEstimate: Math.ceil(fragment.text.length / 4),
1370
+ }));
1371
+ const budget = {
1372
+ mode: 'adaptive',
1373
+ tier: 'max',
1374
+ productCapDisabled: false,
1375
+ rag: {
1376
+ maxFragments: 256,
1377
+ maxCharsPerFragment: 16000,
1378
+ maxTotalChars: 256000,
1379
+ },
1380
+ reportMaxChars: 160000,
1381
+ runtimeGovernor: {
1382
+ timeoutMs: 120000,
1383
+ maxSerializedBytes: 32 * 1024 * 1024,
1384
+ maxFragmentsProcessed: 2048,
1385
+ maxReportChars: 160000,
1386
+ },
1387
+ };
1388
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1389
+ message: 'what is adaptive max report?',
1390
+ responseMode: 'full',
1391
+ responseBudget: budget,
1392
+ knowledgePoints,
1393
+ citations: knowledgePoints[0].citations || [],
1394
+ recalledMemories: [],
1395
+ memoryActions: [],
1396
+ usedScope: globalScope,
1397
+ generatedAt: '2026-09-03T00:00:00.000Z',
1398
+ nextBlockId: (() => {
1399
+ let blockCounter = 0;
1400
+ return () => `adaptive_max_block_${++blockCounter}`;
1401
+ })(),
1402
+ ragContextPack: {
1403
+ query: 'what is adaptive max report?',
1404
+ generatedAt: '2026-09-03T00:00:00.000Z',
1405
+ sourceBoundary: 'full_document',
1406
+ budget: budget.rag,
1407
+ fragments: [
1408
+ {
1409
+ fragmentId: 'adaptive_definition',
1410
+ role: 'parent_context',
1411
+ text: '# Adaptive Max Report\n\nAdaptive max report content.',
1412
+ atomId: item.atom.id,
1413
+ documentId: item.atom.documentId,
1414
+ sourcePath: item.atom.sourcePath,
1415
+ title: item.atom.title,
1416
+ headingPath: ['Adaptive Max Report'],
1417
+ startLine: 1,
1418
+ endLine: 3,
1419
+ charCount: 55,
1420
+ tokenEstimate: 14,
1421
+ truncated: false,
1422
+ citationIds: ['evidence_adaptive_max_report'],
1423
+ sourceBoundary: 'full_document',
1424
+ },
1425
+ ...largeSections,
1426
+ ],
1427
+ sourceDecisions: [],
1428
+ totalCharCount: largeSections.reduce((sum, fragment) => sum + fragment.charCount, 55),
1429
+ tokenEstimate: 0,
1430
+ },
1431
+ ragSufficiencyReview: {
1432
+ reviewedAt: '2026-09-03T00:00:00.000Z',
1433
+ status: 'sufficient',
1434
+ score: 1,
1435
+ reasons: [],
1436
+ deterministic: true,
1437
+ },
1438
+ });
1439
+ expect(reply.answer.length).toBeGreaterThan(24000);
1440
+ expect(reply.answer.length).toBeLessThanOrEqual(160000);
1441
+ expect(reply.fullReportAssembly?.truncated).toBe(false);
1442
+ });
1443
+ test('unbounded report retains safe sections and reports runtime truncation explicitly', () => {
1444
+ const item = makeQueryItem({
1445
+ atom: {
1446
+ id: 'atom_unbounded_report',
1447
+ documentId: 'doc_unbounded_report',
1448
+ sourcePath: 'Knowledge_Base/test/unbounded-report.md',
1449
+ title: 'Unbounded Report',
1450
+ content: 'Unbounded report content.',
1451
+ },
1452
+ evidence: {
1453
+ id: 'evidence_unbounded_report',
1454
+ snippet: 'Unbounded report content.',
1455
+ startLine: 2,
1456
+ endLine: 2,
1457
+ },
1458
+ score: 0.99,
1459
+ });
1460
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1461
+ const makeFragment = (index) => ({
1462
+ fragmentId: `unbounded_report_${index}`,
1463
+ role: 'parent_context',
1464
+ text: `## Safe section ${index}\n\nThis section ${index} remains available under product-unbounded mode. ${'Additional bounded evidence. '.repeat(20)}`,
1465
+ atomId: item.atom.id,
1466
+ documentId: item.atom.documentId,
1467
+ sourcePath: item.atom.sourcePath,
1468
+ title: `Safe section ${index}`,
1469
+ headingPath: ['Unbounded Report', `Safe section ${index}`],
1470
+ startLine: index + 1,
1471
+ endLine: index + 4,
1472
+ charCount: 0,
1473
+ tokenEstimate: 0,
1474
+ truncated: false,
1475
+ citationIds: ['evidence_unbounded_report'],
1476
+ sourceBoundary: 'full_document',
1477
+ });
1478
+ const fragments = Array.from({ length: 5 }, (_, index) => {
1479
+ const fragment = makeFragment(index);
1480
+ return { ...fragment, charCount: fragment.text.length, tokenEstimate: Math.ceil(fragment.text.length / 4) };
1481
+ });
1482
+ const budget = {
1483
+ mode: 'unbounded',
1484
+ tier: 'unbounded',
1485
+ productCapDisabled: true,
1486
+ rag: {
1487
+ maxFragments: 4096,
1488
+ maxCharsPerFragment: 16000,
1489
+ maxTotalChars: 320000,
1490
+ productCapDisabled: true,
1491
+ runtimeMaxFragments: 4096,
1492
+ runtimeMaxCharsPerFragment: 16000,
1493
+ runtimeMaxTotalChars: 64 * 1024 * 1024,
1494
+ },
1495
+ runtimeGovernor: {
1496
+ timeoutMs: 180000,
1497
+ maxSerializedBytes: 64 * 1024 * 1024,
1498
+ maxFragmentsProcessed: 2,
1499
+ maxReportChars: 320000,
1500
+ },
1501
+ };
1502
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1503
+ message: 'what is unbounded report?',
1504
+ responseMode: 'full',
1505
+ responseBudget: budget,
1506
+ knowledgePoints,
1507
+ citations: knowledgePoints[0].citations || [],
1508
+ recalledMemories: [],
1509
+ memoryActions: [],
1510
+ usedScope: globalScope,
1511
+ generatedAt: '2026-09-03T00:00:00.000Z',
1512
+ nextBlockId: (() => {
1513
+ let blockCounter = 0;
1514
+ return () => `unbounded_block_${++blockCounter}`;
1515
+ })(),
1516
+ ragContextPack: {
1517
+ query: 'what is unbounded report?',
1518
+ generatedAt: '2026-09-03T00:00:00.000Z',
1519
+ sourceBoundary: 'full_document',
1520
+ budget: budget.rag,
1521
+ fragments,
1522
+ sourceDecisions: [],
1523
+ totalCharCount: fragments.reduce((sum, fragment) => sum + fragment.charCount, 0),
1524
+ tokenEstimate: 0,
1525
+ },
1526
+ ragSufficiencyReview: {
1527
+ reviewedAt: '2026-09-03T00:00:00.000Z',
1528
+ status: 'sufficient',
1529
+ score: 1,
1530
+ reasons: [],
1531
+ deterministic: true,
1532
+ },
1533
+ });
1534
+ expect(reply.answer).toContain('Safe section 0');
1535
+ expect(reply.answer).toContain('Safe section 1');
1536
+ expect(reply.answer).not.toContain('Safe section 2');
1537
+ expect(reply.fullReportAssembly?.truncated).toBe(true);
1538
+ expect(reply.fullReportAssembly?.truncationReason).toBe('runtime_fragment_limit');
1539
+ });
999
1540
  test('keeps decimal numeric evidence intact in RAG public answers', () => {
1000
1541
  const item = makeQueryItem({
1001
1542
  atom: {
@@ -1228,6 +1769,8 @@ describe('conversationComposer', () => {
1228
1769
  ragSufficiencyReview,
1229
1770
  });
1230
1771
  expect(reply.answer).toContain('transparent drinking vessel');
1772
+ expect(reply.graphAnswerPlan.claims.map((claim) => claim.statement)).toContain('A plastic cup is an opaque polymer vessel that can deform under pressure.');
1773
+ expect(reply.graphAnswerPlan.claims.map((claim) => claim.statement)).toContain('The graph marks the two nodes with a contrast relation, so the comparison should preserve their material and rigidity differences.');
1231
1774
  expect(reply.answer).toContain('opaque polymer vessel');
1232
1775
  expect(reply.answer).toContain('contrast relation');
1233
1776
  expect(reply.assistantBlocks.filter((block) => block.type === 'structured_answer')).toHaveLength(1);
@@ -1334,10 +1877,185 @@ describe('conversationComposer', () => {
1334
1877
  ragContextPack,
1335
1878
  ragSufficiencyReview,
1336
1879
  });
1880
+ expect(reply.graphAnswerPlan.claims.map((claim) => claim.statement)).toEqual(expect.arrayContaining([
1881
+ 'Water Glass Soda-Lime',
1882
+ 'Plastic Cup PET',
1883
+ ]));
1884
+ expect(reply.answerReleaseReview.originalAnswer).toContain('Water Glass Soda-Lime');
1885
+ expect(reply.answerReleaseReview.gates.filter((gate) => !gate.passed)).toEqual([]);
1337
1886
  expect(reply.answer).toContain('Water Glass Soda-Lime');
1338
1887
  expect(reply.answer).toContain('Plastic Cup PET');
1339
1888
  expect(reply.answer).not.toContain('```');
1340
1889
  });
1890
+ test('bounds a full-document comparison to both requested operands instead of preamble context', () => {
1891
+ const point = {
1892
+ atomId: 'atom_full_compare_water_glass',
1893
+ atomIds: ['atom_full_compare_water_glass'],
1894
+ documentId: 'doc_full_compare_water_glass',
1895
+ sourcePath: 'Knowledge_Base/waterglass/water glass.md',
1896
+ title: '\u6c34\u676f (water glass)',
1897
+ summary: '\u6c34\u676f\u662f\u7531\u73bb\u7483\u5bb9\u5668\u4e0e\u6c34\u7ec4\u6210\u7684\u7269\u7406\u7cfb\u7edf\u3002',
1898
+ evidenceSnippet: '\u6c34\u676f\u662f\u7531\u73bb\u7483\u5bb9\u5668\u4e0e\u6c34\u7ec4\u6210\u7684\u7269\u7406\u7cfb\u7edf\u3002',
1899
+ score: 0.98,
1900
+ citation: null,
1901
+ citations: [],
1902
+ matchedSpans: [],
1903
+ matchCount: 1,
1904
+ relationPath: [],
1905
+ relationPathAtomIds: [],
1906
+ relationKinds: [],
1907
+ temporalValidity: {
1908
+ isValid: true,
1909
+ checkedAt: '2026-09-01T00:00:00.000Z',
1910
+ reasons: [],
1911
+ details: [],
1912
+ },
1913
+ capabilities: [],
1914
+ };
1915
+ const documentId = 'doc_full_compare_water_glass';
1916
+ const sourcePath = 'Knowledge_Base/waterglass/water glass.md';
1917
+ const graphContext = {
1918
+ anchorAtomId: point.atomId,
1919
+ anchorTitle: point.title,
1920
+ anchorDocumentId: documentId,
1921
+ supportingAtomIds: [],
1922
+ supportingTitles: [],
1923
+ relationKinds: [],
1924
+ relationSummaries: [],
1925
+ connectionPaths: [{
1926
+ sourceAtomId: 'atom_full_compare_water_glass',
1927
+ sourceTitle: 'Temporal Current Release Source',
1928
+ targetAtomId: 'atom_full_compare_water_glass',
1929
+ targetTitle: 'Temporal Planned Roadmap Source',
1930
+ pathAtomIds: ['atom_full_compare_water_glass'],
1931
+ pathTitles: ['Temporal Current Release Source', 'Temporal Planned Roadmap Source'],
1932
+ pathEdges: [{
1933
+ fromAtomId: 'atom_full_compare_water_glass',
1934
+ toAtomId: 'atom_full_compare_water_glass',
1935
+ relationKind: 'contrast',
1936
+ }],
1937
+ length: 1,
1938
+ }],
1939
+ predecessorWindow: [{
1940
+ atomId: 'atom_full_compare_water_glass',
1941
+ title: 'Temporal Current Release Source',
1942
+ relationKind: 'contrast',
1943
+ confidence: 0.9,
1944
+ }],
1945
+ successorWindow: [{
1946
+ atomId: 'atom_full_compare_water_glass',
1947
+ title: 'Temporal Planned Roadmap Source',
1948
+ relationKind: 'contrast',
1949
+ confidence: 0.9,
1950
+ }],
1951
+ temporalValidity: {
1952
+ checkedAt: '2026-09-01T00:00:00.000Z',
1953
+ allPointsValid: true,
1954
+ warningReasons: [],
1955
+ invalidKnowledgePointTitles: [],
1956
+ edgeKinds: [],
1957
+ details: [],
1958
+ },
1959
+ };
1960
+ const ragContextPack = {
1961
+ query: 'compare water glass and plastic cup',
1962
+ generatedAt: '2026-09-01T00:00:00.000Z',
1963
+ sourceBoundary: 'full_document',
1964
+ budget: { maxFragments: 8, maxCharsPerFragment: 1200, maxTotalChars: 4800 },
1965
+ fragments: [
1966
+ {
1967
+ fragmentId: 'full_compare_definition',
1968
+ role: 'direct_support',
1969
+ text: '## Water Glass\n\nA water glass is a transparent vessel made from soda-lime glass.',
1970
+ atomId: point.atomId,
1971
+ documentId,
1972
+ sourcePath,
1973
+ title: 'Water Glass',
1974
+ headingPath: ['Water Glass'],
1975
+ charCount: 86,
1976
+ tokenEstimate: 22,
1977
+ truncated: false,
1978
+ citationIds: ['full_compare_definition'],
1979
+ relationEdgeIds: [],
1980
+ sourceBoundary: 'full_document',
1981
+ score: 0.98,
1982
+ },
1983
+ {
1984
+ fragmentId: 'full_compare_preamble',
1985
+ role: 'direct_support',
1986
+ text: 'This technical document was generated from the title water glass and provides broad background context.',
1987
+ atomId: point.atomId,
1988
+ documentId,
1989
+ sourcePath,
1990
+ title: 'water glass.md preamble',
1991
+ headingPath: ['preamble'],
1992
+ charCount: 104,
1993
+ tokenEstimate: 26,
1994
+ truncated: false,
1995
+ citationIds: ['full_compare_preamble'],
1996
+ relationEdgeIds: [],
1997
+ sourceBoundary: 'full_document',
1998
+ score: 0.8,
1999
+ },
2000
+ {
2001
+ fragmentId: 'full_compare_mermaid',
2002
+ role: 'direct_support',
2003
+ text: [
2004
+ '### Container comparison',
2005
+ '```mermaid',
2006
+ 'graph LR',
2007
+ 'A[Water Glass Soda-Lime]',
2008
+ 'B[Plastic Cup PET]',
2009
+ '```',
2010
+ ].join('\n'),
2011
+ atomId: point.atomId,
2012
+ documentId,
2013
+ sourcePath,
2014
+ title: 'Container comparison',
2015
+ headingPath: ['Water Glass', 'Container comparison'],
2016
+ charCount: 112,
2017
+ tokenEstimate: 28,
2018
+ truncated: false,
2019
+ citationIds: ['full_compare_mermaid'],
2020
+ relationEdgeIds: [],
2021
+ sourceBoundary: 'full_document',
2022
+ score: 0.95,
2023
+ },
2024
+ ],
2025
+ sourceDecisions: [],
2026
+ totalCharCount: 302,
2027
+ tokenEstimate: 76,
2028
+ };
2029
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2030
+ message: 'compare water glass and plastic cup',
2031
+ knowledgePoints: [point],
2032
+ citations: [],
2033
+ recalledMemories: [],
2034
+ memoryActions: [],
2035
+ usedScope: globalScope,
2036
+ generatedAt: '2026-09-01T00:00:00.000Z',
2037
+ nextBlockId: (() => {
2038
+ let index = 0;
2039
+ return () => `full_compare_block_${++index}`;
2040
+ })(),
2041
+ graphContext,
2042
+ ragContextPack,
2043
+ ragSufficiencyReview: {
2044
+ reviewedAt: '2026-09-01T00:00:00.000Z',
2045
+ status: 'sufficient',
2046
+ score: 0.94,
2047
+ reasons: [],
2048
+ deterministic: true,
2049
+ recoveryAttempted: false,
2050
+ llmJudgeUsed: false,
2051
+ degradationState: 'none',
2052
+ },
2053
+ });
2054
+ expect(reply.answer).toContain('Water Glass Soda-Lime');
2055
+ expect(reply.answer).toContain('Plastic Cup PET');
2056
+ expect(reply.answer).not.toContain('This technical document was generated from the title');
2057
+ expect(reply.answerReleaseReview.gates.filter((gate) => !gate.passed)).toEqual([]);
2058
+ });
1341
2059
  test('uses how-to RAG profile to preserve steps prerequisites downstream checks and failure handling', () => {
1342
2060
  const item = makeQueryItem({
1343
2061
  atom: {
@@ -1648,6 +2366,60 @@ describe('conversationComposer', () => {
1648
2366
  citationIds: ['evidence_generic_optical_bench_drift'],
1649
2367
  sourceBoundary: 'direct_span_only',
1650
2368
  },
2369
+ {
2370
+ fragmentId: 'rag_direct_generic_optical_bench_drift_environment',
2371
+ role: 'direct_support',
2372
+ text: 'Ambient temperature changes shift the optical bench reference position.',
2373
+ atomId: 'atom_generic_optical_bench_drift_environment',
2374
+ documentId: 'doc_generic_optical_bench_drift',
2375
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2376
+ title: 'Optical Bench Drift Environment',
2377
+ headingPath: ['Optical Bench Drift', 'Environmental cause'],
2378
+ startLine: 14,
2379
+ endLine: 14,
2380
+ charCount: 71,
2381
+ tokenEstimate: 14,
2382
+ truncated: false,
2383
+ citationIds: ['evidence_generic_optical_bench_drift'],
2384
+ sourceBoundary: 'full_document',
2385
+ score: 0.93,
2386
+ },
2387
+ {
2388
+ fragmentId: 'rag_direct_generic_optical_bench_drift_mount',
2389
+ role: 'direct_support',
2390
+ text: 'Mount relaxation changes the beam angle while the detector remains stable.',
2391
+ atomId: 'atom_generic_optical_bench_drift_mount',
2392
+ documentId: 'doc_generic_optical_bench_drift',
2393
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2394
+ title: 'Optical Bench Drift Mount',
2395
+ headingPath: ['Optical Bench Drift', 'Mechanical cause'],
2396
+ startLine: 15,
2397
+ endLine: 15,
2398
+ charCount: 72,
2399
+ tokenEstimate: 14,
2400
+ truncated: false,
2401
+ citationIds: ['evidence_generic_optical_bench_drift'],
2402
+ sourceBoundary: 'full_document',
2403
+ score: 0.92,
2404
+ },
2405
+ {
2406
+ fragmentId: 'rag_direct_generic_optical_bench_drift_history',
2407
+ role: 'direct_support',
2408
+ text: 'A time-series baseline separates persistent bench drift from transient detector noise.',
2409
+ atomId: 'atom_generic_optical_bench_drift_history',
2410
+ documentId: 'doc_generic_optical_bench_drift',
2411
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2412
+ title: 'Optical Bench Drift History',
2413
+ headingPath: ['Optical Bench Drift', 'Historical baseline'],
2414
+ startLine: 16,
2415
+ endLine: 16,
2416
+ charCount: 83,
2417
+ tokenEstimate: 16,
2418
+ truncated: false,
2419
+ citationIds: ['evidence_generic_optical_bench_drift'],
2420
+ sourceBoundary: 'full_document',
2421
+ score: 0.91,
2422
+ },
1651
2423
  {
1652
2424
  fragmentId: 'rag_parent_generic_optical_bench_drift',
1653
2425
  role: 'parent_context',
@@ -1711,13 +2483,644 @@ describe('conversationComposer', () => {
1711
2483
  ragContextPack,
1712
2484
  ragSufficiencyReview,
1713
2485
  });
2486
+ expect(reply.answerReleaseReview.originalAnswer).toContain('downstream calibration quality');
2487
+ expect(reply.answerReleaseReview.gates.filter((gate) => !gate.passed)).toEqual([]);
1714
2488
  expect(reply.answer).toContain('reference beam against the current beam centroid');
1715
2489
  expect(reply.answer).toContain('centroid delta before changing hardware');
1716
2490
  expect(reply.answer).toContain('stable baseline records');
1717
2491
  expect(reply.answer).toContain('downstream calibration quality');
2492
+ expect(reply.answer).toContain('Ambient temperature changes');
2493
+ expect(reply.answer).toContain('Mount relaxation changes');
2494
+ expect(reply.answer).toContain('time-series baseline separates');
2495
+ expect(reply.graphAnswerCoverage.passed).toBe(true);
1718
2496
  expect(reply.answer).not.toContain('broad overview of laboratory documentation');
1719
2497
  expect(reply.answer).not.toContain('Graph caveat:');
1720
2498
  });
2499
+ test('renders every planned direct-support claim without an intent profile quota', () => {
2500
+ const item = makeQueryItem({
2501
+ atom: {
2502
+ id: 'atom_profile_free_beam_drift',
2503
+ documentId: 'doc_profile_free_beam_drift',
2504
+ sourcePath: 'Knowledge_Base/test/profile-free-beam-drift.md',
2505
+ title: 'Beam Drift',
2506
+ content: 'Beam drift is a displacement of the measured spot.',
2507
+ },
2508
+ evidence: {
2509
+ id: 'evidence_profile_free_beam_drift',
2510
+ snippet: 'Beam drift is a displacement of the measured spot.',
2511
+ startLine: 3,
2512
+ endLine: 6,
2513
+ },
2514
+ score: 0.93,
2515
+ });
2516
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2517
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
2518
+ let blockCounter = 0;
2519
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2520
+ message: 'tell me about beam drift',
2521
+ knowledgePoints,
2522
+ citations,
2523
+ recalledMemories: [],
2524
+ memoryActions: [],
2525
+ usedScope: globalScope,
2526
+ generatedAt: '2026-07-22T00:00:00.000Z',
2527
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
2528
+ ragSufficiencyReview: {
2529
+ reviewedAt: '2026-07-22T00:00:00.000Z',
2530
+ status: 'sufficient',
2531
+ score: 0.9,
2532
+ reasons: [],
2533
+ deterministic: true,
2534
+ recoveryAttempted: false,
2535
+ llmJudgeUsed: false,
2536
+ degradationState: 'none',
2537
+ },
2538
+ ragContextPack: {
2539
+ query: 'tell me about beam drift',
2540
+ generatedAt: '2026-07-22T00:00:00.000Z',
2541
+ sourceBoundary: 'direct_span_only',
2542
+ budget: { maxFragments: 1, maxCharsPerFragment: 1000, maxTotalChars: 1000 },
2543
+ fragments: [{
2544
+ fragmentId: 'direct_profile_free_beam_drift',
2545
+ role: 'direct_support',
2546
+ text: [
2547
+ 'Beam drift is a displacement of the measured spot.',
2548
+ 'It can be measured against a fixed reference mark.',
2549
+ 'Its correction requires recording the centroid shift before adjustment.',
2550
+ ].join(' '),
2551
+ atomId: 'atom_profile_free_beam_drift',
2552
+ documentId: 'doc_profile_free_beam_drift',
2553
+ sourcePath: 'Knowledge_Base/test/profile-free-beam-drift.md',
2554
+ title: 'Beam Drift',
2555
+ headingPath: ['Beam Drift'],
2556
+ startLine: 3,
2557
+ endLine: 6,
2558
+ charCount: 163,
2559
+ tokenEstimate: 39,
2560
+ truncated: false,
2561
+ citationIds: ['evidence_profile_free_beam_drift'],
2562
+ sourceBoundary: 'direct_span_only',
2563
+ score: 0.7,
2564
+ }],
2565
+ sourceDecisions: [],
2566
+ totalCharCount: 163,
2567
+ tokenEstimate: 39,
2568
+ },
2569
+ });
2570
+ expect(reply.answer).toContain('It can be measured against a fixed reference mark');
2571
+ expect(reply.answer).toContain('Its correction requires recording the centroid shift before adjustment');
2572
+ });
2573
+ test('projects definition answers to bounded non-comparison claims while retaining formula integrity', () => {
2574
+ const item = makeQueryItem({
2575
+ atom: {
2576
+ id: 'atom_projection_water_glass',
2577
+ documentId: 'doc_projection_water_glass',
2578
+ title: 'Water Glass',
2579
+ content: 'A water glass is a transparent vessel used to hold water.',
2580
+ },
2581
+ evidence: {
2582
+ id: 'evidence_projection_water_glass',
2583
+ snippet: 'A water glass is a transparent vessel used to hold water.',
2584
+ },
2585
+ score: 0.95,
2586
+ });
2587
+ const projectionPoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2588
+ const densePack = {
2589
+ query: 'what is water glass?',
2590
+ generatedAt: '2026-07-12T00:00:00.000Z',
2591
+ sourceBoundary: 'direct_span_only',
2592
+ budget: { maxFragments: 1, maxCharsPerFragment: 1000, maxTotalChars: 1000 },
2593
+ fragments: [{
2594
+ fragmentId: 'definition_projection_dense',
2595
+ role: 'direct_support',
2596
+ atomId: 'atom_projection_water_glass',
2597
+ documentId: 'doc_projection_water_glass',
2598
+ sourcePath: 'Knowledge_Base/test/projection-water-glass.md',
2599
+ title: 'Water Glass',
2600
+ headingPath: ['Water Glass'],
2601
+ text: [
2602
+ 'A water glass is a transparent vessel used to hold water.',
2603
+ 'It separates the liquid from the surrounding environment.',
2604
+ 'Its wall conducts heat between the drink and the air.',
2605
+ 'It is compared with a PET Plastic Cup and a Stainless Steel Metal Cup.',
2606
+ 'The water glass thermal field follows $$\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T$$.',
2607
+ 'This extra sentence should remain outside the compact definition answer.',
2608
+ ].join(' '),
2609
+ charCount: 330,
2610
+ tokenEstimate: 80,
2611
+ truncated: false,
2612
+ citationIds: ['evidence_projection_water_glass'],
2613
+ sourceBoundary: 'direct_span_only',
2614
+ score: 0.95,
2615
+ }],
2616
+ sourceDecisions: [],
2617
+ totalCharCount: 330,
2618
+ tokenEstimate: 80,
2619
+ };
2620
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2621
+ message: 'what is water glass?',
2622
+ knowledgePoints: projectionPoints,
2623
+ citations: [],
2624
+ recalledMemories: [],
2625
+ memoryActions: [],
2626
+ usedScope: globalScope,
2627
+ generatedAt: '2026-07-12T00:00:00.000Z',
2628
+ nextBlockId: (() => {
2629
+ let index = 0;
2630
+ return () => `projection_block_${++index}`;
2631
+ })(),
2632
+ ragContextPack: densePack,
2633
+ ragSufficiencyReview: {
2634
+ reviewedAt: '2026-07-12T00:00:00.000Z',
2635
+ status: 'sufficient',
2636
+ score: 0.9,
2637
+ reasons: [],
2638
+ deterministic: true,
2639
+ recoveryAttempted: false,
2640
+ llmJudgeUsed: false,
2641
+ degradationState: 'none',
2642
+ },
2643
+ });
2644
+ expect(reply.answer).toContain('A water glass is a transparent vessel used to hold water.');
2645
+ expect(reply.answer).toContain('It separates the liquid from the surrounding environment.');
2646
+ expect(reply.answer).toContain('Its wall conducts heat between the drink and the air.');
2647
+ expect(reply.answer).toContain('$$\n\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T\n$$');
2648
+ expect(reply.answer).not.toContain('PET Plastic Cup');
2649
+ expect(reply.answer).not.toContain('Stainless Steel Metal Cup');
2650
+ expect(reply.answer).not.toContain('This extra sentence should remain outside');
2651
+ expect(reply.graphAnswerPlan).toEqual(reply.answerReleaseReview.publicGraphAnswerPlan);
2652
+ expect(reply.graphAnswerCoverage).toEqual(reply.answerReleaseReview.graphAnswerCoverage);
2653
+ expect(reply.knowledgeRun.graphAnswerPlan).toEqual(reply.graphAnswerPlan);
2654
+ expect(reply.knowledgeRun.graphAnswerCoverage).toEqual(reply.graphAnswerCoverage);
2655
+ expect((reply.answer.match(/\$\$/gu) || []).length % 2).toBe(0);
2656
+ expect((reply.answer.match(/\\frac\{\\partial T\}\{\\partial t\}=\\alpha\\nabla\^2 T/gu) || [])).toHaveLength(1);
2657
+ expect(reply.answer.trim()).not.toMatch(/(?:and|or|with|between|is|are|:|:)\s*$/iu);
2658
+ });
2659
+ test('compound Chinese definition queries keep the subject answer and complete formula while dropping document scaffolding', () => {
2660
+ const items = [
2661
+ makeQueryItem({
2662
+ atom: {
2663
+ id: 'atom_amorphous_ice_definition',
2664
+ documentId: 'doc_amorphous_ice',
2665
+ title: '非晶冰',
2666
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2667
+ content: '## 非晶冰\n非晶冰(Amorphous ice)是水的一种固态形式,其分子排列缺乏长程有序结构。',
2668
+ keywords: ['非晶冰', 'amorphous', 'ice'],
2669
+ },
2670
+ evidence: {
2671
+ id: 'evidence_amorphous_ice_definition',
2672
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2673
+ snippet: '## 非晶冰\n非晶冰(Amorphous ice)是水的一种固态形式,其分子排列缺乏长程有序结构。',
2674
+ startLine: 3,
2675
+ endLine: 6,
2676
+ },
2677
+ score: 0.98,
2678
+ }),
2679
+ makeQueryItem({
2680
+ atom: {
2681
+ id: 'atom_amorphous_ice_math',
2682
+ documentId: 'doc_amorphous_ice',
2683
+ title: '核心概念与数学基础',
2684
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2685
+ content: '### 核心概念与数学基础\n其结构可用径向分布函数 $g(r)$ 描述。\n$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$',
2686
+ keywords: ['核心概念', '数学基础', '非晶冰', 'g', 'r'],
2687
+ },
2688
+ evidence: {
2689
+ id: 'evidence_amorphous_ice_math',
2690
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2691
+ snippet: '### 核心概念与数学基础\n其结构可用径向分布函数 $g(r)$ 描述。\n$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$',
2692
+ startLine: 7,
2693
+ endLine: 18,
2694
+ },
2695
+ score: 0.86,
2696
+ }),
2697
+ makeQueryItem({
2698
+ atom: {
2699
+ id: 'atom_amorphous_ice_application',
2700
+ documentId: 'doc_amorphous_ice',
2701
+ title: '常见用例与性能指标',
2702
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2703
+ content: '### 常见用例与性能指标\n本节介绍冷冻电子显微镜和天体物理学应用。',
2704
+ keywords: ['常见用例', '性能指标', '应用'],
2705
+ },
2706
+ evidence: {
2707
+ id: 'evidence_amorphous_ice_application',
2708
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2709
+ snippet: '### 常见用例与性能指标\n本节介绍冷冻电子显微镜和天体物理学应用。',
2710
+ startLine: 48,
2711
+ endLine: 52,
2712
+ },
2713
+ score: 0.62,
2714
+ }),
2715
+ ];
2716
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)(items, () => []);
2717
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2718
+ message: '什么是非晶冰?我应该通过哪些知识点学习?',
2719
+ answerLanguage: 'zh',
2720
+ knowledgePoints: points,
2721
+ citations: points[0]?.citations || [],
2722
+ recalledMemories: [],
2723
+ memoryActions: [],
2724
+ usedScope: globalScope,
2725
+ generatedAt: '2026-09-01T00:00:00.000Z',
2726
+ nextBlockId: (() => {
2727
+ let index = 0;
2728
+ return () => `compound_definition_block_${++index}`;
2729
+ })(),
2730
+ ragContextPack: {
2731
+ query: '什么是非晶冰?我应该通过哪些知识点学习?',
2732
+ generatedAt: '2026-09-01T00:00:00.000Z',
2733
+ sourceBoundary: 'direct_span_only',
2734
+ budget: { maxFragments: 3, maxCharsPerFragment: 900, maxTotalChars: 2400 },
2735
+ fragments: [
2736
+ {
2737
+ fragmentId: 'rag_amorphous_ice_definition',
2738
+ role: 'direct_support',
2739
+ atomId: 'atom_amorphous_ice_definition',
2740
+ documentId: 'doc_amorphous_ice',
2741
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2742
+ title: '非晶冰',
2743
+ headingPath: ['非晶冰'],
2744
+ text: '## 非晶冰\n非晶冰(Amorphous ice)是水的一种固态形式,其分子排列缺乏长程有序结构。',
2745
+ startLine: 3,
2746
+ endLine: 6,
2747
+ charCount: 80,
2748
+ tokenEstimate: 20,
2749
+ truncated: false,
2750
+ citationIds: ['evidence_amorphous_ice_definition'],
2751
+ relationEdgeIds: [],
2752
+ sourceBoundary: 'direct_span_only',
2753
+ score: 0.98,
2754
+ },
2755
+ {
2756
+ fragmentId: 'rag_amorphous_ice_math',
2757
+ role: 'direct_support',
2758
+ atomId: 'atom_amorphous_ice_math',
2759
+ documentId: 'doc_amorphous_ice',
2760
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2761
+ title: '核心概念与数学基础',
2762
+ headingPath: ['非晶冰', '核心概念与数学基础'],
2763
+ text: '### 核心概念与数学基础\n其结构可用径向分布函数 $g(r)$ 描述。\n$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$',
2764
+ startLine: 7,
2765
+ endLine: 18,
2766
+ charCount: 160,
2767
+ tokenEstimate: 40,
2768
+ truncated: false,
2769
+ citationIds: ['evidence_amorphous_ice_math'],
2770
+ relationEdgeIds: [],
2771
+ sourceBoundary: 'direct_span_only',
2772
+ score: 0.86,
2773
+ },
2774
+ {
2775
+ fragmentId: 'rag_amorphous_ice_technical_noise',
2776
+ role: 'direct_support',
2777
+ atomId: 'atom_amorphous_ice_application',
2778
+ documentId: 'doc_amorphous_ice',
2779
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2780
+ title: '关键技术规格',
2781
+ headingPath: ['非晶冰', '关键技术规格'],
2782
+ text: '### 关键技术规格\n非晶冰存在多种形态,玻璃化转变温度为 $T_g$。',
2783
+ startLine: 32,
2784
+ endLine: 45,
2785
+ charCount: 70,
2786
+ tokenEstimate: 18,
2787
+ truncated: false,
2788
+ citationIds: ['evidence_amorphous_ice_application'],
2789
+ relationEdgeIds: [],
2790
+ sourceBoundary: 'direct_span_only',
2791
+ score: 0.72,
2792
+ },
2793
+ {
2794
+ fragmentId: 'rag_amorphous_ice_math_duplicate',
2795
+ role: 'parent_context',
2796
+ atomId: 'atom_amorphous_ice_math',
2797
+ documentId: 'doc_amorphous_ice',
2798
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2799
+ title: '核心概念与数学基础',
2800
+ headingPath: ['非晶冰', '核心概念与数学基础'],
2801
+ text: '核心概念重复说明:$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$。',
2802
+ startLine: 19,
2803
+ endLine: 23,
2804
+ charCount: 110,
2805
+ tokenEstimate: 28,
2806
+ truncated: false,
2807
+ citationIds: ['evidence_amorphous_ice_math'],
2808
+ relationEdgeIds: [],
2809
+ sourceBoundary: 'full_document',
2810
+ score: 0.65,
2811
+ },
2812
+ ],
2813
+ sourceDecisions: [],
2814
+ totalCharCount: 240,
2815
+ tokenEstimate: 60,
2816
+ },
2817
+ ragSufficiencyReview: {
2818
+ reviewedAt: '2026-09-01T00:00:00.000Z',
2819
+ status: 'sufficient',
2820
+ score: 0.95,
2821
+ reasons: [],
2822
+ deterministic: true,
2823
+ recoveryAttempted: false,
2824
+ llmJudgeUsed: false,
2825
+ degradationState: 'none',
2826
+ },
2827
+ });
2828
+ expect(reply.answer).toContain('非晶冰(Amorphous ice)是水的一种固态形式');
2829
+ expect(reply.answer).toContain('$g(r)$');
2830
+ expect(reply.answer).toContain('$$');
2831
+ expect(reply.answer).not.toMatch(/###\s+(?!建议学习路径)/u);
2832
+ expect(reply.answer).not.toContain('常见用例与性能指标');
2833
+ expect(reply.answer).not.toContain('关键技术规格');
2834
+ expect(reply.answer).not.toContain('$T_g$');
2835
+ expect(reply.answer).not.toContain('本节介绍');
2836
+ expect(reply.answer).not.toContain('水杯');
2837
+ expect((reply.answer.match(/\\frac\{V\}\{N\^2\}/gu) || [])).toHaveLength(1);
2838
+ expect(reply.answer).toContain('\n\n$$\n');
2839
+ expect(reply.answer).toContain('\n### 建议学习路径\n');
2840
+ expect(reply.answer).toContain('1. **核心结构与数学基础**');
2841
+ expect(reply.answer).toContain('当前资料顺序,不代表显式前置关系');
2842
+ expect(reply.answer).not.toContain('后续分支包括');
2843
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
2844
+ expect(structuredBlock && 'answerTaskPlan' in structuredBlock ? structuredBlock.answerTaskPlan : undefined)
2845
+ .toEqual(expect.objectContaining({
2846
+ schemaVersion: '1',
2847
+ subtasks: expect.arrayContaining([
2848
+ expect.objectContaining({ kind: 'learning_route', required: true }),
2849
+ ]),
2850
+ }));
2851
+ expect(reply.answerReleaseReview.answerTaskCoverage).toEqual(expect.objectContaining({
2852
+ passed: true,
2853
+ coveredSubtaskIds: expect.arrayContaining(['learning_route']),
2854
+ }));
2855
+ });
2856
+ test('compound definition release stays bounded when the RAG pack contains a full document', () => {
2857
+ const sourcePath = 'Knowledge_Base/waterglass/Amorphous ice.md';
2858
+ const documentText = [
2859
+ '## 非晶冰',
2860
+ '非晶冰(Amorphous ice)是水的一种固态形式,其分子排列缺乏晶体冰的长程有序结构。',
2861
+ '### 核心概念与数学基础',
2862
+ '其结构可用径向分布函数 $g(r)$ 描述。',
2863
+ '$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$',
2864
+ '#### 4. 热力学:热量传递',
2865
+ '系统内部的温度场由热传导方程描述。',
2866
+ '### 常见用例与性能指标',
2867
+ '非晶冰可用于冷冻电子显微镜和天体物理学研究。',
2868
+ '### 关键技术规格',
2869
+ '不同密度和制备方法会形成不同形态。',
2870
+ '### 相关技术与比较数学模型',
2871
+ '```mermaid\ngraph LR\nA[非晶冰] --> B[晶体冰]\n```',
2872
+ ].join('\n');
2873
+ const item = makeQueryItem({
2874
+ atom: {
2875
+ id: 'atom_full_document_amorphous_ice',
2876
+ documentId: 'doc_full_document_amorphous_ice',
2877
+ title: '非晶冰',
2878
+ sourcePath,
2879
+ content: documentText,
2880
+ keywords: ['非晶冰', 'amorphous', 'ice'],
2881
+ },
2882
+ evidence: {
2883
+ id: 'evidence_full_document_amorphous_ice',
2884
+ sourcePath,
2885
+ snippet: documentText,
2886
+ startLine: 1,
2887
+ endLine: 12,
2888
+ },
2889
+ score: 0.98,
2890
+ });
2891
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2892
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2893
+ message: '什么是非晶冰?我应该通过哪些知识点学习?',
2894
+ answerLanguage: 'zh',
2895
+ knowledgePoints: points,
2896
+ citations: points[0]?.citations || [],
2897
+ recalledMemories: [],
2898
+ memoryActions: [],
2899
+ usedScope: globalScope,
2900
+ generatedAt: '2026-09-01T00:20:00.000Z',
2901
+ nextBlockId: (() => {
2902
+ let index = 0;
2903
+ return () => `full_document_compound_block_${++index}`;
2904
+ })(),
2905
+ ragContextPack: {
2906
+ query: '什么是非晶冰?我应该通过哪些知识点学习?',
2907
+ generatedAt: '2026-09-01T00:20:00.000Z',
2908
+ sourceBoundary: 'full_document',
2909
+ budget: { maxFragments: 4, maxCharsPerFragment: 1600, maxTotalChars: 5000 },
2910
+ fragments: [
2911
+ {
2912
+ fragmentId: 'rag_full_document_amorphous_ice',
2913
+ role: 'direct_support',
2914
+ atomId: 'atom_full_document_amorphous_ice',
2915
+ documentId: 'doc_full_document_amorphous_ice',
2916
+ sourcePath,
2917
+ title: '非晶冰',
2918
+ headingPath: ['非晶冰'],
2919
+ text: documentText,
2920
+ startLine: 1,
2921
+ endLine: 12,
2922
+ charCount: documentText.length,
2923
+ tokenEstimate: 180,
2924
+ truncated: false,
2925
+ citationIds: ['evidence_full_document_amorphous_ice'],
2926
+ relationEdgeIds: [],
2927
+ sourceBoundary: 'full_document',
2928
+ score: 0.98,
2929
+ },
2930
+ {
2931
+ fragmentId: 'rag_full_document_amorphous_ice_duplicate_formula',
2932
+ role: 'parent_context',
2933
+ atomId: 'atom_full_document_amorphous_ice',
2934
+ documentId: 'doc_full_document_amorphous_ice',
2935
+ sourcePath,
2936
+ title: '核心概念与数学基础',
2937
+ headingPath: ['非晶冰', '核心概念与数学基础'],
2938
+ text: '核心概念重复说明:$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$。',
2939
+ startLine: 4,
2940
+ endLine: 5,
2941
+ charCount: 110,
2942
+ tokenEstimate: 28,
2943
+ truncated: false,
2944
+ citationIds: ['evidence_full_document_amorphous_ice'],
2945
+ relationEdgeIds: [],
2946
+ sourceBoundary: 'full_document',
2947
+ score: 0.7,
2948
+ },
2949
+ ],
2950
+ sourceDecisions: [],
2951
+ totalCharCount: documentText.length + 110,
2952
+ tokenEstimate: 208,
2953
+ },
2954
+ ragSufficiencyReview: {
2955
+ reviewedAt: '2026-09-01T00:20:00.000Z',
2956
+ status: 'sufficient',
2957
+ score: 0.95,
2958
+ reasons: [],
2959
+ deterministic: true,
2960
+ recoveryAttempted: false,
2961
+ llmJudgeUsed: false,
2962
+ degradationState: 'none',
2963
+ },
2964
+ });
2965
+ expect(reply.answer).toContain('非晶冰(Amorphous ice)是水的一种固态形式');
2966
+ expect(reply.answer).toContain('g(r)');
2967
+ expect(reply.answer).toContain('$$');
2968
+ expect(reply.answer).not.toContain('常见用例与性能指标');
2969
+ expect(reply.answer).not.toContain('关键技术规格');
2970
+ expect(reply.answer).not.toContain('相关技术与比较数学模型');
2971
+ expect(reply.answer).not.toContain('```mermaid');
2972
+ expect(reply.graphAnswerPlan.answerTaskPlan?.learningRoute.length).toBeGreaterThanOrEqual(3);
2973
+ expect(reply.graphAnswerPlan.answerTaskPlan?.learningRoute.every((node) => node.evidenceRefs.length > 0)).toBe(true);
2974
+ expect(reply.answerReleaseReview.answerTaskCoverage).toEqual(expect.objectContaining({
2975
+ passed: true,
2976
+ coveredSubtaskIds: expect.arrayContaining(['definition', 'learning_route']),
2977
+ }));
2978
+ expect(reply.answer).not.toMatch(/(?:^|\\s)\\d+\\.?(?:\\s|$)/u);
2979
+ expect((reply.answer.match(/\\frac\{V\}\{N\^2\}/gu) || [])).toHaveLength(1);
2980
+ expect(reply.answer.length).toBeLessThan(900);
2981
+ });
2982
+ test('simple definition release keeps complete core formulas without expanding into later sections', () => {
2983
+ const sourcePath = 'Knowledge_Base/waterglass/water glass.md';
2984
+ const documentText = [
2985
+ '## 水杯 (water glass)',
2986
+ '此处的“水杯”被定义为一个由水和透明玻璃容器组成的物理系统。',
2987
+ '### 核心概念及其数学基础',
2988
+ '系统内部温度由热传导方程描述: $$ \\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T $$。',
2989
+ '当光线穿过空气、玻璃和水时会发生折射,满足斯涅尔定律: $$ n_1\\sin(\\theta_1)=n_2\\sin(\\theta_2) $$。',
2990
+ '### 常见用例与量化性能指标',
2991
+ '静水压力可以用 P = rho g h 估算。',
2992
+ '### 相关技术与比较数学模型',
2993
+ '```mermaid\ngraph LR\nA[水杯] --> B[塑料杯]\n```',
2994
+ ].join('\n');
2995
+ const item = makeQueryItem({
2996
+ atom: {
2997
+ id: 'atom_full_document_water_glass',
2998
+ documentId: 'doc_full_document_water_glass',
2999
+ title: '水杯 (water glass)',
3000
+ sourcePath,
3001
+ content: documentText,
3002
+ keywords: ['水杯', 'water', 'glass'],
3003
+ },
3004
+ evidence: {
3005
+ id: 'evidence_full_document_water_glass',
3006
+ sourcePath,
3007
+ snippet: documentText,
3008
+ startLine: 1,
3009
+ endLine: 9,
3010
+ },
3011
+ score: 0.98,
3012
+ });
3013
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
3014
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
3015
+ message: '什么是waterglass?',
3016
+ answerLanguage: 'zh',
3017
+ knowledgePoints: points,
3018
+ citations: points[0]?.citations || [],
3019
+ recalledMemories: [],
3020
+ memoryActions: [],
3021
+ usedScope: globalScope,
3022
+ generatedAt: '2026-09-01T00:30:00.000Z',
3023
+ nextBlockId: (() => {
3024
+ let index = 0;
3025
+ return () => `simple_full_document_block_${++index}`;
3026
+ })(),
3027
+ ragContextPack: {
3028
+ query: '什么是waterglass?',
3029
+ generatedAt: '2026-09-01T00:30:00.000Z',
3030
+ sourceBoundary: 'full_document',
3031
+ budget: { maxFragments: 2, maxCharsPerFragment: 1800, maxTotalChars: 3600 },
3032
+ fragments: [{
3033
+ fragmentId: 'rag_full_document_water_glass',
3034
+ role: 'direct_support',
3035
+ atomId: 'atom_full_document_water_glass',
3036
+ documentId: 'doc_full_document_water_glass',
3037
+ sourcePath,
3038
+ title: '水杯 (water glass)',
3039
+ headingPath: ['水杯 (water glass)'],
3040
+ text: documentText,
3041
+ startLine: 1,
3042
+ endLine: 9,
3043
+ charCount: documentText.length,
3044
+ tokenEstimate: 160,
3045
+ truncated: false,
3046
+ citationIds: ['evidence_full_document_water_glass'],
3047
+ relationEdgeIds: [],
3048
+ sourceBoundary: 'full_document',
3049
+ score: 0.98,
3050
+ }],
3051
+ sourceDecisions: [],
3052
+ totalCharCount: documentText.length,
3053
+ tokenEstimate: 160,
3054
+ },
3055
+ ragSufficiencyReview: {
3056
+ reviewedAt: '2026-09-01T00:30:00.000Z',
3057
+ status: 'sufficient',
3058
+ score: 0.95,
3059
+ reasons: [],
3060
+ deterministic: true,
3061
+ recoveryAttempted: false,
3062
+ llmJudgeUsed: false,
3063
+ degradationState: 'none',
3064
+ },
3065
+ });
3066
+ expect(reply.answer).toContain('水杯');
3067
+ expect(reply.answer).toContain('\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T');
3068
+ expect(reply.answer).toContain('n_1\\sin(\\theta_1)=n_2\\sin(\\theta_2)');
3069
+ expect(reply.answer).not.toContain('常见用例与量化性能指标');
3070
+ expect(reply.answer).not.toContain('相关技术与比较数学模型');
3071
+ expect(reply.answer).not.toContain('```mermaid');
3072
+ expect((reply.answer.match(/\\frac\{\\partial T\}\{\\partial t\}=\\alpha\\nabla\^2 T/gu) || [])).toHaveLength(1);
3073
+ expect((reply.answer.match(/n_1\\sin\(\\theta_1\)=n_2\\sin\(\\theta_2\)/gu) || [])).toHaveLength(1);
3074
+ expect(reply.answer.length).toBeLessThan(900);
3075
+ });
3076
+ test('uses the explicit answer language for grounded and deferred action text', () => {
3077
+ const item = makeQueryItem({
3078
+ atom: {
3079
+ id: 'atom_language_contract',
3080
+ documentId: 'doc_language_contract',
3081
+ title: 'Language Contract',
3082
+ content: 'A language contract keeps the answer and follow-up actions consistent.',
3083
+ },
3084
+ evidence: {
3085
+ id: 'evidence_language_contract',
3086
+ snippet: 'A language contract keeps the answer and follow-up actions consistent.',
3087
+ },
3088
+ score: 0.95,
3089
+ });
3090
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
3091
+ const buildReply = (answerLanguage) => (0, conversationComposer_1.buildScopedConversationReply)({
3092
+ message: 'what is language contract?',
3093
+ answerLanguage,
3094
+ knowledgePoints: points,
3095
+ citations: points[0].citations || [],
3096
+ recalledMemories: [],
3097
+ memoryActions: [],
3098
+ usedScope: globalScope,
3099
+ generatedAt: '2026-07-12T00:10:00.000Z',
3100
+ nextBlockId: (() => {
3101
+ let index = 0;
3102
+ return () => `language_block_${answerLanguage}_${++index}`;
3103
+ })(),
3104
+ });
3105
+ const englishBlock = buildReply('en').assistantBlocks.find((block) => block.type === 'structured_answer');
3106
+ const chineseBlock = buildReply('zh').assistantBlocks.find((block) => block.type === 'structured_answer');
3107
+ expect(englishBlock && 'title' in englishBlock ? englishBlock.title : '').toBe('Grounded Answer');
3108
+ expect(chineseBlock && 'title' in chineseBlock ? chineseBlock.title : '').toBe('可信回答');
3109
+ expect(englishBlock && 'directAnswer' in englishBlock ? englishBlock.directAnswer : '').toMatch(/\.$/u);
3110
+ expect(englishBlock && 'nextActionsMarkdown' in englishBlock ? englishBlock.nextActionsMarkdown : '').toContain('## Next Actions');
3111
+ expect(chineseBlock && 'directAnswer' in chineseBlock ? chineseBlock.directAnswer : '').toMatch(/。$/u);
3112
+ expect(chineseBlock && 'nextActionsMarkdown' in chineseBlock ? chineseBlock.nextActionsMarkdown : '').toContain('## 下一步行动');
3113
+ expect(chineseBlock && 'nextActionsMarkdown' in chineseBlock ? chineseBlock.nextActionsMarkdown : '').not.toContain('## Next Actions');
3114
+ expect(chineseBlock && 'overviewMarkdown' in chineseBlock ? chineseBlock.overviewMarkdown : '').toContain('## \u56de\u7b54\u4e0a\u4e0b\u6587');
3115
+ expect(chineseBlock && 'explanationMarkdown' in chineseBlock ? chineseBlock.explanationMarkdown : '').toContain('## \u8bf4\u660e');
3116
+ expect(chineseBlock && 'evidenceMarkdown' in chineseBlock ? chineseBlock.evidenceMarkdown : '').toContain('## \u8bc1\u636e\u6458\u8981');
3117
+ expect(buildReply('zh').assistantBlocks).toEqual(expect.arrayContaining([
3118
+ expect.objectContaining({ type: 'knowledge_run_summary', title: '\u77e5\u8bc6\u8fd0\u884c' }),
3119
+ expect.objectContaining({ type: 'citations', title: '\u5f15\u7528' }),
3120
+ expect.objectContaining({ type: 'knowledge_actions', title: '\u77e5\u8bc6\u64cd\u4f5c' }),
3121
+ expect.objectContaining({ type: 'system_notice', text: expect.stringContaining('\u672a\u53ec\u56de') }),
3122
+ ]));
3123
+ });
1721
3124
  test('fails graph comparison gate when compare intent only has reference context and no real branch signal', () => {
1722
3125
  const knowledgePoints = [
1723
3126
  {