noteconnection 1.7.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 (328) hide show
  1. package/LICENSE +674 -21
  2. package/README.md +260 -1626
  3. package/dist/src/agent_workspace.contract.parity.test.js +159 -0
  4. package/dist/src/agent_workspace.frontend.test.js +10388 -0
  5. package/dist/src/agent_workspace.locale.contract.test.js +95 -0
  6. package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
  7. package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
  8. package/dist/src/appimage.patchelf.test.js +113 -0
  9. package/dist/src/appimage.portability.test.js +156 -0
  10. package/dist/src/backend/FileLoader.js +18 -4
  11. package/dist/src/backend/FileLoader.test.js +125 -0
  12. package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
  13. package/dist/src/backend/GraphBuilder.js +66 -18
  14. package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
  15. package/dist/src/backend/GraphMetrics.js +0 -3
  16. package/dist/src/backend/ResourceIdentity.js +155 -0
  17. package/dist/src/backend/ResourceIdentity.test.js +113 -0
  18. package/dist/src/backend/algorithms/CycleDetection.js +0 -1
  19. package/dist/src/backend/algorithms/HybridEngine.js +0 -3
  20. package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
  21. package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
  22. package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
  23. package/dist/src/backend/build_dag.js +0 -1
  24. package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
  25. package/dist/src/capacitor.runtime.contract.test.js +11 -2
  26. package/dist/src/copy.assets.contract.test.js +130 -0
  27. package/dist/src/core/Graph.js +138 -42
  28. package/dist/src/core/Graph.test.js +74 -0
  29. package/dist/src/core/NoteConnection.js +2 -2
  30. package/dist/src/core/PathBridge.js +293 -5
  31. package/dist/src/core/ResourceReference.js +17 -0
  32. package/dist/src/desktop.godot.pack.test.js +142 -0
  33. package/dist/src/desktop.runtime.graph.test.js +66 -0
  34. package/dist/src/export/WorkspaceExportBundle.js +1174 -0
  35. package/dist/src/export/WorkspaceExportBundle.test.js +1541 -0
  36. package/dist/src/export/index.js +18 -0
  37. package/dist/src/export/types.js +2 -0
  38. package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
  39. package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
  40. package/dist/src/foundation.release.evidence.contract.test.js +524 -0
  41. package/dist/src/foundation.sqlite.runtime.contract.test.js +88 -0
  42. package/dist/src/frontend/README.md +260 -1626
  43. package/dist/src/frontend/agent_workspace.js +6011 -0
  44. package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
  45. package/dist/src/frontend/analysis.js +31 -4
  46. package/dist/src/frontend/app.js +2178 -62
  47. package/dist/src/frontend/focus_mode_interactions.js +33 -0
  48. package/dist/src/frontend/godot_future_path_renderer.js +540 -0
  49. package/dist/src/frontend/godot_tree_interactions.js +178 -0
  50. package/dist/src/frontend/graph_state.mjs +105 -0
  51. package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
  52. package/dist/src/frontend/i18n.mjs +186 -0
  53. package/dist/src/frontend/index.html +672 -268
  54. package/dist/src/frontend/knowledge_projection_contract.js +230 -0
  55. package/dist/src/frontend/knowledge_projection_store.js +214 -0
  56. package/dist/src/frontend/layout_gpu.js +12 -4
  57. package/dist/src/frontend/libs/path_core.js +158 -42
  58. package/dist/src/frontend/locales/en.json +753 -5
  59. package/dist/src/frontend/locales/zh.json +753 -5
  60. package/dist/src/frontend/main.mjs +60 -0
  61. package/dist/src/frontend/markdown_runtime.js +827 -0
  62. package/dist/src/frontend/mobile_budget_runtime.js +33 -0
  63. package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
  64. package/dist/src/frontend/mobile_identity_contract.js +221 -0
  65. package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
  66. package/dist/src/frontend/notemd.css +49 -0
  67. package/dist/src/frontend/notemd.html +7 -1
  68. package/dist/src/frontend/notemd.js +64 -0
  69. package/dist/src/frontend/path.html +113 -2
  70. package/dist/src/frontend/path_app.js +2108 -130
  71. package/dist/src/frontend/path_layout.mjs +143 -0
  72. package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
  73. package/dist/src/frontend/path_modules_bridge.js +486 -0
  74. package/dist/src/frontend/path_state.mjs +118 -0
  75. package/dist/src/frontend/path_styles.css +146 -0
  76. package/dist/src/frontend/path_worker_bridge.mjs +85 -0
  77. package/dist/src/frontend/reader.js +393 -33
  78. package/dist/src/frontend/runtime_bridge.js +67 -54
  79. package/dist/src/frontend/runtime_bridge.mjs +279 -0
  80. package/dist/src/frontend/settings.js +130 -12
  81. package/dist/src/frontend/simulationWorker.js +1 -1
  82. package/dist/src/frontend/source_manager.js +297 -22
  83. package/dist/src/frontend/storage_provider.js +661 -71
  84. package/dist/src/frontend/styles.css +2814 -72
  85. package/dist/src/frontend/workbench_state.mjs +101 -0
  86. package/dist/src/frontend/workspace_panes.js +10272 -0
  87. package/dist/src/frontend.locale.contract.test.js +62 -0
  88. package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
  89. package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
  90. package/dist/src/graph.answer.operator.contract.test.js +72 -0
  91. package/dist/src/index.js +6 -2
  92. package/dist/src/index.knowledge-root.test.js +118 -0
  93. package/dist/src/indexing/IndexLifecycle.js +217 -0
  94. package/dist/src/indexing/IndexLifecycle.test.js +73 -0
  95. package/dist/src/indexing/SegmentBuilder.js +64 -0
  96. package/dist/src/indexing/UnitBuilder.js +48 -0
  97. package/dist/src/indexing/types.js +2 -0
  98. package/dist/src/knowledge.api.contract.test.js +172 -0
  99. package/dist/src/knowledge_projection.contract.test.js +134 -0
  100. package/dist/src/knowledge_projection_store.test.js +200 -0
  101. package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
  102. package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
  103. package/dist/src/learning/KnowledgeLearningPlatform.js +11181 -0
  104. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +745 -0
  105. package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
  106. package/dist/src/learning/KnowledgeLearningPlatform.test.js +3208 -0
  107. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2998 -0
  108. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3936 -0
  109. package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
  110. package/dist/src/learning/PublicAnswerContract.test.js +85 -0
  111. package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
  112. package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
  113. package/dist/src/learning/agentConversationExecution.js +211 -0
  114. package/dist/src/learning/agentConversationExecution.test.js +209 -0
  115. package/dist/src/learning/agentConversationSerialization.js +266 -0
  116. package/dist/src/learning/agentConversationSerialization.test.js +165 -0
  117. package/dist/src/learning/agentResponseBudget.js +179 -0
  118. package/dist/src/learning/agentResponseBudget.test.js +97 -0
  119. package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
  120. package/dist/src/learning/answerReleaseReview.js +5260 -0
  121. package/dist/src/learning/answerReleaseReview.test.js +4174 -0
  122. package/dist/src/learning/answerTaskPlan.js +473 -0
  123. package/dist/src/learning/api.js +2 -0
  124. package/dist/src/learning/conversationComposer.js +1721 -0
  125. package/dist/src/learning/conversationComposer.test.js +3220 -0
  126. package/dist/src/learning/domains/ConversationManager.js +53 -0
  127. package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
  128. package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
  129. package/dist/src/learning/domains/MasteryEngine.js +387 -0
  130. package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
  131. package/dist/src/learning/domains/QualityEvaluator.js +307 -0
  132. package/dist/src/learning/domains/TutorRouter.js +313 -0
  133. package/dist/src/learning/domains/index.js +33 -0
  134. package/dist/src/learning/domains/types.js +7 -0
  135. package/dist/src/learning/errors.js +29 -0
  136. package/dist/src/learning/evidenceContextAssembler.js +1368 -0
  137. package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
  138. package/dist/src/learning/graphAnswerCoverage.js +37 -0
  139. package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
  140. package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
  141. package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
  142. package/dist/src/learning/graphAnswerFacts.js +94 -0
  143. package/dist/src/learning/graphAnswerFacts.test.js +39 -0
  144. package/dist/src/learning/graphAnswerPlan.js +908 -0
  145. package/dist/src/learning/graphAnswerPlan.test.js +907 -0
  146. package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
  147. package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
  148. package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
  149. package/dist/src/learning/graphClaimMatcher.js +104 -0
  150. package/dist/src/learning/graphConditionedContext.js +109 -0
  151. package/dist/src/learning/graphConditionedContext.test.js +99 -0
  152. package/dist/src/learning/graphContextAssembler.js +860 -0
  153. package/dist/src/learning/graphContextAssembler.test.js +1033 -0
  154. package/dist/src/learning/graphExpansionPolicy.js +22 -0
  155. package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
  156. package/dist/src/learning/identityMetadata.test.js +26 -0
  157. package/dist/src/learning/index.js +29 -0
  158. package/dist/src/learning/mobileAnswerProjection.js +104 -0
  159. package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
  160. package/dist/src/learning/queryBackend.js +1922 -0
  161. package/dist/src/learning/queryBackend.test.js +976 -0
  162. package/dist/src/learning/ragContextPack.js +356 -0
  163. package/dist/src/learning/ragContextPack.test.js +311 -0
  164. package/dist/src/learning/ragEvidenceQuality.js +96 -0
  165. package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
  166. package/dist/src/learning/ragPublicText.js +101 -0
  167. package/dist/src/learning/ragPublicText.test.js +57 -0
  168. package/dist/src/learning/ragSufficiencyJudge.js +171 -0
  169. package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
  170. package/dist/src/learning/ragSufficiencyProviderJudge.js +217 -0
  171. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +207 -0
  172. package/dist/src/learning/requestNormalization.js +304 -0
  173. package/dist/src/learning/requestNormalization.test.js +101 -0
  174. package/dist/src/learning/runtimeCapability.js +4677 -0
  175. package/dist/src/learning/runtimeCapability.test.js +3635 -0
  176. package/dist/src/learning/store.js +1344 -0
  177. package/dist/src/learning/store.test.js +1425 -0
  178. package/dist/src/learning/tutorAdapter.js +2 -0
  179. package/dist/src/learning/types.js +2 -0
  180. package/dist/src/learning/vectorAccelerationAdapter.js +958 -0
  181. package/dist/src/learning/vectorAccelerationAdapter.test.js +412 -0
  182. package/dist/src/learning/workspaceHydration.js +168 -0
  183. package/dist/src/learning/workspaceHydration.test.js +30 -0
  184. package/dist/src/lfs.asset.policy.contract.test.js +153 -0
  185. package/dist/src/license.policy.contract.test.js +71 -0
  186. package/dist/src/markdown/MarkdownGateway.js +4 -0
  187. package/dist/src/markdown/MarkdownGateway.test.js +59 -0
  188. package/dist/src/memory/MemoryGovernance.js +74 -0
  189. package/dist/src/memory/MemoryGovernance.test.js +46 -0
  190. package/dist/src/memory/types.js +2 -0
  191. package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
  192. package/dist/src/middleware/SseResponseWriter.js +122 -0
  193. package/dist/src/middleware/SseResponseWriter.test.js +54 -0
  194. package/dist/src/middleware/auth.js +38 -0
  195. package/dist/src/middleware/auth.test.js +33 -0
  196. package/dist/src/middleware/body-parser.js +45 -0
  197. package/dist/src/middleware/cors.js +44 -0
  198. package/dist/src/middleware/index.js +22 -0
  199. package/dist/src/middleware/request-trace.js +96 -0
  200. package/dist/src/mobile.android.evidence.contract.test.js +146 -0
  201. package/dist/src/mobile.android.signing.contract.test.js +66 -0
  202. package/dist/src/mobile.artifact.contract.test.js +144 -0
  203. package/dist/src/mobile.budget.contract.test.js +90 -0
  204. package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
  205. package/dist/src/mobile.graph.parity.contract.test.js +207 -0
  206. package/dist/src/mobile.identity.contract.test.js +66 -0
  207. package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
  208. package/dist/src/mobile.identity.corpus.test.js +40 -0
  209. package/dist/src/mobile.native.recovery.contract.test.js +64 -0
  210. package/dist/src/mobile.pipeline.test.js +11 -1
  211. package/dist/src/mobile.profile.contract.test.js +225 -0
  212. package/dist/src/mobile.semantic.parity.test.js +92 -0
  213. package/dist/src/mobile_exact_analyzer.test.js +113 -0
  214. package/dist/src/notemd/AppConfigToml.js +6 -4
  215. package/dist/src/notemd/ContentGenerator.js +4 -9
  216. package/dist/src/notemd/FileProcessor.js +4 -9
  217. package/dist/src/notemd/MermaidProcessor.js +400 -50
  218. package/dist/src/notemd/NotemdService.js +506 -26
  219. package/dist/src/notemd/PromptManager.js +15 -0
  220. package/dist/src/notemd/Translator.js +4 -9
  221. package/dist/src/notemd/cli/commands.js +357 -0
  222. package/dist/src/notemd/cli/dispatcher.js +225 -0
  223. package/dist/src/notemd/cli/index.js +169 -0
  224. package/dist/src/notemd/cli/parser.js +68 -0
  225. package/dist/src/notemd/cli/types.js +2 -0
  226. package/dist/src/notemd/constants.js +43 -0
  227. package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
  228. package/dist/src/notemd/diagram/diagramSpec.js +79 -0
  229. package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
  230. package/dist/src/notemd/diagram/intent.js +95 -0
  231. package/dist/src/notemd/diagram/planner.js +71 -0
  232. package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
  233. package/dist/src/notemd/diagram/types.js +18 -0
  234. package/dist/src/notemd/index.js +26 -0
  235. package/dist/src/notemd/operations/capabilityManifest.js +23 -0
  236. package/dist/src/notemd/operations/cliContracts.js +17 -0
  237. package/dist/src/notemd/operations/configProfileCommands.js +85 -0
  238. package/dist/src/notemd/operations/registry.contract.test.js +95 -0
  239. package/dist/src/notemd/operations/registry.js +991 -0
  240. package/dist/src/notemd/operations/types.js +2 -0
  241. package/dist/src/notemd/progressReporter.js +9 -0
  242. package/dist/src/notemd/providerDiagnostics.js +220 -0
  243. package/dist/src/notemd/providerProfiles.js +42 -0
  244. package/dist/src/notemd/providerTemplates.js +231 -0
  245. package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
  246. package/dist/src/notemd/search/SearchManager.js +13 -0
  247. package/dist/src/notemd/search/SearchProvider.js +2 -0
  248. package/dist/src/notemd/search/TavilyProvider.js +44 -0
  249. package/dist/src/notemd.agent.manifest.test.js +85 -0
  250. package/dist/src/notemd.api.contract.test.js +14 -1
  251. package/dist/src/notemd.app_config_toml.test.js +2 -1
  252. package/dist/src/notemd.batch.workflow.test.js +117 -0
  253. package/dist/src/notemd.cli.e2e.test.js +136 -0
  254. package/dist/src/notemd.core.test.js +51 -0
  255. package/dist/src/notemd.diagram.pipeline.test.js +233 -0
  256. package/dist/src/notemd.providerTemplates.test.js +34 -0
  257. package/dist/src/notemd.server.integration.test.js +262 -39
  258. package/dist/src/notemd.workflow.pipeline.test.js +162 -0
  259. package/dist/src/pathbridge.handshake.contract.test.js +45 -2
  260. package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
  261. package/dist/src/pathcore.bundle.contract.test.js +8 -0
  262. package/dist/src/pathmode.background.contract.test.js +69 -0
  263. package/dist/src/pathmode.settings.api.contract.test.js +9 -0
  264. package/dist/src/pkg.sidecar.contract.test.js +9 -3
  265. package/dist/src/platform/ExportProfile.js +86 -0
  266. package/dist/src/platform/MobileBudget.js +11 -0
  267. package/dist/src/platform/PlatformCapabilities.js +59 -0
  268. package/dist/src/platform/PlatformCapabilities.test.js +36 -0
  269. package/dist/src/platform/RenderMaterializer.js +33 -0
  270. package/dist/src/platform/RenderMaterializer.test.js +32 -0
  271. package/dist/src/query_backend.external_http.integration.test.js +410 -0
  272. package/dist/src/reader_renderer.js +404 -3
  273. package/dist/src/reader_renderer.test.js +87 -0
  274. package/dist/src/release.godot.mirror.contract.test.js +97 -0
  275. package/dist/src/resources/ResourceRegistry.js +263 -0
  276. package/dist/src/resources/ResourceRegistry.test.js +97 -0
  277. package/dist/src/resources/types.js +2 -0
  278. package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
  279. package/dist/src/routes/index.js +19 -0
  280. package/dist/src/routes/knowledge.js +981 -0
  281. package/dist/src/routes/knowledgePayload.js +185 -0
  282. package/dist/src/routes/knowledgePayload.test.js +53 -0
  283. package/dist/src/routes/markdown.js +295 -0
  284. package/dist/src/routes/notemd.js +1321 -0
  285. package/dist/src/routes/registry.contract.test.js +133 -0
  286. package/dist/src/routes/registry.shadow.contract.test.js +22 -0
  287. package/dist/src/routes/render.js +248 -0
  288. package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
  289. package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
  290. package/dist/src/routes/settings.js +6 -0
  291. package/dist/src/routes/staticFiles.js +94 -0
  292. package/dist/src/routes/types.js +2 -0
  293. package/dist/src/runtime.capabilities.test.js +18 -2
  294. package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
  295. package/dist/src/server.js +10342 -1325
  296. package/dist/src/server.migration.test.js +584 -10
  297. package/dist/src/server.port.fallback.contract.test.js +63 -0
  298. package/dist/src/session/SessionStateStore.js +81 -0
  299. package/dist/src/session/SessionStateStore.test.js +58 -0
  300. package/dist/src/session/types.js +2 -0
  301. package/dist/src/settings.runtime.contract.test.js +50 -0
  302. package/dist/src/shared/types.contract.test.js +107 -0
  303. package/dist/src/shared/types.js +22 -0
  304. package/dist/src/sidecar.build.lock.contract.test.js +201 -0
  305. package/dist/src/sidecar.freshness.contract.test.js +184 -0
  306. package/dist/src/sidecar.replacement.boundary.contract.test.js +131 -0
  307. package/dist/src/sidecar.supply.readiness.contract.test.js +155 -0
  308. package/dist/src/simulation.worker.offline.test.js +104 -0
  309. package/dist/src/source_manager.loadflow.test.js +46 -0
  310. package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
  311. package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
  312. package/dist/src/storage.provider.contract.test.js +152 -1
  313. package/dist/src/tauri.frontend.build.contract.test.js +68 -0
  314. package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
  315. package/dist/src/tauri.window.evidence.execution.test.js +68 -0
  316. package/dist/src/utils/RuntimePaths.js +10 -13
  317. package/dist/src/utils/RuntimePaths.test.js +27 -0
  318. package/dist/src/utils/platform.js +153 -0
  319. package/dist/src/windows.installer.build.test.js +109 -0
  320. package/dist/src/windows.installer.payload.test.js +76 -0
  321. package/dist/src/windows.installer.runtime.test.js +69 -0
  322. package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
  323. package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
  324. package/dist/src/workflows/types.js +2 -0
  325. package/dist/src/workspace/WorkspaceRegistry.js +135 -0
  326. package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
  327. package/dist/src/workspace/types.js +2 -0
  328. package/package.json +80 -15
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_RAG_CONTEXT_BUDGET = void 0;
4
+ exports.normalizeRagContextBudget = normalizeRagContextBudget;
5
+ exports.estimateRagTokenCount = estimateRagTokenCount;
6
+ exports.buildRagContextPack = buildRagContextPack;
7
+ const crypto_1 = require("crypto");
8
+ const graphClaimMatcher_1 = require("./graphClaimMatcher");
9
+ const ragPublicText_1 = require("./ragPublicText");
10
+ exports.DEFAULT_RAG_CONTEXT_BUDGET = {
11
+ maxFragments: 16,
12
+ maxCharsPerFragment: 1600,
13
+ maxTotalChars: 6400,
14
+ };
15
+ const ROLE_PRIORITY = {
16
+ direct_support: 0,
17
+ conflict: 1,
18
+ graph_neighbor_support: 2,
19
+ parent_context: 3,
20
+ adjacent_context: 4,
21
+ background: 5,
22
+ };
23
+ function normalizeRagContextBudget(input) {
24
+ const productCapDisabled = input?.productCapDisabled === true;
25
+ const maxFragmentsLimit = productCapDisabled ? 4096 : 512;
26
+ const maxTotalCharsLimit = productCapDisabled ? 512000 : 512000;
27
+ const numberOrDefault = (value, fallback, min, max) => {
28
+ const numeric = Number(value);
29
+ if (!Number.isFinite(numeric)) {
30
+ return fallback;
31
+ }
32
+ return Math.floor(Math.max(min, Math.min(max, numeric)));
33
+ };
34
+ return {
35
+ maxFragments: numberOrDefault(input?.maxFragments, exports.DEFAULT_RAG_CONTEXT_BUDGET.maxFragments, 1, maxFragmentsLimit),
36
+ maxCharsPerFragment: numberOrDefault(input?.maxCharsPerFragment, exports.DEFAULT_RAG_CONTEXT_BUDGET.maxCharsPerFragment, 80, 20000),
37
+ maxTotalChars: numberOrDefault(input?.maxTotalChars, exports.DEFAULT_RAG_CONTEXT_BUDGET.maxTotalChars, 120, maxTotalCharsLimit),
38
+ ...(productCapDisabled ? {
39
+ productCapDisabled: true,
40
+ runtimeMaxFragments: numberOrDefault(input?.runtimeMaxFragments, input?.maxFragments ?? exports.DEFAULT_RAG_CONTEXT_BUDGET.maxFragments, 1, 4096),
41
+ runtimeMaxCharsPerFragment: numberOrDefault(input?.runtimeMaxCharsPerFragment, input?.maxCharsPerFragment ?? exports.DEFAULT_RAG_CONTEXT_BUDGET.maxCharsPerFragment, 80, 20000),
42
+ runtimeMaxTotalChars: numberOrDefault(input?.runtimeMaxTotalChars, input?.maxTotalChars ?? exports.DEFAULT_RAG_CONTEXT_BUDGET.maxTotalChars, 120, 64 * 1024 * 1024),
43
+ } : {}),
44
+ };
45
+ }
46
+ function estimateRagTokenCount(text) {
47
+ const normalized = String(text || '').trim();
48
+ if (!normalized) {
49
+ return 0;
50
+ }
51
+ return Math.max(1, Math.ceil(normalized.length / 4));
52
+ }
53
+ const QUERY_WINDOW_STOPWORDS = new Set([
54
+ 'and', 'compare', 'contrast', 'difference', 'the', 'versus', 'with',
55
+ ]);
56
+ function queryWindowTerms(query) {
57
+ return Array.from(new Set((String(query || '').toLowerCase().match(/[a-z0-9][a-z0-9_-]{2,}|[\u3400-\u9fff]{2,}/gu) || [])
58
+ .filter((term) => !QUERY_WINDOW_STOPWORDS.has(term))));
59
+ }
60
+ function queryTermCoverage(text, terms) {
61
+ const normalized = text.toLowerCase();
62
+ return terms.reduce((count, term) => count + (normalized.includes(term) ? 1 : 0), 0);
63
+ }
64
+ function queryFeatureCoverage(text, queryFeatures) {
65
+ return (0, graphClaimMatcher_1.semanticFeatures)(text).reduce((count, feature) => count + (queryFeatures.has(feature) ? 1 : 0), 0);
66
+ }
67
+ function queryWindowAnchors(source, terms, queryFeatures) {
68
+ const anchors = new Set();
69
+ const normalizedSource = source.toLowerCase();
70
+ terms.forEach((term) => {
71
+ let matchIndex = normalizedSource.indexOf(term);
72
+ while (matchIndex >= 0) {
73
+ anchors.add(matchIndex);
74
+ matchIndex = normalizedSource.indexOf(term, matchIndex + term.length);
75
+ }
76
+ });
77
+ const clausePattern = /[^\n.!?。!?;;]+(?:[.!?。!?;;]+|$)/gu;
78
+ let clauseMatch;
79
+ while ((clauseMatch = clausePattern.exec(source)) !== null) {
80
+ if (queryFeatureCoverage(clauseMatch[0], queryFeatures) > 0) {
81
+ anchors.add(clauseMatch.index + Math.floor(clauseMatch[0].length / 2));
82
+ }
83
+ }
84
+ return Array.from(anchors).sort((left, right) => left - right);
85
+ }
86
+ function queryCenteredWindow(source, maxChars, query, baseline) {
87
+ const terms = queryWindowTerms(query);
88
+ const queryFeatures = new Set((0, graphClaimMatcher_1.semanticFeatures)(query));
89
+ if (queryFeatures.size < 2) {
90
+ return null;
91
+ }
92
+ const visibleSource = (0, ragPublicText_1.omitFencedMarkdownPayload)(source);
93
+ const markerLength = '\n[...]\n'.length;
94
+ const windowLength = Math.max(16, maxChars - markerLength * 2);
95
+ const visibleBaseline = (0, ragPublicText_1.omitFencedMarkdownPayload)(baseline);
96
+ const baselineFeatureCoverage = queryFeatureCoverage(visibleBaseline, queryFeatures);
97
+ const baselineTermCoverage = queryTermCoverage(visibleBaseline, terms);
98
+ let selected = null;
99
+ for (const anchor of queryWindowAnchors(visibleSource, terms, queryFeatures)) {
100
+ const start = Math.max(0, Math.min(visibleSource.length - windowLength, anchor - Math.floor(windowLength * 0.35)));
101
+ const candidate = visibleSource.slice(start, start + windowLength);
102
+ const featureCoverage = queryFeatureCoverage(candidate, queryFeatures);
103
+ const termCoverage = queryTermCoverage(candidate, terms);
104
+ if (!selected
105
+ || featureCoverage > selected.featureCoverage
106
+ || (featureCoverage === selected.featureCoverage
107
+ && termCoverage > selected.termCoverage)) {
108
+ selected = { start, text: candidate, featureCoverage, termCoverage };
109
+ }
110
+ }
111
+ if (!selected
112
+ || selected.featureCoverage < baselineFeatureCoverage
113
+ || (selected.featureCoverage === baselineFeatureCoverage
114
+ && selected.termCoverage <= baselineTermCoverage)) {
115
+ return null;
116
+ }
117
+ const prefix = selected.start > 0 ? '[...]\n' : '';
118
+ const suffix = selected.start + selected.text.length < visibleSource.length ? '\n[...]' : '';
119
+ return `${prefix}${selected.text.trim()}${suffix}`;
120
+ }
121
+ function truncateMiddle(text, maxChars, query) {
122
+ const source = String(text || '');
123
+ if (source.length <= maxChars) {
124
+ return { text: source, truncated: false };
125
+ }
126
+ if (maxChars <= 16) {
127
+ return { text: source.slice(0, maxChars), truncated: true };
128
+ }
129
+ const marker = '\n[...]\n';
130
+ const available = Math.max(0, maxChars - marker.length);
131
+ const headLength = Math.ceil(available * 0.6);
132
+ const tailLength = Math.floor(available * 0.4);
133
+ const baseline = `${source.slice(0, headLength).trimEnd()}${marker}${source.slice(source.length - tailLength).trimStart()}`;
134
+ return {
135
+ text: queryCenteredWindow(source, maxChars, query, baseline) || baseline,
136
+ truncated: true,
137
+ };
138
+ }
139
+ function withBudgetedText(fragment, maxChars, reason, query) {
140
+ const truncated = truncateMiddle(fragment.text, maxChars, query);
141
+ const text = truncated.text;
142
+ return {
143
+ ...fragment,
144
+ text,
145
+ charCount: text.length,
146
+ tokenEstimate: estimateRagTokenCount(text),
147
+ truncated: fragment.truncated || truncated.truncated,
148
+ truncationReason: truncated.truncated ? reason : fragment.truncationReason,
149
+ };
150
+ }
151
+ function rolePriority(fragment) {
152
+ return ROLE_PRIORITY[fragment.role] ?? ROLE_PRIORITY.background;
153
+ }
154
+ function sortFragmentsForBudget(fragments, fragmentOrder) {
155
+ const order = new Map((fragmentOrder || []).map((fragmentId, index) => [String(fragmentId), index]));
156
+ return fragments
157
+ .map((fragment, index) => ({ fragment, index }))
158
+ .sort((a, b) => {
159
+ const priorityDelta = rolePriority(a.fragment) - rolePriority(b.fragment);
160
+ if (priorityDelta !== 0) {
161
+ return priorityDelta;
162
+ }
163
+ const scoreDelta = Number(b.fragment.score || 0) - Number(a.fragment.score || 0);
164
+ if (Math.abs(scoreDelta) > 0.0001) {
165
+ return scoreDelta;
166
+ }
167
+ const conditionedOrderDelta = (order.get(a.fragment.fragmentId) ?? Number.MAX_SAFE_INTEGER)
168
+ - (order.get(b.fragment.fragmentId) ?? Number.MAX_SAFE_INTEGER);
169
+ if (conditionedOrderDelta !== 0) {
170
+ return conditionedOrderDelta;
171
+ }
172
+ return a.index - b.index;
173
+ })
174
+ .map((entry) => entry.fragment);
175
+ }
176
+ function applyContextBudget(fragments, budget, decisions, query, fragmentOrder) {
177
+ const selected = [];
178
+ let usedChars = 0;
179
+ const productCapDisabled = budget.productCapDisabled === true;
180
+ const maxFragments = productCapDisabled
181
+ ? budget.runtimeMaxFragments || budget.maxFragments
182
+ : budget.maxFragments;
183
+ const maxCharsPerFragment = productCapDisabled
184
+ ? budget.runtimeMaxCharsPerFragment || budget.maxCharsPerFragment
185
+ : budget.maxCharsPerFragment;
186
+ const maxTotalChars = productCapDisabled
187
+ ? budget.runtimeMaxTotalChars || budget.maxTotalChars
188
+ : budget.maxTotalChars;
189
+ sortFragmentsForBudget(fragments, fragmentOrder).forEach((fragment) => {
190
+ if (selected.length >= maxFragments) {
191
+ decisions.push({
192
+ documentId: fragment.documentId,
193
+ sourcePath: fragment.sourcePath,
194
+ sourceBoundary: fragment.sourceBoundary,
195
+ status: 'fragment_dropped',
196
+ reason: productCapDisabled ? 'runtime_fragment_limit_exceeded' : 'max_fragments_exceeded',
197
+ });
198
+ return;
199
+ }
200
+ let candidate = withBudgetedText(fragment, maxCharsPerFragment, productCapDisabled ? 'runtime_max_chars_per_fragment_exceeded' : 'max_chars_per_fragment_exceeded', query);
201
+ const remainingChars = maxTotalChars - usedChars;
202
+ if (remainingChars <= 0) {
203
+ decisions.push({
204
+ documentId: fragment.documentId,
205
+ sourcePath: fragment.sourcePath,
206
+ sourceBoundary: fragment.sourceBoundary,
207
+ status: 'fragment_dropped',
208
+ reason: productCapDisabled ? 'runtime_total_chars_exceeded' : 'max_total_chars_exceeded',
209
+ });
210
+ return;
211
+ }
212
+ if (candidate.charCount > remainingChars) {
213
+ if (remainingChars < 80 && candidate.role !== 'direct_support') {
214
+ decisions.push({
215
+ documentId: fragment.documentId,
216
+ sourcePath: fragment.sourcePath,
217
+ sourceBoundary: fragment.sourceBoundary,
218
+ status: 'fragment_dropped',
219
+ reason: 'remaining_context_budget_too_small',
220
+ });
221
+ return;
222
+ }
223
+ candidate = withBudgetedText(candidate, remainingChars, productCapDisabled ? 'runtime_total_chars_exceeded' : 'max_total_chars_exceeded', query);
224
+ }
225
+ selected.push(candidate);
226
+ usedChars += candidate.charCount;
227
+ decisions.push({
228
+ documentId: candidate.documentId,
229
+ sourcePath: candidate.sourcePath,
230
+ sourceBoundary: candidate.sourceBoundary,
231
+ status: candidate.truncated ? 'fragment_truncated' : 'fragment_included',
232
+ reason: candidate.truncationReason,
233
+ });
234
+ });
235
+ return selected;
236
+ }
237
+ function resolveSourceBoundary(requestedBoundary, fragments) {
238
+ if (requestedBoundary) {
239
+ return requestedBoundary;
240
+ }
241
+ return fragments.some((fragment) => fragment.sourceBoundary === 'full_document')
242
+ ? 'full_document'
243
+ : 'direct_span_only';
244
+ }
245
+ function annotateReadDecisions(decisions, fragments) {
246
+ const fragmentCountsByDocument = fragments.reduce((counts, fragment) => {
247
+ const previous = counts.get(fragment.documentId) || 0;
248
+ counts.set(fragment.documentId, previous + 1);
249
+ return counts;
250
+ }, new Map());
251
+ decisions.forEach((decision) => {
252
+ if (decision.status === 'read') {
253
+ decision.fragmentsSelected = fragmentCountsByDocument.get(decision.documentId) || 0;
254
+ }
255
+ });
256
+ }
257
+ function sortedStrings(values) {
258
+ return normalizedStrings(values).sort();
259
+ }
260
+ function normalizedStrings(values) {
261
+ return (Array.isArray(values) ? values : [])
262
+ .map((value) => String(value || '').trim())
263
+ .filter(Boolean);
264
+ }
265
+ function buildRagContextReplayId(params) {
266
+ const payload = {
267
+ query: params.query,
268
+ sourceBoundary: params.sourceBoundary,
269
+ budget: params.budget,
270
+ totalCharCount: params.totalCharCount,
271
+ tokenEstimate: params.tokenEstimate,
272
+ fragments: params.fragments
273
+ .map((fragment) => ({
274
+ fragmentId: fragment.fragmentId,
275
+ role: fragment.role,
276
+ documentId: fragment.documentId,
277
+ sourcePath: fragment.sourcePath,
278
+ atomId: fragment.atomId || '',
279
+ title: fragment.title || '',
280
+ headingPath: normalizedStrings(fragment.headingPath),
281
+ citationIds: sortedStrings(fragment.citationIds),
282
+ relationEdgeIds: sortedStrings(fragment.relationEdgeIds),
283
+ sourceBoundary: fragment.sourceBoundary,
284
+ startOffset: fragment.startOffset ?? null,
285
+ endOffset: fragment.endOffset ?? null,
286
+ startLine: fragment.startLine ?? null,
287
+ endLine: fragment.endLine ?? null,
288
+ truncated: fragment.truncated,
289
+ truncationReason: fragment.truncationReason || '',
290
+ text: fragment.text,
291
+ }))
292
+ .sort((left, right) => [
293
+ left.role.localeCompare(right.role),
294
+ left.documentId.localeCompare(right.documentId),
295
+ left.fragmentId.localeCompare(right.fragmentId),
296
+ left.text.localeCompare(right.text),
297
+ ].find((delta) => delta !== 0) || 0),
298
+ sourceDecisions: params.sourceDecisions
299
+ .map((decision) => ({
300
+ documentId: decision.documentId,
301
+ sourcePath: decision.sourcePath,
302
+ sourceBoundary: decision.sourceBoundary,
303
+ status: decision.status,
304
+ reason: decision.reason || '',
305
+ charsRead: decision.charsRead ?? null,
306
+ fragmentsSelected: decision.fragmentsSelected ?? null,
307
+ }))
308
+ .sort((left, right) => [
309
+ left.documentId.localeCompare(right.documentId),
310
+ left.sourcePath.localeCompare(right.sourcePath),
311
+ left.sourceBoundary.localeCompare(right.sourceBoundary),
312
+ left.status.localeCompare(right.status),
313
+ left.reason.localeCompare(right.reason),
314
+ ].find((delta) => delta !== 0) || 0),
315
+ graphConditioning: params.graphConditioning || null,
316
+ };
317
+ const digest = (0, crypto_1.createHash)('sha256')
318
+ .update(JSON.stringify(payload))
319
+ .digest('hex')
320
+ .slice(0, 16);
321
+ return `ragctx_${digest}`;
322
+ }
323
+ function buildRagContextPack(params) {
324
+ const budget = normalizeRagContextBudget(params.budget);
325
+ const decisions = Array.isArray(params.sourceDecisions)
326
+ ? params.sourceDecisions.map((decision) => ({ ...decision }))
327
+ : [];
328
+ const fragments = Array.isArray(params.fragments) ? params.fragments.filter(Boolean) : [];
329
+ const selectedFragments = applyContextBudget(fragments, budget, decisions, params.query, params.fragmentOrder);
330
+ annotateReadDecisions(decisions, selectedFragments);
331
+ const totalCharCount = selectedFragments.reduce((sum, fragment) => sum + fragment.charCount, 0);
332
+ const tokenEstimate = selectedFragments.reduce((sum, fragment) => sum + fragment.tokenEstimate, 0);
333
+ const query = String(params.query || '');
334
+ const sourceBoundary = resolveSourceBoundary(params.sourceBoundary, selectedFragments);
335
+ return {
336
+ replayId: buildRagContextReplayId({
337
+ query,
338
+ sourceBoundary,
339
+ budget,
340
+ fragments: selectedFragments,
341
+ sourceDecisions: decisions,
342
+ totalCharCount,
343
+ tokenEstimate,
344
+ graphConditioning: params.graphConditioning,
345
+ }),
346
+ query,
347
+ generatedAt: String(params.generatedAt || new Date().toISOString()),
348
+ sourceBoundary,
349
+ budget,
350
+ fragments: selectedFragments,
351
+ sourceDecisions: decisions,
352
+ totalCharCount,
353
+ tokenEstimate,
354
+ graphConditioning: params.graphConditioning,
355
+ };
356
+ }
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ragContextPack_1 = require("./ragContextPack");
4
+ function makeFragment(overrides = {}) {
5
+ const text = overrides.text || 'A water glass is a transparent drinking vessel that contains water.';
6
+ return {
7
+ fragmentId: overrides.fragmentId || `fragment_${overrides.role || 'direct_support'}`,
8
+ role: overrides.role || 'direct_support',
9
+ text,
10
+ atomId: overrides.atomId || 'atom_water_glass',
11
+ documentId: overrides.documentId || 'doc_water_glass',
12
+ sourcePath: overrides.sourcePath || 'Knowledge_Base/test/water-glass.md',
13
+ title: overrides.title || 'Water Glass',
14
+ headingPath: Array.isArray(overrides.headingPath) ? overrides.headingPath : ['Water Glass'],
15
+ startOffset: overrides.startOffset,
16
+ endOffset: overrides.endOffset,
17
+ startLine: overrides.startLine,
18
+ endLine: overrides.endLine,
19
+ charCount: text.length,
20
+ tokenEstimate: Math.ceil(text.length / 4),
21
+ truncated: overrides.truncated === true,
22
+ truncationReason: overrides.truncationReason,
23
+ citationIds: Array.isArray(overrides.citationIds) ? overrides.citationIds : ['evidence_water_glass'],
24
+ relationEdgeIds: Array.isArray(overrides.relationEdgeIds) ? overrides.relationEdgeIds : [],
25
+ score: Number.isFinite(overrides.score) ? Number(overrides.score) : 0.8,
26
+ sourceBoundary: overrides.sourceBoundary || 'direct_span_only',
27
+ };
28
+ }
29
+ describe('buildRagContextPack', () => {
30
+ test('generates a stable replay id from the selected evidence payload', () => {
31
+ const direct = makeFragment({
32
+ fragmentId: 'direct_water_glass',
33
+ role: 'direct_support',
34
+ text: 'Direct definition of water glass.',
35
+ score: 0.72,
36
+ });
37
+ const parent = makeFragment({
38
+ fragmentId: 'parent_water_glass',
39
+ role: 'parent_context',
40
+ text: 'Parent section explains the vessel boundary and optical qualifier.',
41
+ sourceBoundary: 'full_document',
42
+ score: 0.61,
43
+ });
44
+ const baseParams = {
45
+ query: 'what is water glass?',
46
+ sourceDecisions: [
47
+ {
48
+ documentId: 'doc_water_glass',
49
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
50
+ sourceBoundary: 'full_document',
51
+ status: 'read',
52
+ charsRead: 4096,
53
+ },
54
+ ],
55
+ budget: {
56
+ maxFragments: 4,
57
+ maxCharsPerFragment: 400,
58
+ maxTotalChars: 900,
59
+ },
60
+ generatedAt: '2026-07-05T00:00:00.000Z',
61
+ };
62
+ const firstPack = (0, ragContextPack_1.buildRagContextPack)({
63
+ ...baseParams,
64
+ fragments: [parent, direct],
65
+ });
66
+ const secondPack = (0, ragContextPack_1.buildRagContextPack)({
67
+ ...baseParams,
68
+ fragments: [direct, parent],
69
+ });
70
+ const changedPack = (0, ragContextPack_1.buildRagContextPack)({
71
+ ...baseParams,
72
+ fragments: [
73
+ direct,
74
+ {
75
+ ...parent,
76
+ text: 'Parent section now carries a different qualifier.',
77
+ charCount: 'Parent section now carries a different qualifier.'.length,
78
+ },
79
+ ],
80
+ });
81
+ expect(firstPack.replayId).toMatch(/^ragctx_[a-f0-9]{16}$/);
82
+ expect(secondPack.replayId).toBe(firstPack.replayId);
83
+ expect(changedPack.replayId).not.toBe(firstPack.replayId);
84
+ });
85
+ test('middle-truncates oversized fragments while preserving the opening and terminal qualifier', () => {
86
+ const longText = [
87
+ 'Opening definition: a water glass is a transparent vessel.',
88
+ 'Middle detail '.repeat(30),
89
+ 'Terminal qualifier: this claim only covers the scoped note.',
90
+ ].join('\n');
91
+ const pack = (0, ragContextPack_1.buildRagContextPack)({
92
+ query: 'what is water glass?',
93
+ fragments: [
94
+ makeFragment({
95
+ role: 'parent_context',
96
+ text: longText,
97
+ sourceBoundary: 'full_document',
98
+ }),
99
+ ],
100
+ sourceDecisions: [],
101
+ budget: {
102
+ maxFragments: 3,
103
+ maxCharsPerFragment: 180,
104
+ maxTotalChars: 220,
105
+ },
106
+ generatedAt: '2026-07-05T00:00:00.000Z',
107
+ });
108
+ expect(pack.fragments).toHaveLength(1);
109
+ expect(pack.fragments[0].truncated).toBe(true);
110
+ expect(pack.fragments[0].text).toContain('Opening definition');
111
+ expect(pack.fragments[0].text).toContain('Terminal qualifier');
112
+ expect(pack.fragments[0].text).toContain('[...]');
113
+ expect(pack.sourceDecisions).toEqual(expect.arrayContaining([
114
+ expect.objectContaining({
115
+ status: 'fragment_truncated',
116
+ reason: 'max_chars_per_fragment_exceeded',
117
+ }),
118
+ ]));
119
+ });
120
+ test('centers the bounded window on query terms when a later comparison clause would otherwise be dropped', () => {
121
+ const longText = [
122
+ 'Opening definition: a water glass is a transparent vessel.',
123
+ 'Background context '.repeat(28),
124
+ 'Comparison: a plastic cup is lightweight and less rigid than a water glass.',
125
+ 'Terminal qualifier: the comparison remains limited to the scoped note.',
126
+ ].join('\n');
127
+ const pack = (0, ragContextPack_1.buildRagContextPack)({
128
+ query: 'compare water glass and plastic cup',
129
+ fragments: [
130
+ makeFragment({
131
+ role: 'parent_context',
132
+ text: longText,
133
+ sourceBoundary: 'full_document',
134
+ }),
135
+ ],
136
+ sourceDecisions: [],
137
+ budget: {
138
+ maxFragments: 3,
139
+ maxCharsPerFragment: 180,
140
+ maxTotalChars: 220,
141
+ },
142
+ generatedAt: '2026-07-05T00:00:00.000Z',
143
+ });
144
+ expect(pack.fragments[0].text).toContain('plastic cup');
145
+ expect(pack.fragments[0].text).toContain('water glass');
146
+ });
147
+ test('prefers multilingual comparison prose outside fenced renderer payloads', () => {
148
+ const longText = [
149
+ 'Opening definition: a water glass is a transparent vessel.',
150
+ 'Background context '.repeat(20),
151
+ '```mermaid',
152
+ 'graph LR',
153
+ 'A[Water Glass] --> B[Plastic Cup PET]',
154
+ '```',
155
+ '相同尺寸下,PET塑料杯比玻璃水杯更轻、更有韧性,但刚度和透明度较低。',
156
+ 'Terminal qualifier: the comparison remains limited to the scoped note.',
157
+ ].join('\n');
158
+ const pack = (0, ragContextPack_1.buildRagContextPack)({
159
+ query: 'compare water glass and plastic cup',
160
+ fragments: [makeFragment({
161
+ role: 'parent_context',
162
+ text: longText,
163
+ sourceBoundary: 'full_document',
164
+ })],
165
+ sourceDecisions: [],
166
+ budget: {
167
+ maxFragments: 3,
168
+ maxCharsPerFragment: 180,
169
+ maxTotalChars: 220,
170
+ },
171
+ });
172
+ expect(pack.fragments[0].text).toContain('PET塑料杯');
173
+ expect(pack.fragments[0].text).not.toContain('graph LR');
174
+ });
175
+ test('uses role priority so weak background cannot displace direct or graph evidence', () => {
176
+ const pack = (0, ragContextPack_1.buildRagContextPack)({
177
+ query: 'what is water glass?',
178
+ fragments: [
179
+ makeFragment({
180
+ fragmentId: 'background_1',
181
+ role: 'background',
182
+ text: 'Background note with weak topical overlap.',
183
+ score: 0.95,
184
+ }),
185
+ makeFragment({
186
+ fragmentId: 'direct_1',
187
+ role: 'direct_support',
188
+ text: 'Direct definition of water glass.',
189
+ score: 0.7,
190
+ }),
191
+ makeFragment({
192
+ fragmentId: 'graph_1',
193
+ role: 'graph_neighbor_support',
194
+ text: 'Graph neighbor evidence about vessel boundary.',
195
+ score: 0.6,
196
+ sourceBoundary: 'full_document',
197
+ }),
198
+ ],
199
+ sourceDecisions: [],
200
+ budget: {
201
+ maxFragments: 2,
202
+ maxCharsPerFragment: 200,
203
+ maxTotalChars: 400,
204
+ },
205
+ });
206
+ expect(pack.fragments.map((fragment) => fragment.fragmentId)).toEqual(['direct_1', 'graph_1']);
207
+ expect(pack.sourceDecisions).toEqual(expect.arrayContaining([
208
+ expect.objectContaining({
209
+ status: 'fragment_dropped',
210
+ reason: 'max_fragments_exceeded',
211
+ documentId: 'doc_water_glass',
212
+ }),
213
+ ]));
214
+ });
215
+ test('uses graph-conditioned order only as a tie-break inside the same role and score', () => {
216
+ const pack = (0, ragContextPack_1.buildRagContextPack)({
217
+ query: 'what is water glass?',
218
+ fragments: [
219
+ makeFragment({ fragmentId: 'graph-unmatched', role: 'graph_neighbor_support', score: 0.6 }),
220
+ makeFragment({ fragmentId: 'graph-matched', role: 'graph_neighbor_support', score: 0.6 }),
221
+ makeFragment({ fragmentId: 'direct', role: 'direct_support', score: 0.4 }),
222
+ ],
223
+ fragmentOrder: ['graph-matched', 'graph-unmatched', 'direct'],
224
+ sourceDecisions: [],
225
+ budget: {
226
+ maxFragments: 3,
227
+ maxCharsPerFragment: 200,
228
+ maxTotalChars: 600,
229
+ },
230
+ });
231
+ expect(pack.fragments.map((fragment) => fragment.fragmentId)).toEqual([
232
+ 'direct',
233
+ 'graph-matched',
234
+ 'graph-unmatched',
235
+ ]);
236
+ });
237
+ test('unbounded product mode ignores product caps while retaining explicit runtime limits', () => {
238
+ const pack = (0, ragContextPack_1.buildRagContextPack)({
239
+ query: 'what is water glass?',
240
+ fragments: Array.from({ length: 4 }, (_, index) => makeFragment({
241
+ fragmentId: `unbounded_${index}`,
242
+ role: 'parent_context',
243
+ text: `Section ${index}: ${'water glass evidence '.repeat(12)}`,
244
+ sourceBoundary: 'full_document',
245
+ })),
246
+ sourceDecisions: [],
247
+ budget: {
248
+ maxFragments: 1,
249
+ maxCharsPerFragment: 100,
250
+ maxTotalChars: 120,
251
+ productCapDisabled: true,
252
+ runtimeMaxFragments: 8,
253
+ runtimeMaxCharsPerFragment: 900,
254
+ runtimeMaxTotalChars: 10000,
255
+ },
256
+ });
257
+ expect(pack.fragments).toHaveLength(4);
258
+ expect(pack.budget.productCapDisabled).toBe(true);
259
+ expect(pack.sourceDecisions).not.toEqual(expect.arrayContaining([
260
+ expect.objectContaining({ reason: 'max_fragments_exceeded' }),
261
+ ]));
262
+ });
263
+ test('preserves the configured max tier instead of applying the legacy 200-fragment/100k cap', () => {
264
+ const pack = (0, ragContextPack_1.buildRagContextPack)({
265
+ query: 'max budget probe',
266
+ fragments: Array.from({ length: 260 }, (_, index) => makeFragment({
267
+ fragmentId: `max_tier_${index}`,
268
+ role: 'direct_support',
269
+ text: `Max tier evidence ${index}.`,
270
+ sourceBoundary: 'full_document',
271
+ })),
272
+ sourceDecisions: [],
273
+ budget: {
274
+ maxFragments: 256,
275
+ maxCharsPerFragment: 16000,
276
+ maxTotalChars: 256000,
277
+ },
278
+ });
279
+ expect(pack.budget.maxFragments).toBe(256);
280
+ expect(pack.budget.maxTotalChars).toBe(256000);
281
+ expect(pack.fragments).toHaveLength(256);
282
+ });
283
+ test('unbounded mode reports a runtime fragment stop instead of silently dropping content', () => {
284
+ const pack = (0, ragContextPack_1.buildRagContextPack)({
285
+ query: 'what is water glass?',
286
+ fragments: Array.from({ length: 4 }, (_, index) => makeFragment({
287
+ fragmentId: `runtime_limited_${index}`,
288
+ role: 'parent_context',
289
+ text: `Runtime section ${index}: water glass evidence.`,
290
+ sourceBoundary: 'full_document',
291
+ })),
292
+ sourceDecisions: [],
293
+ budget: {
294
+ maxFragments: 1,
295
+ maxCharsPerFragment: 100,
296
+ maxTotalChars: 120,
297
+ productCapDisabled: true,
298
+ runtimeMaxFragments: 2,
299
+ runtimeMaxCharsPerFragment: 900,
300
+ runtimeMaxTotalChars: 10000,
301
+ },
302
+ });
303
+ expect(pack.fragments).toHaveLength(2);
304
+ expect(pack.sourceDecisions).toEqual(expect.arrayContaining([
305
+ expect.objectContaining({
306
+ status: 'fragment_dropped',
307
+ reason: 'runtime_fragment_limit_exceeded',
308
+ }),
309
+ ]));
310
+ });
311
+ });