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
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const graphConditionedContext_1 = require("./graphConditionedContext");
4
+ function makeFragment(overrides = {}) {
5
+ return {
6
+ fragmentId: 'fragment-default',
7
+ role: 'background',
8
+ text: 'evidence',
9
+ documentId: 'document-1',
10
+ sourcePath: 'notes/topic.md',
11
+ headingPath: [],
12
+ charCount: 8,
13
+ tokenEstimate: 2,
14
+ truncated: false,
15
+ citationIds: [],
16
+ relationEdgeIds: [],
17
+ score: 0.5,
18
+ sourceBoundary: 'direct_span_only',
19
+ ...overrides,
20
+ };
21
+ }
22
+ function makePlan() {
23
+ return {
24
+ intent: 'causal',
25
+ depth: 'standard',
26
+ anchorAtomId: 'atom-anchor',
27
+ leadClaimId: 'claim-required',
28
+ claims: [{
29
+ claimId: 'claim-required',
30
+ role: 'mechanism',
31
+ required: true,
32
+ priority: 100,
33
+ statement: 'The planned mechanism is supported by the graph.',
34
+ subjectAtomId: 'atom-anchor',
35
+ supportingAtomIds: ['atom-support'],
36
+ supportingEdgeIds: ['edge-support'],
37
+ evidenceRefs: [{
38
+ evidenceId: 'evidence-1',
39
+ atomId: 'atom-support',
40
+ sourcePath: 'notes/topic.md',
41
+ citationIds: ['citation-1'],
42
+ text: 'evidence',
43
+ }],
44
+ confidence: 0.95,
45
+ }],
46
+ requiredRoles: ['mechanism'],
47
+ omittedCandidates: [],
48
+ };
49
+ }
50
+ describe('conditionRagFragmentsByGraphPlan', () => {
51
+ test('prefers fragments matched by required graph atoms and relation edges', () => {
52
+ const fragments = [
53
+ makeFragment({
54
+ fragmentId: 'neighbor-unmatched',
55
+ role: 'graph_neighbor_support',
56
+ atomId: 'atom-other',
57
+ relationEdgeIds: ['edge-other'],
58
+ }),
59
+ makeFragment({
60
+ fragmentId: 'neighbor-matched',
61
+ role: 'graph_neighbor_support',
62
+ atomId: 'atom-support',
63
+ relationEdgeIds: ['edge-support'],
64
+ }),
65
+ makeFragment({
66
+ fragmentId: 'anchor-direct',
67
+ role: 'direct_support',
68
+ atomId: 'atom-anchor',
69
+ }),
70
+ ];
71
+ const result = (0, graphConditionedContext_1.conditionRagFragmentsByGraphPlan)({
72
+ fragments,
73
+ graphAnswerPlan: makePlan(),
74
+ });
75
+ expect(result.fragmentOrder.indexOf('neighbor-matched'))
76
+ .toBeLessThan(result.fragmentOrder.indexOf('neighbor-unmatched'));
77
+ expect(result.trace.strategy).toBe('graph_answer_plan');
78
+ expect(result.trace.matchedClaimCount).toBe(1);
79
+ expect(result.trace.matchedFragmentCount).toBe(2);
80
+ expect(result.trace.selectedAtomIds).toEqual(['atom-anchor', 'atom-support']);
81
+ expect(result.trace.selectedEdgeIds).toEqual(['edge-support']);
82
+ });
83
+ test('keeps input order and exposes an explicit fallback when no plan exists', () => {
84
+ const fragments = [
85
+ makeFragment({ fragmentId: 'second' }),
86
+ makeFragment({ fragmentId: 'first' }),
87
+ ];
88
+ const result = (0, graphConditionedContext_1.conditionRagFragmentsByGraphPlan)({ fragments });
89
+ expect(result.fragmentOrder).toEqual(['second', 'first']);
90
+ expect(result.trace).toEqual({
91
+ strategy: 'none',
92
+ matchedClaimCount: 0,
93
+ matchedFragmentCount: 0,
94
+ selectedAtomIds: [],
95
+ selectedEdgeIds: [],
96
+ fallbackReason: 'no_graph_answer_plan',
97
+ });
98
+ });
99
+ });
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.assembleAgentConversationGraphContext = assembleAgentConversationGraphContext;
4
4
  exports.buildAgentConversationGraphContextFromKnowledgePoints = buildAgentConversationGraphContextFromKnowledgePoints;
