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,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ragPublicText_1 = require("./ragPublicText");
4
+ const KnowledgeLearningPlatform_1 = require("./KnowledgeLearningPlatform");
5
+ describe('ragPublicText', () => {
6
+ test('retains numbered procedure evidence for a how-should request', () => {
7
+ expect((0, ragPublicText_1.shouldRejectCompareProcedureEvidenceClause)('Step 1: Validate the candidate.', 'How should the index be replaced?')).toBe(false);
8
+ expect((0, ragPublicText_1.shouldRejectCompareProcedureEvidenceClause)('Step 1: Validate the candidate.', 'Compare the two indexes.')).toBe(true);
9
+ });
10
+ test.each(['[[Checkpoint]]', '[[Checkpoint]] [[Mutation log]]', '1. [[Checkpoint]] 2. [[Mutation log]]'])('rejects link-only navigation as factual evidence: %s', text => {
11
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)(text)).toBe(true);
12
+ });
13
+ test('preserves factual prose that contains a wiki link', () => {
14
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('A [[Mutation log]] records an ordered sequence of changes.')).toBe(false);
15
+ });
16
+ test('comparison composition selects source facts instead of bare wiki links', async () => {
17
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({ autoPersist: false });
18
+ await platform.ingestKnowledge({ documents: [
19
+ { documentId: 'checkpoint', sourcePath: 'test/checkpoint.md', content: '# Checkpoint\nA checkpoint stores the complete state at a checkpoint. [[Mutation log]]' },
20
+ { documentId: 'mutation-log', sourcePath: 'test/mutation-log.md', content: '# Mutation Log\nA mutation log records an ordered sequence of state changes. [[Checkpoint]]' },
21
+ ] });
22
+ const response = await platform.agentConversation({ message: 'Compare Checkpoint and Mutation Log', persistMemory: false });
23
+ expect(response.answer).toMatch(/complete state|ordered sequence/i);
24
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)(response.answer)).toBe(false);
25
+ });
26
+ test('removes Markdown list and bold-label scaffolding before public composition', () => {
27
+ expect((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)('* **Quantitative analysis**: pressure follows P = rho g h.'))
28
+ .toBe('Quantitative analysis: pressure follows P = rho g h.');
29
+ expect((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)('* $T$ is the temperature field.'))
30
+ .toBe('$T$ is the temperature field.');
31
+ expect((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)('A value * B value remains mathematical.'))
32
+ .toBe('A value * B value remains mathematical.');
33
+ });
34
+ test('removes markdown headings and table scaffolding while preserving the factual lead', () => {
35
+ expect((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)('### Technical specifications The glass density is about 2500 kg/m³. | Parameter | Value | | :--- | :--- | | density | 2500 |')).toBe('Technical specifications The glass density is about 2500 kg/m³.');
36
+ });
37
+ test('rejects authoring instructions and evidence-control prose in Chinese and English', () => {
38
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('所有推理过程以英文进行,最终输出为简体中文。')).toBe(true);
39
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('Operators must resolve the active owner before publishing.')).toBe(true);
40
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('The glass wall conducts heat into the environment.')).toBe(false);
41
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('This clause is intentionally beyond the public definition budget and should remain internal.')).toBe(true);
42
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('The response budget limits serialized bytes.')).toBe(false);
43
+ });
44
+ test('removes fenced diagram payloads instead of exposing renderer source', () => {
45
+ expect((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)('Heat moves by conduction and convection. ```mermaid graph TD A --> B ```')).toBe('Heat moves by conduction and convection.');
46
+ });
47
+ test('rejects flattened table introductions after Markdown extraction', () => {
48
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('下表列出了标准温度和压力下的典型技术参数。参数 Parameter 密度 2500 999.8 kg/m³ 单位 Unit。')).toBe(true);
49
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('The following table lists typical parameters. Parameter Density Value Unit.')).toBe(true);
50
+ expect((0, ragPublicText_1.shouldRejectPublicEvidenceClause)('参数 (Parameter) 钠钙玻璃 水 单位 (Unit) :--- :--- 密度 2500 999.8 kg/m³。')).toBe(true);
51
+ });
52
+ test('removes flattened section labels while retaining their factual sentence', () => {
53
+ expect((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)('核心概念及其数学基础:水杯系统通过传导、对流和辐射与环境交换热量。')).toBe('水杯系统通过传导、对流和辐射与环境交换热量。');
54
+ expect((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)('Material science: Glass is an amorphous solid without long-range order.')).toBe('Glass is an amorphous solid without long-range order.');
55
+ expect((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)('核心概念及其数学基础 水杯系统通过传导、对流和辐射与环境交换热量。')).toBe('水杯系统通过传导、对流和辐射与环境交换热量。');
56
+ });
57
+ });
@@ -27,6 +27,10 @@ function hasGraphEvidence(fragments) {
27
27
  function hasConflictEvidence(fragments) {
28
28
  return fragments.some((fragment) => fragment.role === 'conflict' && textHasSubstance(fragment));
29
29
  }
30
+ function hasIncompleteSourceScan(pack) {
31
+ return pack.sourceDecisions.some(decision => decision.status === 'source_window_unavailable'
32
+ && String(decision.reason || '').startsWith('runtime_'));
33
+ }
30
34
  function hasUnavailableGraphNeighborSourceWindow(pack) {
31
35
  return pack.sourceDecisions.some((decision) => (decision.status === 'source_window_unavailable'
32
36
  && String(decision.reason || '').includes('graph_neighbor_support')));
@@ -61,6 +65,9 @@ function buildDeterministicReview(params) {
61
65
  if (budgetViolation) {
62
66
  reasons.push('context_budget_dropped_fragments');
63
67
  }
68
+ const incompleteSourceScan = hasIncompleteSourceScan(params.contextPack);
69
+ if (incompleteSourceScan)
70
+ reasons.push('source_scan_incomplete');
64
71
  if (!directSupport) {
65
72
  return {
66
73
  reviewedAt: String(params.reviewedAt || new Date().toISOString()),
@@ -91,6 +98,7 @@ function buildDeterministicReview(params) {
91
98
  && documentAugmentation
92
99
  && !conflictEvidence
93
100
  && !graphNeighborEvidenceMissing
101
+ && !incompleteSourceScan
94
102
  ? 'sufficient'
95
103
  : 'borderline';
96
104
  return {
@@ -137,11 +145,13 @@ async function reviewRagContextSufficiency(params) {
137
145
  const deterministicReview = buildDeterministicReview(params);
138
146
  if (deterministicReview.status !== 'borderline'
139
147
  || params.allowLlmJudge !== true
140
- || typeof params.llmJudge !== 'function') {
148
+ || typeof params.llmJudge !== 'function'
149
+ || hasIncompleteSourceScan(params.contextPack)) {
141
150
  return deterministicReview;
142
151
  }
143
152
  try {
144
153
  const judgeReview = await params.llmJudge({
154
+ signal: params.signal,
145
155
  query: params.query,
146
156
  contextPack: params.contextPack,
147
157
  graphContext: params.graphContext,
@@ -155,43 +155,30 @@ function buildReviewContent(input) {
155
155
  });
156
156
  }
157
157
  async function completeWithTimeout(params) {
158
+ params.request.signal?.throwIfAborted();
158
159
  const controller = new AbortController();
159
160
  let didTimeout = false;
160
- let timer = null;
161
- const timeoutPromise = new Promise((_resolve, reject) => {
162
- timer = setTimeout(() => {
163
- didTimeout = true;
164
- controller.abort();
165
- reject(new Error('RAG sufficiency judge timed out.'));
166
- }, params.timeoutMs);
167
- params.request.signal?.addEventListener('abort', () => {
168
- if (timer) {
169
- clearTimeout(timer);
170
- timer = null;
171
- }
172
- controller.abort();
173
- reject(new Error('RAG sufficiency judge cancelled.'));
174
- }, { once: true });
175
- });
161
+ const timer = setTimeout(() => {
162
+ didTimeout = true;
163
+ controller.abort(new Error('RAG sufficiency judge timed out.'));
164
+ }, params.timeoutMs);
165
+ const onParentAbort = () => controller.abort(params.request.signal?.reason);
166
+ params.request.signal?.addEventListener('abort', onParentAbort, { once: true });
176
167
  try {
177
- return await Promise.race([
178
- params.client.complete({
179
- ...params.request,
180
- signal: controller.signal,
181
- }),
182
- timeoutPromise,
183
- ]);
168
+ const completion = await params.client.complete({ ...params.request, signal: controller.signal });
169
+ controller.signal.throwIfAborted();
170
+ return completion;
184
171
  }
185
172
  catch (error) {
186
173
  if (didTimeout) {
187
174
  throw new Error('RAG sufficiency judge timed out.');
188
175
  }
176
+ params.request.signal?.throwIfAborted();
189
177
  throw error;
190
178
  }
191
179
  finally {
192
- if (timer) {
193
- clearTimeout(timer);
194
- }
180
+ clearTimeout(timer);
181
+ params.request.signal?.removeEventListener('abort', onParentAbort);
195
182
  }
196
183
  }
197
184
  function createRagSufficiencyProviderJudge(options) {
@@ -199,7 +186,9 @@ function createRagSufficiencyProviderJudge(options) {
199
186
  const timeoutMs = resolveBoundedInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS);
200
187
  const maxTokens = resolveBoundedInteger(options.maxTokens, DEFAULT_MAX_TOKENS, MIN_MAX_TOKENS, MAX_MAX_TOKENS);
201
188
  return async (input) => {
189
+ input.signal?.throwIfAborted();
202
190
  const settings = await options.settingsProvider();
191
+ input.signal?.throwIfAborted();
203
192
  const provider = (0, LlmProvider_1.selectProviderForTask)(settings, RAG_SUFFICIENCY_TASK_KEY);
204
193
  const model = (0, LlmProvider_1.selectModelForTask)(settings, RAG_SUFFICIENCY_TASK_KEY, provider);
205
194
  const configuredPrompt = settings.enableGlobalCustomPrompts
@@ -209,6 +198,7 @@ function createRagSufficiencyProviderJudge(options) {
209
198
  client,
210
199
  timeoutMs,
211
200
  request: {
201
+ signal: input.signal,
212
202
  provider,
213
203
  model,
214
204
  prompt: configuredPrompt || DEFAULT_REVIEW_PROMPT,
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const ragSufficiencyProviderJudge_1 = require("./ragSufficiencyProviderJudge");
4
+ const events_1 = require("events");
4
5
  function makeSettings() {
5
6
  return {
6
7
  providers: [
@@ -68,6 +69,56 @@ function makePack() {
68
69
  };
69
70
  }
70
71
  describe('createRagSufficiencyProviderJudge', () => {
72
+ test('turn cancellation reaches provider I/O', async () => {
73
+ const controller = new AbortController();
74
+ const reason = new Error('turn_cancelled');
75
+ let entered;
76
+ const started = new Promise(resolve => { entered = resolve; });
77
+ const complete = jest.fn(request => new Promise((_resolve, reject) => {
78
+ request.signal.addEventListener('abort', () => reject(request.signal.reason), { once: true });
79
+ entered();
80
+ }));
81
+ const judge = (0, ragSufficiencyProviderJudge_1.createRagSufficiencyProviderJudge)({ settingsProvider: makeSettings, llmClient: { complete } });
82
+ const pending = judge({ query: 'water glass', contextPack: makePack(), signal: controller.signal }).catch(error => error);
83
+ await started;
84
+ controller.abort(reason);
85
+ expect(await pending).toBe(reason);
86
+ expect(complete.mock.calls[0][0].signal.aborted).toBe(true);
87
+ });
88
+ test('does not return while a cancelled provider callback can still run', async () => {
89
+ const controller = new AbortController();
90
+ const reason = new Error('turn_cancelled');
91
+ let release;
92
+ let entered;
93
+ const started = new Promise(resolve => { entered = resolve; });
94
+ const held = new Promise(resolve => { release = resolve; });
95
+ const complete = jest.fn(async () => { entered(); await held; return { text: '{"status":"sufficient","score":1}', provider: 'OpenAI', model: 'gpt-test' }; });
96
+ const judge = (0, ragSufficiencyProviderJudge_1.createRagSufficiencyProviderJudge)({ settingsProvider: makeSettings, llmClient: { complete } });
97
+ let finished = false;
98
+ const pending = judge({ query: 'water glass', contextPack: makePack(), signal: controller.signal }).catch(error => error).finally(() => { finished = true; });
99
+ await started;
100
+ controller.abort(reason);
101
+ await new Promise(resolve => setImmediate(resolve));
102
+ const finishedBeforeProviderStopped = finished;
103
+ release();
104
+ expect(await pending).toBe(reason);
105
+ expect(finishedBeforeProviderStopped).toBe(false);
106
+ });
107
+ test('a pre-cancelled turn does not start a provider request', async () => {
108
+ const controller = new AbortController();
109
+ controller.abort(new Error('already_cancelled'));
110
+ const complete = jest.fn();
111
+ const judge = (0, ragSufficiencyProviderJudge_1.createRagSufficiencyProviderJudge)({ settingsProvider: makeSettings, llmClient: { complete } });
112
+ await expect(judge({ query: 'water glass', contextPack: makePack(), signal: controller.signal })).rejects.toThrow('already_cancelled');
113
+ expect(complete).not.toHaveBeenCalled();
114
+ });
115
+ test('completed provider work releases the parent cancellation listener', async () => {
116
+ const controller = new AbortController();
117
+ const complete = jest.fn().mockResolvedValue({ text: '{"status":"sufficient","score":1}', provider: 'OpenAI', model: 'gpt-test' });
118
+ const judge = (0, ragSufficiencyProviderJudge_1.createRagSufficiencyProviderJudge)({ settingsProvider: makeSettings, llmClient: { complete } });
119
+ await judge({ query: 'water glass', contextPack: makePack(), signal: controller.signal });
120
+ expect((0, events_1.getEventListeners)(controller.signal, 'abort')).toHaveLength(0);
121
+ });
71
122
  test('calls the configured NoteMD provider with bounded JSON-only review input', async () => {
72
123
  const complete = jest.fn().mockResolvedValue({
73
124
  text: '```json\n{"status":"sufficient","score":0.84,"reasons":["answerable_from_context"],"degradationState":"none"}\n```',
@@ -26,6 +26,92 @@ function parsePositiveIntegerValue(value) {
26
26
  }
27
27
  return Math.floor(numeric);
28
28
  }
29
+ function normalizeAnswerLanguageValue(value) {
30
+ const normalized = String(value || '')
31
+ .trim()
32
+ .toLowerCase()
33
+ .replace(/_/gu, '-');
34
+ if (!normalized) {
35
+ return undefined;
36
+ }
37
+ if (normalized === 'auto') {
38
+ return 'auto';
39
+ }
40
+ if (normalized === 'zh'
41
+ || normalized.startsWith('zh-')
42
+ || ['chinese', 'mandarin', 'simplified-chinese', 'traditional-chinese'].includes(normalized)) {
43
+ return 'zh';
44
+ }
45
+ if (normalized === 'en'
46
+ || normalized.startsWith('en-')
47
+ || normalized === 'english') {
48
+ return 'en';
49
+ }
50
+ return undefined;
51
+ }
52
+ function normalizeResponseProfileValue(value) {
53
+ const normalized = String(value || '').trim().toLowerCase().replace(/[-\s]+/gu, '_');
54
+ if (normalized === 'default') {
55
+ return 'default';
56
+ }
57
+ if (normalized === 'mobile_compact' || normalized === 'mobile') {
58
+ return 'mobile_compact';
59
+ }
60
+ return undefined;
61
+ }
62
+ function normalizeResponseModeValue(value) {
63
+ const normalized = String(value || '').trim().toLowerCase().replace(/[-\s]+/gu, '_');
64
+ if (normalized === 'slim' || normalized === 'definition' || normalized === 'compact' || normalized === 'default') {
65
+ return 'slim';
66
+ }
67
+ if (normalized === 'full' || normalized === 'comprehensive' || normalized === 'report') {
68
+ return 'full';
69
+ }
70
+ return undefined;
71
+ }
72
+ function normalizeResponseBudgetModeValue(value) {
73
+ const normalized = String(value || '').trim().toLowerCase().replace(/[-\s]+/gu, '_');
74
+ if (!normalized || ['adaptive', 'default', 'standard', 'auto'].includes(normalized)) {
75
+ return 'adaptive';
76
+ }
77
+ if (['unbounded', 'no_cap', 'nocap', 'no_limit', 'nolimit', 'unlimited'].includes(normalized)) {
78
+ return 'unbounded';
79
+ }
80
+ return 'adaptive';
81
+ }
82
+ function normalizeResponseBudgetCapabilityValue(value) {
83
+ if (!isObjectRecord(value)) {
84
+ return undefined;
85
+ }
86
+ const memoryClass = String(value.memoryClass || value.memory_class || '')
87
+ .trim()
88
+ .toLowerCase()
89
+ .replace(/[-\s]+/gu, '_');
90
+ const workload = String(value.workload || value.workload_class || '')
91
+ .trim()
92
+ .toLowerCase()
93
+ .replace(/[-\s]+/gu, '_');
94
+ const normalizePositiveHint = (hint) => {
95
+ const numeric = Number(hint);
96
+ return Number.isFinite(numeric) && numeric > 0 ? Math.floor(numeric) : undefined;
97
+ };
98
+ const normalized = {};
99
+ if (['low', 'standard', 'high'].includes(memoryClass)) {
100
+ normalized.memoryClass = memoryClass;
101
+ }
102
+ if (['normal', 'large', 'max'].includes(workload)) {
103
+ normalized.workload = workload;
104
+ }
105
+ const maxReportCharsHint = normalizePositiveHint(value.maxReportCharsHint ?? value.max_report_chars_hint);
106
+ if (maxReportCharsHint !== undefined) {
107
+ normalized.maxReportCharsHint = maxReportCharsHint;
108
+ }
109
+ const maxSerializedBytesHint = normalizePositiveHint(value.maxSerializedBytesHint ?? value.max_serialized_bytes_hint);
110
+ if (maxSerializedBytesHint !== undefined) {
111
+ normalized.maxSerializedBytesHint = maxSerializedBytesHint;
112
+ }
113
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
114
+ }
29
115
  function normalizeStringList(values, options = {}) {
30
116
  const list = Array.isArray(values) ? values : [];
31
117
  const normalized = list
@@ -81,11 +167,31 @@ function normalizeKnowledgeQueryRequestPayload(payload) {
81
167
  function normalizeAgentConversationRequestPayload(payload) {
82
168
  const record = isObjectRecord(payload) ? payload : {};
83
169
  const topKValue = parsePositiveIntegerValue(readFirstPresentValue(record, ['topK', 'k', 'limit']));
170
+ const answerLanguage = normalizeAnswerLanguageValue(readFirstPresentValue(record, ['answerLanguage', 'answer_language', 'responseLanguage', 'response_language']));
171
+ const responseProfile = normalizeResponseProfileValue(readFirstPresentValue(record, ['responseProfile', 'response_profile', 'profile']));
172
+ const responseMode = normalizeResponseModeValue(readFirstPresentValue(record, ['responseMode', 'response_mode', 'answerMode', 'answer_mode', 'detailMode', 'detail_mode']));
173
+ const responseBudgetMode = normalizeResponseBudgetModeValue(readFirstPresentValue(record, [
174
+ 'responseBudgetMode',
175
+ 'response_budget_mode',
176
+ 'budgetMode',
177
+ 'budget_mode',
178
+ ]));
179
+ const responseBudgetCapability = normalizeResponseBudgetCapabilityValue(readFirstPresentValue(record, [
180
+ 'responseBudgetCapability',
181
+ 'response_budget_capability',
182
+ 'budgetCapability',
183
+ 'budget_capability',
184
+ ]));
84
185
  return {
85
186
  userId: String(readFirstPresentValue(record, ['userId', 'user_id', 'learnerId']) || '').trim(),
86
187
  sessionId: readFirstNonEmptyString(record, ['sessionId', 'session_id']),
87
188
  activeTarget: readFirstNonEmptyString(record, ['activeTarget', 'active_target', 'target']),
88
189
  message: readFirstNonEmptyString(record, ['message', 'prompt', 'query', 'q', 'text']) || '',
190
+ answerLanguage,
191
+ responseMode,
192
+ responseBudgetMode,
193
+ responseBudgetCapability,
194
+ responseProfile,
89
195
  topK: topKValue > 0 ? topKValue : undefined,
90
196
  asOf: readFirstNonEmptyString(record, ['asOf', 'as_of', 'timestamp', 'now']),
91
197
  persistMemory: readFirstPresentValue(record, ['persistMemory', 'persist_memory']) !== false,
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const requestNormalization_1 = require("./requestNormalization");
4
+ describe('agent conversation request normalization', () => {
5
+ test.each([
6
+ ['zh-CN', 'zh'],
7
+ ['zh_CN', 'zh'],
8
+ ['Chinese', 'zh'],
9
+ ['en-US', 'en'],
10
+ ['en_US', 'en'],
11
+ ['English', 'en'],
12
+ ['auto', 'auto'],
13
+ ['unsupported-language', undefined],
14
+ ])('normalizes answer language %s to %s', (answerLanguage, expectedLanguage) => {
15
+ expect((0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
16
+ message: 'what is water glass?',
17
+ answerLanguage,
18
+ }).answerLanguage).toBe(expectedLanguage);
19
+ });
20
+ test('keeps an explicit English answer contract even when the query contains CJK text', () => {
21
+ expect((0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
22
+ message: '\u4ec0\u4e48\u662f water glass?',
23
+ response_language: 'en-US',
24
+ }).answerLanguage).toBe('en');
25
+ });
26
+ test('normalizes the additive mobile response profile without changing the default', () => {
27
+ expect((0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
28
+ message: 'what is water glass?',
29
+ response_profile: 'mobile',
30
+ }).responseProfile).toBe('mobile_compact');
31
+ expect((0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
32
+ message: 'what is water glass?',
33
+ }).responseProfile).toBeUndefined();
34
+ });
35
+ test.each([
36
+ [undefined, undefined],
37
+ ['slim', 'slim'],
38
+ ['definition', 'slim'],
39
+ ['compact', 'slim'],
40
+ ['full', 'full'],
41
+ ['comprehensive', 'full'],
42
+ ['unsupported-mode', undefined],
43
+ ])('normalizes answer response mode %s to %s', (responseMode, expectedMode) => {
44
+ expect((0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
45
+ message: 'what is water glass?',
46
+ responseMode,
47
+ }).responseMode).toBe(expectedMode);
48
+ });
49
+ test('keeps response mode independent from the mobile response profile', () => {
50
+ const request = (0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
51
+ message: 'what is water glass?',
52
+ response_mode: 'full',
53
+ response_profile: 'mobile',
54
+ });
55
+ expect(request.responseMode).toBe('full');
56
+ expect(request.responseProfile).toBe('mobile_compact');
57
+ });
58
+ test.each([
59
+ [undefined, 'adaptive'],
60
+ ['adaptive', 'adaptive'],
61
+ ['unbounded', 'unbounded'],
62
+ ['no-cap', 'unbounded'],
63
+ ['no_cap', 'unbounded'],
64
+ ['unsupported-budget', 'adaptive'],
65
+ ])('normalizes response budget mode %s to %s', (responseBudgetMode, expectedMode) => {
66
+ expect((0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
67
+ message: 'what is water glass?',
68
+ responseBudgetMode,
69
+ }).responseBudgetMode).toBe(expectedMode);
70
+ });
71
+ test('normalizes and clamps response budget capability hints at the boundary', () => {
72
+ const request = (0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
73
+ message: 'what is water glass?',
74
+ response_budget_mode: 'unbounded',
75
+ response_budget_capability: {
76
+ memoryClass: 'HIGH',
77
+ workload: 'max',
78
+ maxReportCharsHint: 999999999,
79
+ maxSerializedBytesHint: -4,
80
+ ignored: 'field',
81
+ },
82
+ });
83
+ expect(request.responseBudgetMode).toBe('unbounded');
84
+ expect(request.responseBudgetCapability).toEqual({
85
+ memoryClass: 'high',
86
+ workload: 'max',
87
+ maxReportCharsHint: 999999999,
88
+ });
89
+ });
90
+ test('keeps budget mode additive when mobile projection is requested', () => {
91
+ const request = (0, requestNormalization_1.normalizeAgentConversationRequestPayload)({
92
+ message: 'what is water glass?',
93
+ response_mode: 'full',
94
+ response_budget_mode: 'unbounded',
95
+ response_profile: 'mobile',
96
+ });
97
+ expect(request.responseMode).toBe('full');
98
+ expect(request.responseBudgetMode).toBe('unbounded');
99
+ expect(request.responseProfile).toBe('mobile_compact');
100
+ });
101
+ });