5
5
  const store_1 = require("./store");
6
+ const answerTaskPlan_1 = require("./answerTaskPlan");
6
7
  function normalizeGraphComparableTitle(value) {
7
8
  return normalizeWhitespace(String(value || '').toLowerCase());
8
9
  }
@@ -105,21 +106,10 @@ function classifyConversationIntent(message) {
105
106
  if (!normalized) {
106
107
  return 'generic';
107
108
  }
108
- if (normalized.includes('compare')
109
- || normalized.includes('difference')
110
- || normalized.includes('vs')
111
- || normalized.includes('区分')
112
- || normalized.includes('对比')) {
109
+ if ((0, answerTaskPlan_1.isComparisonRequest)(normalized)) {
113
110
  return 'compare';
114
111
  }
115
- if (normalized.includes('how to')
116
- || normalized.includes('how do')
117
- || normalized.includes('steps')
118
- || normalized.includes('plan')
119
- || normalized.includes('如何')
120
- || normalized.includes('怎么')
121
- || normalized.includes('步骤')
122
- || normalized.includes('方案')) {
112
+ if ((0, answerTaskPlan_1.isProcedureRequest)(normalized)) {
123
113
  return 'how_to';
124
114
  }
125
115
  if (normalized.includes('what is')
@@ -722,7 +722,7 @@ describe('assembleAgentConversationGraphContext', () => {
722
722
  outDegree: 2,
723
723
  }));
724
724
  });
725
- test('prioritizes contrast successors over procedural sequence nodes for compare intent', async () => {
725
+ test.each(['compare water glass and plastic cup', '比较 water glass 和 plastic cup'])('prioritizes contrast successors over procedural sequence nodes: %s', async (message) => {
726
726
  const atoms = [
727
727
  createAtom({
728
728
  id: 'atom_water_glass',
@@ -779,7 +779,7 @@ describe('assembleAgentConversationGraphContext', () => {
779
779
  }),
780
780
  ];
781
781
  const result = await (0, graphContextAssembler_1.assembleAgentConversationGraphContext)({
782
- message: 'compare water glass and plastic cup',
782
+ message,
783
783
  usedScope: globalScope,
784
784
  knowledgePoints,
785
785
  store: new InMemoryOpsStore(atoms, edges),
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveGraphExpansionPolicy = resolveGraphExpansionPolicy;
4
+ const EXPLICIT_DEPTH_PATTERN = /\b(?:deep|detailed|comprehensive|thorough|in-depth|in depth|in detail|fully|complete|research)\b|深入|深度|详细|详尽|完整|充分|展开|研究/u;
5
+ function resolveGraphExpansionPolicy(message) {
6
+ const explicitDepthRequested = EXPLICIT_DEPTH_PATTERN.test(String(message || '').toLowerCase());
7
+ return explicitDepthRequested
8
+ ? {
9
+ enabled: true,
10
+ reason: 'explicit_depth_request',
11
+ maxSteps: 1,
12
+ maxNeighbors: 8,
13
+ maxPathDepth: 8,
14
+ }
15
+ : {
16
+ enabled: false,
17
+ reason: 'ordinary_query',
18
+ maxSteps: 0,
19
+ maxNeighbors: 6,
20
+ maxPathDepth: 6,
21
+ };
22
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const graphExpansionPolicy_1 = require("./graphExpansionPolicy");
4
+ describe('resolveGraphExpansionPolicy', () => {
5
+ test.each([
6
+ 'what is water glass',
7
+ 'explain water glass',
8
+ '为什么杯壁会传热',
9
+ ])('keeps ordinary questions on deterministic bounded retrieval: %s', (message) => {
10
+ expect((0, graphExpansionPolicy_1.resolveGraphExpansionPolicy)(message)).toEqual({
11
+ enabled: false,
12
+ reason: 'ordinary_query',
13
+ maxSteps: 0,
14
+ maxNeighbors: 6,
15
+ maxPathDepth: 6,
16
+ });
17
+ });
18
+ test.each([
19
+ 'explain water glass in detail',
20
+ 'perform a deep analysis of water glass',
21
+ 'research the complete thermal mechanism',
22
+ '详细解释水杯的热交换机制',
23
+ '深入研究杯壁与环境的关系',
24
+ ])('enables one replayable bounded expansion for explicit depth: %s', (message) => {
25
+ expect((0, graphExpansionPolicy_1.resolveGraphExpansionPolicy)(message)).toEqual({
26
+ enabled: true,
27
+ reason: 'explicit_depth_request',
28
+ maxSteps: 1,
29
+ maxNeighbors: 8,
30
+ maxPathDepth: 8,
31
+ });
32
+ });
33
+ });
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const KnowledgeLearningPlatform_1 = require("./KnowledgeLearningPlatform");
4
+ describe('knowledge document identity metadata', () => {
5
+ test('deletes a document through its portable source URI after ingest', async () => {
6
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform(() => new Date('2026-08-17T00:00:00.000Z'));
7
+ await platform.ingestKnowledge({
8
+ incremental: true,
9
+ documents: [{
10
+ documentId: 'legacy_doc',
11
+ sourcePath: 'Knowledge_Base/notes/identity.md',
12
+ sourceUri: 'note://workspace/v1/notes/identity.md',
13
+ revision: 'sha256:identity-revision',
14
+ identityAliases: ['identity', 'notes/identity.md'],
15
+ content: '# Identity\nPortable identity metadata stays additive.',
16
+ }],
17
+ });
18
+ const deleted = await platform.ingestKnowledge({
19
+ incremental: true,
20
+ deletedDocuments: [{ sourceUri: 'note://workspace/v1/notes/identity.md' }],
21
+ });
22
+ expect(deleted.summary.deletedDocuments).toBe(1);
23
+ expect(deleted.staleness[0]?.documentId).toBe('legacy_doc');
24
+ expect(deleted.staleness[0]?.status).toBe('deleted');
25
+ });
26
+ });
@@ -23,6 +23,7 @@ __exportStar(require("./queryBackend"), exports);
23
23
  __exportStar(require("./vectorAccelerationAdapter"), exports);
24
24
  __exportStar(require("./runtimeCapability"), exports);
25
25
  __exportStar(require("./conversationComposer"), exports);
26
+ __exportStar(require("./agentConversationSerialization"), exports);
26
27
  __exportStar(require("./ragSufficiencyProviderJudge"), exports);
27
28
  __exportStar(require("./errors"), exports);
28
29
  __exportStar(require("./domains"), exports);
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MOBILE_ANSWER_PROJECTION_SCHEMA_VERSION = void 0;
4
+ exports.projectAnswerForMobile = projectAnswerForMobile;
5
+ exports.MOBILE_ANSWER_PROJECTION_SCHEMA_VERSION = 1;
6
+ const MAX_MOBILE_DIRECT_ANSWER_CHARS = 2400;
7
+ const MAX_MOBILE_ROUTE_NODES = 8;
8
+ const MAX_MOBILE_CITATIONS = 4;
9
+ const MAX_MOBILE_TITLE_CHARS = 160;
10
+ const MAX_MOBILE_SOURCE_PATH_CHARS = 320;
11
+ function normalize(value) {
12
+ return String(value || '').replace(/\s+/gu, ' ').trim();
13
+ }
14
+ function normalizeMarkdown(value) {
15
+ return String(value || '').replace(/\r\n?/gu, '\n').trim();
16
+ }
17
+ function trimText(value, limit) {
18
+ const normalized = normalize(value);
19
+ return normalized.length <= limit ? normalized : `${normalized.slice(0, Math.max(0, limit - 1)).trim()}…`;
20
+ }
21
+ function countDisplayMathDelimiters(value) {
22
+ return (value.match(/(?<!\\)\$\$/gu) || []).length;
23
+ }
24
+ function trimMarkdown(value, limit) {
25
+ const normalized = normalizeMarkdown(value);
26
+ if (normalized.length <= limit) {
27
+ return normalized;
28
+ }
29
+ const suffix = '…';
30
+ let candidate = normalized.slice(0, Math.max(0, limit - suffix.length)).trimEnd();
31
+ // Do not publish an answer with an unmatched display-math fence. If a
32
+ // block crosses the mobile byte/character budget, omit that incomplete
33
+ // block rather than asking every client to repair malformed Markdown.
34
+ if (countDisplayMathDelimiters(candidate) % 2 !== 0) {
35
+ const openingIndex = candidate.lastIndexOf('$$');
36
+ candidate = openingIndex >= 0 ? candidate.slice(0, openingIndex).trimEnd() : '';
37
+ }
38
+ return `${candidate}${suffix}`.trim();
39
+ }
40
+ function stripRouteFromAnswer(answer) {
41
+ const normalized = normalizeMarkdown(answer);
42
+ const headingIndex = normalized.search(/(?:^|\n)#{1,6}\s+(?:建议学习路径|推荐学习路径|Suggested learning path)\s*$/imu);
43
+ const withoutHeadingRoute = headingIndex >= 0
44
+ ? normalized.slice(0, headingIndex).trimEnd()
45
+ : normalized;
46
+ return withoutHeadingRoute
47
+ .replace(/(?:建议学习路径|推荐学习路径|Suggested learning path)\s*[::][^\n]*(?:\n[^\n]*)*$/iu, '')
48
+ .replace(/([。.!?])\1+/gu, '$1')
49
+ .trim();
50
+ }
51
+ function resolveTaskPlan(response) {
52
+ return response.answerTaskPlan
53
+ || response.graphAnswerPlan?.answerTaskPlan
54
+ || response.trace?.answerTaskPlan
55
+ || response.knowledgeRun?.answerTaskPlan;
56
+ }
57
+ function projectCitation(citation) {
58
+ const citationId = normalize(citation.citationId);
59
+ const title = trimText(citation.title, MAX_MOBILE_TITLE_CHARS);
60
+ const sourcePath = trimText(citation.sourcePath, MAX_MOBILE_SOURCE_PATH_CHARS);
61
+ if (!citationId || !title || !sourcePath) {
62
+ return null;
63
+ }
64
+ return {
65
+ citationId,
66
+ title,
67
+ sourcePath,
68
+ ...(Number.isFinite(Number(citation.startLine)) ? { startLine: Number(citation.startLine) } : {}),
69
+ ...(Number.isFinite(Number(citation.endLine)) ? { endLine: Number(citation.endLine) } : {}),
70
+ };
71
+ }
72
+ function projectAnswerForMobile(response) {
73
+ const taskPlan = resolveTaskPlan(response);
74
+ const structuredAnswerBlock = (response.assistantBlocks || [])
75
+ .find((block) => block && block.type === 'structured_answer');
76
+ const directAnswer = trimMarkdown(stripRouteFromAnswer(String(response.answer
77
+ || response.assistantMessage
78
+ || (structuredAnswerBlock && 'directAnswer' in structuredAnswerBlock
79
+ ? structuredAnswerBlock.directAnswer
80
+ : '')
81
+ || '')), MAX_MOBILE_DIRECT_ANSWER_CHARS);
82
+ const route = (taskPlan?.learningRoute || [])
83
+ .slice()
84
+ .sort((left, right) => left.order - right.order)
85
+ .slice(0, MAX_MOBILE_ROUTE_NODES)
86
+ .map((node) => ({
87
+ nodeId: trimText(node.nodeId, MAX_MOBILE_TITLE_CHARS),
88
+ title: trimText(node.title, MAX_MOBILE_TITLE_CHARS),
89
+ role: node.role,
90
+ order: Math.max(1, Math.floor(Number(node.order) || 1)),
91
+ }))
92
+ .filter((node) => Boolean(node.nodeId && node.title));
93
+ const citations = (response.citations || [])
94
+ .slice(0, MAX_MOBILE_CITATIONS)
95
+ .map(projectCitation)
96
+ .filter((citation) => Boolean(citation));
97
+ return {
98
+ schemaVersion: exports.MOBILE_ANSWER_PROJECTION_SCHEMA_VERSION,
99
+ primarySubject: trimText(taskPlan?.primarySubject || '', MAX_MOBILE_TITLE_CHARS),
100
+ directAnswer,
101
+ route,
102
+ citations,
103
+ };
104
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mobileAnswerProjection_1 = require("./mobileAnswerProjection");
4
+ describe('mobile answer projection', () => {
5
+ test('keeps a bounded task result and drops internal RAG payloads', () => {
6
+ const response = {
7
+ answer: '非晶冰是水的一种缺乏长程有序的固态形式。建议学习路径:1. 水分子与氢键网络 -> 2. 非晶冰 -> 3. 径向分布函数 g(r)。',
8
+ assistantMessage: 'same',
9
+ answerTaskPlan: {
10
+ schemaVersion: '1',
11
+ primarySubject: '非晶冰',
12
+ requestedDepth: 'standard',
13
+ subtasks: [
14
+ { subtaskId: 'definition', kind: 'definition', subject: '非晶冰', required: true, expectedOutput: 'direct_answer' },
15
+ { subtaskId: 'learning_route', kind: 'learning_route', subject: '非晶冰', required: true, expectedOutput: 'ordered_nodes' },
16
+ ],
17
+ learningRoute: [
18
+ { nodeId: 'water', title: '水分子与氢键网络', role: 'prerequisite', order: 1, orderingBasis: 'source_order', evidenceRefs: ['e1'], reason: '基础' },
19
+ { nodeId: 'ice', title: '非晶冰', role: 'core', order: 2, orderingBasis: 'source_order', evidenceRefs: ['e2'], reason: '核心' },
20
+ { nodeId: 'rdf', title: '径向分布函数 g(r)', role: 'mechanism', order: 3, orderingBasis: 'source_order', evidenceRefs: ['e3'], reason: '机制' },
21
+ ],
22
+ },
23
+ citations: [
24
+ {
25
+ citationId: 'e1', atomId: 'water', documentId: 'doc', sourcePath: 'Knowledge_Base/waterglass/water.md', title: '水分子', snippet: 'full source text should not be copied', startLine: 2, endLine: 3, score: 0.9,
26
+ },
27
+ ],
28
+ trace: {
29
+ ragContextPack: { fragments: [{ text: 'large internal source body' }] },
30
+ },
31
+ };
32
+ const projection = (0, mobileAnswerProjection_1.projectAnswerForMobile)(response);
33
+ expect(projection).toEqual(expect.objectContaining({
34
+ schemaVersion: 1,
35
+ primarySubject: '非晶冰',
36
+ directAnswer: '非晶冰是水的一种缺乏长程有序的固态形式。',
37
+ route: [
38
+ expect.objectContaining({ title: '水分子与氢键网络', role: 'prerequisite', order: 1 }),
39
+ expect.objectContaining({ title: '非晶冰', role: 'core', order: 2 }),
40
+ expect.objectContaining({ title: '径向分布函数 g(r)', role: 'mechanism', order: 3 }),
41
+ ],
42
+ citations: [expect.objectContaining({ title: '水分子', sourcePath: 'Knowledge_Base/waterglass/water.md' })],
43
+ }));
44
+ expect(JSON.stringify(projection)).not.toContain('large internal source body');
45
+ expect(JSON.stringify(projection)).not.toContain('full source text should not be copied');
46
+ });
47
+ test('preserves display-math Markdown while removing the appended learning route', () => {
48
+ const response = {
49
+ answer: [
50
+ '非晶冰是缺乏长程有序的水的固态形式。',
51
+ '',
52
+ '$$',
53
+ 'g(r) = \\frac{1}{4\\pi r^2\\rho} \\frac{dN}{dr}',
54
+ '$$',
55
+ '',
56
+ '### 建议学习路径',
57
+ '',
58
+ '1. **水分子**(前置)',
59
+ ].join('\n'),
60
+ assistantMessage: 'fallback',
61
+ citations: [],
62
+ answerTaskPlan: {
63
+ schemaVersion: '1',
64
+ primarySubject: '非晶冰',
65
+ requestedDepth: 'standard',
66
+ subtasks: [
67
+ { subtaskId: 'definition', kind: 'definition', subject: '非晶冰', required: true, expectedOutput: 'direct_answer' },
68
+ { subtaskId: 'learning_route', kind: 'learning_route', subject: '非晶冰', required: true, expectedOutput: 'ordered_nodes' },
69
+ ],
70
+ learningRoute: [],
71
+ },
72
+ };
73
+ const projection = (0, mobileAnswerProjection_1.projectAnswerForMobile)(response);
74
+ expect(projection.directAnswer).toContain('$$\ng(r) = \\frac{1}{4\\pi r^2\\rho} \\frac{dN}{dr}\n$$');
75
+ expect(projection.directAnswer).not.toContain('建议学习路径');
76
+ expect((projection.directAnswer.match(/(?<!\\)\$\$/gu) || []).length % 2).toBe(0);
77
+ });
78
+ test('does not leave an unmatched display-math fence when the mobile budget truncates an answer', () => {
79
+ const longPrefix = '说明。'.repeat(500);
80
+ const response = {
81
+ answer: `${longPrefix}\n\n$$\nE = mc^2\n$$`,
82
+ assistantMessage: '',
83
+ citations: [],
84
+ };
85
+ const projection = (0, mobileAnswerProjection_1.projectAnswerForMobile)(response);
86
+ expect(projection.directAnswer.length).toBeLessThanOrEqual(2400);
87
+ expect((projection.directAnswer.match(/(?<!\\)\$\$/gu) || []).length % 2).toBe(0);
88
+ });
89
+ test('uses the response-level answer as the canonical release over a stale structured block', () => {
90
+ const response = {
91
+ answer: 'Canonical released answer for the subject.',
92
+ assistantMessage: 'Canonical released answer for the subject.',
93
+ assistantBlocks: [{
94
+ blockId: 'stale',
95
+ type: 'structured_answer',
96
+ directAnswer: 'Stale block answer that must not win.',
97
+ }],
98
+ citations: [],
99
+ };
100
+ expect((0, mobileAnswerProjection_1.projectAnswerForMobile)(response).directAnswer).toBe('Canonical released answer for the subject.');
101
+ });
102
+ });
@@ -42,6 +42,7 @@ exports.listGraphQueryBackendCatalog = listGraphQueryBackendCatalog;
42
42
  const crypto_1 = require("crypto");
43
43
  const fs = __importStar(require("fs"));
44
44
  const path = __importStar(require("path"));
45
+ const graphClaimMatcher_1 = require("./graphClaimMatcher");
45
46
  function evaluateAnnRunbookHealth(params) {
46
47
  const checks = [];
47
48
  const threshold = params.failureThreshold ?? 3;
@@ -295,7 +296,9 @@ function buildAtomSemanticTokens(atom) {
295
296
  .slice(0, SEMANTIC_KEYWORD_TOKEN_LIMIT));
296
297
  const titleTokens = extractSemanticTokens(atom.title, SEMANTIC_TITLE_TOKEN_LIMIT);
297
298
  const contentTokens = extractSemanticTokens(atom.content, SEMANTIC_CONTENT_TOKEN_LIMIT);
298
- return uniqueTokens([...keywordTokens, ...titleTokens, ...contentTokens]);
299
+ const conceptTokens = (0, graphClaimMatcher_1.semanticFeatures)(`${atom.title} ${atom.content}`)
300
+ .filter((feature) => feature.startsWith('concept:'));
301
+ return uniqueTokens([...keywordTokens, ...titleTokens, ...contentTokens, ...conceptTokens]);
299
302
  }
300
303
  function buildQuerySemanticTokens(context) {
301
304
  const directTokens = uniqueTokens((context.queryTokens || [])
@@ -303,7 +306,9 @@ function buildQuerySemanticTokens(context) {
303
306
  .filter((token) => token.length > 0));
304
307
  const variantTokens = buildQueryTextVariants(context).flatMap((variant) => (extractSemanticTokens(variant, SEMANTIC_QUERY_TOKEN_LIMIT)));
305
308
  const fallbackTokens = extractSemanticTokens(context.query, SEMANTIC_QUERY_TOKEN_LIMIT);
306
- return uniqueTokens([...directTokens, ...variantTokens, ...fallbackTokens]).slice(0, SEMANTIC_QUERY_TOKEN_LIMIT);
309
+ const conceptTokens = (0, graphClaimMatcher_1.semanticFeatures)(buildQueryTextVariants(context).join(' '))
310
+ .filter((feature) => feature.startsWith('concept:'));
311
+ return uniqueTokens([...directTokens, ...variantTokens, ...fallbackTokens, ...conceptTokens]).slice(0, SEMANTIC_QUERY_TOKEN_LIMIT);
307
312
  }
308
313
  function computeJaccard(left, right) {
309
314
  if (!left.length || !right.length) {
@@ -986,8 +991,10 @@ const DEFAULT_LOCAL_VECTOR_ACCELERATION_ADAPTER = {
986
991
  async function selectLocalVectorAnnCandidates(params) {
987
992
  const adapterId = String(params.accelerationAdapter?.id || '').trim() || LOCAL_VECTOR_ACCELERATION_ADAPTER_ID;
988
993
  try {
994
+ params.signal?.throwIfAborted();
989
995
  if (typeof params.accelerationAdapter.syncIndex === 'function') {
990
996
  await params.accelerationAdapter.syncIndex({
997
+ signal: params.signal,
991
998
  atomCount: params.index.atomCount,
992
999
  tokenToAtomIds: params.index.tokenToAtomIds,
993
1000
  signatureBuckets: params.index.signatureBuckets,
@@ -997,7 +1004,9 @@ async function selectLocalVectorAnnCandidates(params) {
997
1004
  indexSignature: params.index.signature,
998
1005
  });
999
1006
  }
1007
+ params.signal?.throwIfAborted();
1000
1008
  const rawSelection = await params.accelerationAdapter.selectCandidates({
1009
+ signal: params.signal,
1001
1010
  atomCount: params.index.atomCount,
1002
1011
  queryTokens: [...params.queryTokens],
1003
1012
  queryWeights: params.queryWeights,
@@ -1010,6 +1019,7 @@ async function selectLocalVectorAnnCandidates(params) {
1010
1019
  embeddingDimension: params.index.documentFrequency.size,
1011
1020
  indexSignature: params.index.signature,
1012
1021
  });
1022
+ params.signal?.throwIfAborted();
1013
1023
  const mode = (rawSelection?.mode === 'token_prefilter'
1014
1024
  || rawSelection?.mode === 'token_signature_prefilter'
1015
1025
  || rawSelection?.mode === 'full_scan') ? rawSelection.mode : 'full_scan';
@@ -1029,6 +1039,7 @@ async function selectLocalVectorAnnCandidates(params) {
1029
1039
  };
1030
1040
  }
1031
1041
  catch (error) {
1042
+ params.signal?.throwIfAborted();
1032
1043
  const normalizedError = String(error?.message || error || 'unknown_error')
1033
1044
  .replace(/\s+/g, ' ')
1034
1045
  .trim()
@@ -1054,7 +1065,11 @@ class LocalHybridGraphQueryBackend {
1054
1065
  const querySemanticTokens = buildQuerySemanticTokens(context);
1055
1066
  const graphFeatureIndex = buildGraphFeatureIndex(context, querySemanticTokens);
1056
1067
  const candidates = [];
1057
- context.atoms.forEach((atom) => {
1068
+ for (const [candidateIndex, atom] of context.atoms.entries()) {
1069
+ if (context.signal && candidateIndex % 128 === 0) {
1070
+ await new Promise(resolve => setImmediate(resolve));
1071
+ context.signal.throwIfAborted();
1072
+ }
1058
1073
  const titleLower = atom.title.toLowerCase();
1059
1074
  const contentLower = atom.content.toLowerCase();
1060
1075
  const keywordMatches = context.queryTokens.filter((token) => atom.keywords.includes(token)).length;
@@ -1071,7 +1086,7 @@ class LocalHybridGraphQueryBackend {
1071
1086
  score: Number(clamp(score, 0, 100).toFixed(4)),
1072
1087
  });
1073
1088
  }
1074
- });
1089
+ }
1075
1090
  candidates.sort((left, right) => right.score - left.score);
1076
1091
  const maxCandidates = Math.max(context.topK, Math.min(context.atoms.length, context.topK * 4));
1077
1092
  return {
@@ -1102,7 +1117,11 @@ class KeywordOnlyGraphQueryBackend {
1102
1117
  async query(context) {
1103
1118
  const queryVariants = buildQueryTextVariants(context);
1104
1119
  const candidates = [];
1105
- context.atoms.forEach((atom) => {
1120
+ for (const [candidateIndex, atom] of context.atoms.entries()) {
1121
+ if (context.signal && candidateIndex % 128 === 0) {
1122
+ await new Promise(resolve => setImmediate(resolve));
1123
+ context.signal.throwIfAborted();
1124
+ }
1106
1125
  const titleLower = atom.title.toLowerCase();
1107
1126
  const contentLower = atom.content.toLowerCase();
1108
1127
  const keywordMatches = context.queryTokens.filter((token) => atom.keywords.includes(token)).length;
@@ -1115,7 +1134,7 @@ class KeywordOnlyGraphQueryBackend {
1115
1134
  score: Number(clamp(score, 0, 100).toFixed(4)),
1116
1135
  });
1117
1136
  }
1118
- });
1137
+ }
1119
1138
  candidates.sort((left, right) => right.score - left.score);
1120
1139
  const maxCandidates = Math.max(context.topK, Math.min(context.atoms.length, context.topK * 4));
1121
1140
  return {
@@ -1190,6 +1209,7 @@ class LocalVectorGraphQueryBackend {
1190
1209
  let annCandidateSelection;
1191
1210
  try {
1192
1211
  annCandidateSelection = await selectLocalVectorAnnCandidates({
1212
+ signal: context.signal,
1193
1213
  index,
1194
1214
  queryTokens: querySemanticTokens,
1195
1215
  queryWeights,
@@ -1265,10 +1285,14 @@ class LocalVectorGraphQueryBackend {
1265
1285
  representationStrictMode: this.localVectorAccelerationRepresentationStrict,
1266
1286
  };
1267
1287
  const candidates = [];
1268
- atomIdsToScore.forEach((atomId) => {
1288
+ for (const [candidateIndex, atomId] of atomIdsToScore.entries()) {
1289
+ if (context.signal && candidateIndex % 128 === 0) {
1290
+ await new Promise(resolve => setImmediate(resolve));
1291
+ context.signal.throwIfAborted();
1292
+ }
1269
1293
  const entry = index.entriesByAtomId.get(atomId);
1270
1294
  if (!entry) {
1271
- return;
1295
+ continue;
1272
1296
  }
1273
1297
  const cosineSimilarity = computeCosineSimilarity(queryWeights, entry.weights);
1274
1298
  const semanticOverlap = computeJaccard(querySemanticTokens, entry.tokens);
@@ -1278,13 +1302,13 @@ class LocalVectorGraphQueryBackend {
1278
1302
  : 0;
1279
1303
  const score = (cosineSimilarity * 4.2) + (semanticOverlap * 1.1) + graphFeatureBonus + signatureBonus;
1280
1304
  if (score <= 0) {
1281
- return;
1305
+ continue;
1282
1306
  }
1283
1307
  candidates.push({
1284
1308
  atomId,
1285
1309
  score: Number(clamp(score, 0, 100).toFixed(4)),
1286
1310
  });
1287
- });
1311
+ }
1288
1312
  candidates.sort((left, right) => right.score - left.score);
1289
1313
  const maxCandidates = Math.max(context.topK, Math.min(context.atoms.length, context.topK * 4));
1290
1314
  return {
@@ -135,6 +135,27 @@ describe('query backend factory', () => {
135
135
  expect(result.trace?.retrievalModes).toContain('semantic_similarity');
136
136
  expect(result.trace?.modeWeights?.semantic).toBeGreaterThan(0);
137
137
  });
138
+ test('local hybrid backend reuses multilingual graph concepts for comparison retrieval', async () => {
139
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_hybrid' });
140
+ const atoms = [
141
+ makeAtom('water_glass_definition', '水杯', '水杯是透明玻璃容器。', ['水杯', '玻璃'], 'zh'),
142
+ makeAtom('container_material_comparison', '材料比较', '塑料杯与玻璃水杯相比更轻、更有韧性,但刚度和透明度较低。', ['塑料杯', '玻璃水杯', '比较'], 'zh'),
143
+ ];
144
+ const result = await backend.query({
145
+ request: {
146
+ query: 'compare water glass and plastic cup',
147
+ topK: 2,
148
+ },
149
+ query: 'compare water glass and plastic cup',
150
+ queryTokens: ['compare', 'water', 'glass', 'plastic', 'cup'],
151
+ queryVariants: ['water glass', 'plastic cup'],
152
+ asOf: '2026-01-01T00:00:00.000Z',
153
+ topK: 2,
154
+ atoms,
155
+ activeEdges: [],
156
+ });
157
+ expect(result.candidates.map((candidate) => candidate.atomId)).toContain('container_material_comparison');
158
+ });
138
159
  test('local vector backend includes vector similarity mode', async () => {
139
160
  const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
140
161
  const atoms = [