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
@@ -285,12 +285,21 @@ function parseExternalHttpRetryAfterMs(rawValue, nowMs = Date.now()) {
285
285
  }
286
286
  return Math.min(EXTERNAL_HTTP_RETRY_AFTER_MS_MAX, Math.floor(deltaMs));
287
287
  }
288
- async function waitForRetryDelay(ms) {
288
+ async function waitForRetryDelay(ms, signal) {
289
+ signal?.throwIfAborted();
289
290
  if (!Number.isFinite(ms) || ms <= 0) {
290
291
  return;
291
292
  }
292
- await new Promise((resolve) => {
293
- setTimeout(resolve, ms);
293
+ await new Promise((resolve, reject) => {
294
+ const onAbort = () => {
295
+ clearTimeout(timer);
296
+ reject(signal?.reason);
297
+ };
298
+ const timer = setTimeout(() => {
299
+ signal?.removeEventListener('abort', onAbort);
300
+ resolve();
301
+ }, ms);
302
+ signal?.addEventListener('abort', onAbort, { once: true });
294
303
  });
295
304
  }
296
305
  function selectCandidatesFromExternalStub(input) {
@@ -483,6 +492,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
483
492
  return {
484
493
  id: EXTERNAL_HTTP_ADAPTER_ID,
485
494
  async syncIndex(input) {
495
+ input.signal?.throwIfAborted();
486
496
  const requestedRepresentationVersion = normalizeRepresentationVersion(input.representationVersion);
487
497
  const requestedEmbeddingModelId = normalizeEmbeddingModelId(input.embeddingModelId);
488
498
  const requestedEmbeddingDimension = normalizeEmbeddingDimension(input.embeddingDimension);
@@ -543,7 +553,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
543
553
  tokenToAtomIds: Array.from(input.tokenToAtomIds.entries()),
544
554
  signatureBuckets: Array.from(input.signatureBuckets.entries()),
545
555
  }),
546
- signal: controller.signal,
556
+ signal: input.signal ? AbortSignal.any([input.signal, controller.signal]) : controller.signal,
547
557
  });
548
558
  const connectorRequestId = normalizeExternalHttpRequestId(response?.headers?.get?.('x-request-id'), clientRequestId);
549
559
  if (connectorRequestId) {
@@ -559,6 +569,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
559
569
  throw new Error(indexSyncMessage);
560
570
  }
561
571
  const payload = await response.json();
572
+ input.signal?.throwIfAborted();
562
573
  const responseRepresentationVersion = normalizeRepresentationVersion(payload?.representationVersion);
563
574
  const responseEmbeddingModelId = normalizeEmbeddingModelId(payload?.embeddingModelId);
564
575
  const responseEmbeddingDimension = normalizeEmbeddingDimension(payload?.embeddingDimension);
@@ -609,6 +620,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
609
620
  };
610
621
  }
611
622
  catch (error) {
623
+ input.signal?.throwIfAborted();
612
624
  syncFailureCount += 1;
613
625
  indexSyncStatus = endpoint ? 'degraded' : 'unavailable';
614
626
  indexSyncMessage = truncateMessage(error?.message || error, 240) || 'external_http_index_sync_failed';
@@ -624,6 +636,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
624
636
  }
625
637
  },
626
638
  async selectCandidates(input) {
639
+ input.signal?.throwIfAborted();
627
640
  selectionRequestCount += 1;
628
641
  const requestedRepresentationVersion = normalizeRepresentationVersion(input.representationVersion);
629
642
  const requestedEmbeddingModelId = normalizeEmbeddingModelId(input.embeddingModelId);
@@ -686,6 +699,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
686
699
  const totalAttempts = Math.max(1, maxRetries + 1);
687
700
  let lastError = null;
688
701
  for (let attemptIndex = 0; attemptIndex < totalAttempts; attemptIndex += 1) {
702
+ input.signal?.throwIfAborted();
689
703
  const currentAttempt = attemptIndex + 1;
690
704
  const clientRequestId = nextExternalHttpRequestId();
691
705
  let connectorRequestId = '';
@@ -714,7 +728,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
714
728
  : undefined,
715
729
  indexSignature: requestedIndexSignature || undefined,
716
730
  }),
717
- signal: controller.signal,
731
+ signal: input.signal ? AbortSignal.any([input.signal, controller.signal]) : controller.signal,
718
732
  });
719
733
  connectorRequestId = normalizeExternalHttpRequestId(response?.headers?.get?.('x-request-id'), clientRequestId);
720
734
  if (connectorRequestId) {
@@ -739,6 +753,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
739
753
  throw new Error(`external_http_status_${statusCode}:errorCode=${statusErrorCode}${retryAfterSegment}:requestId=${connectorRequestId || clientRequestId}`);
740
754
  }
741
755
  const payload = await response.json();
756
+ input.signal?.throwIfAborted();
742
757
  const mode = String(payload?.mode || '').trim().toLowerCase();
743
758
  const normalizedMode = (mode === 'token_prefilter'
744
759
  || mode === 'token_signature_prefilter') ? mode : 'full_scan';
@@ -858,6 +873,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
858
873
  };
859
874
  }
860
875
  catch (error) {
876
+ input.signal?.throwIfAborted();
861
877
  lastError = error;
862
878
  const transient = isExternalHttpTransientFailure(error);
863
879
  const finalAttempt = currentAttempt >= totalAttempts;
@@ -897,7 +913,7 @@ function createVectorAccelerationAdapter(provider, options = {}) {
897
913
  + `requestId=${connectorRequestId || clientRequestId}`),
898
914
  checkedAt: new Date().toISOString(),
899
915
  });
900
- await waitForRetryDelay(effectiveBackoffDelay);
916
+ await waitForRetryDelay(effectiveBackoffDelay, input.signal);
901
917
  continue;
902
918
  }
903
919
  consecutiveFailures += 1;
@@ -40,6 +40,36 @@ describe('vector acceleration adapter', () => {
40
40
  const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('local');
41
41
  expect(adapter).toBeUndefined();
42
42
  });
43
+ test.each(['syncIndex', 'selectCandidates'])('cancels %s HTTP without retry or circuit failure', async (operation) => {
44
+ const controller = new AbortController();
45
+ const cancelled = new Error('turn_cancelled');
46
+ const fetchMock = jest.fn((_url, init) => new Promise((_resolve, reject) => {
47
+ const signal = init.signal;
48
+ signal.addEventListener('abort', () => reject(signal.reason), { once: true });
49
+ controller.abort(cancelled);
50
+ }));
51
+ globalThis.fetch = fetchMock;
52
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
53
+ externalHttp: { endpoint: 'http://127.0.0.1:18080', maxRetries: 3 },
54
+ });
55
+ const input = { ...baseSelectionInput, signal: controller.signal };
56
+ await expect(adapter[operation](input)).rejects.toBe(cancelled);
57
+ expect(fetchMock).toHaveBeenCalledTimes(1);
58
+ expect(JSON.parse(fetchMock.mock.calls[0][1].body)).not.toHaveProperty('signal');
59
+ expect(adapter.getHealth?.().consecutiveFailures).toBe(0);
60
+ });
61
+ test('cancels the connector retry delay before a second request', async () => {
62
+ const controller = new AbortController();
63
+ const cancelled = new Error('turn_cancelled');
64
+ globalThis.fetch = jest.fn().mockRejectedValue(new Error('socket_hang_up'));
65
+ const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_http', {
66
+ externalHttp: { endpoint: 'http://127.0.0.1:18080', maxRetries: 3, retryDelayMs: 1000 },
67
+ });
68
+ const result = Promise.resolve(adapter.selectCandidates({ ...baseSelectionInput, signal: controller.signal }));
69
+ setImmediate(() => controller.abort(cancelled));
70
+ await expect(result).rejects.toBe(cancelled);
71
+ expect(globalThis.fetch).toHaveBeenCalledTimes(1);
72
+ });
43
73
  test('external stub adapter selects token-based candidates for large corpus', async () => {
44
74
  const adapter = (0, vectorAccelerationAdapter_1.createVectorAccelerationAdapter)('external_stub');
45
75
  expect(adapter).toBeDefined();
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.deriveKnowledgeTargetLookupQueries = deriveKnowledgeTargetLookupQueries;
37
+ exports.normalizeKnowledgeSourcePath = normalizeKnowledgeSourcePath;
38
+ exports.buildKnowledgeSourceInventoryDiff = buildKnowledgeSourceInventoryDiff;
39
+ exports.markdownPreviewMatchesTitleLikeQueries = markdownPreviewMatchesTitleLikeQueries;
40
+ const path = __importStar(require("path"));
41
+ function normalizeLookupQuery(value) {
42
+ return String(value || '')
43
+ .normalize('NFKC')
44
+ .replace(/[??!!。.;;:,,]+/gu, ' ')
45
+ .replace(/\s+/gu, ' ')
46
+ .trim()
47
+ .toLowerCase();
48
+ }
49
+ function stripDefinitionPredicate(value) {
50
+ return value
51
+ .replace(/^(?:what\s+is|what\s+are|define|explain|tell\s+me\s+about|what's)\s+/iu, '')
52
+ .replace(/^(?:什么是|何谓|解释(?:一下)?|介绍(?:一下)?|请(?:解释|介绍)(?:一下)?)\s*/u, '')
53
+ .trim();
54
+ }
55
+ /**
56
+ * Extracts bounded title-like variants from a user query. Compound prompts keep
57
+ * their first definition clause so a Chinese subject can resolve an English
58
+ * filename without scanning the whole corpus.
59
+ */
60
+ function deriveKnowledgeTargetLookupQueries(query) {
61
+ const source = String(query || '').normalize('NFKC');
62
+ const candidates = new Set();
63
+ const add = (value) => {
64
+ const normalized = normalizeLookupQuery(value);
65
+ if (!normalized) {
66
+ return;
67
+ }
68
+ candidates.add(normalized);
69
+ const stripped = stripDefinitionPredicate(normalized);
70
+ if (stripped) {
71
+ candidates.add(stripped);
72
+ candidates.add(stripped.replace(/\s+/gu, ''));
73
+ }
74
+ };
75
+ add(source);
76
+ source.split(/[??!!。.;;\n\r]+/gu).forEach(add);
77
+ const definitionSubject = source.match(/^(?:what\s+is|what\s+are|define|explain|tell\s+me\s+about|what's|什么是|何谓|解释(?:一下)?|介绍(?:一下)?|请(?:解释|介绍)(?:一下)?)\s*([^??!!。.;;\n\r]+)/iu);
78
+ if (definitionSubject?.[1]) {
79
+ const subject = definitionSubject[1]
80
+ .replace(/\s+(?:我应该|应该|how\s+should|what\s+should).*$/iu, '')
81
+ .trim();
82
+ add(subject);
83
+ }
84
+ if (candidates.has('water glass') || candidates.has('waterglass')) {
85
+ candidates.add('water glass');
86
+ candidates.add('waterglass');
87
+ candidates.add('水玻璃');
88
+ candidates.add('水杯');
89
+ }
90
+ return Array.from(candidates.values()).filter(Boolean);
91
+ }
92
+ /**
93
+ * Produces the case-folded path key used only for inventory reconciliation.
94
+ * The original spelling is retained in diff output for diagnostics and ingest.
95
+ */
96
+ function normalizeKnowledgeSourcePath(value) {
97
+ return String(value || '')
98
+ .normalize('NFKC')
99
+ .replace(/\\/g, '/')
100
+ .replace(/^\/+/, '')
101
+ .replace(/\/{2,}/g, '/')
102
+ .replace(/\/+$/g, '')
103
+ .toLowerCase();
104
+ }
105
+ function buildKnowledgeSourceInventoryDiff(params) {
106
+ const collect = (sourcePaths) => {
107
+ const byNormalizedPath = new Map();
108
+ sourcePaths.forEach((sourcePath) => {
109
+ const normalized = normalizeKnowledgeSourcePath(sourcePath);
110
+ if (normalized && !byNormalizedPath.has(normalized)) {
111
+ byNormalizedPath.set(normalized, sourcePath);
112
+ }
113
+ });
114
+ return byNormalizedPath;
115
+ };
116
+ const disk = collect(params.diskSourcePaths);
117
+ const indexed = collect(params.indexedSourcePaths);
118
+ const addedSourcePaths = Array.from(disk.entries())
119
+ .filter(([normalizedPath]) => !indexed.has(normalizedPath))
120
+ .map(([, sourcePath]) => sourcePath)
121
+ .sort((left, right) => normalizeKnowledgeSourcePath(left).localeCompare(normalizeKnowledgeSourcePath(right)));
122
+ const removedSourcePaths = Array.from(indexed.entries())
123
+ .filter(([normalizedPath]) => !disk.has(normalizedPath))
124
+ .map(([, sourcePath]) => sourcePath)
125
+ .sort((left, right) => normalizeKnowledgeSourcePath(left).localeCompare(normalizeKnowledgeSourcePath(right)));
126
+ return { addedSourcePaths, removedSourcePaths };
127
+ }
128
+ function normalizeTitleCandidate(value) {
129
+ return String(value || '')
130
+ .normalize('NFKC')
131
+ .replace(/[^\p{L}\p{N}\s_-]+/gu, ' ')
132
+ .replace(/\s+/g, ' ')
133
+ .trim()
134
+ .toLowerCase()
135
+ .replace(/^(?:the|a|an)\s+/u, '')
136
+ .trim();
137
+ }
138
+ function collectPreviewTitleCandidates(sourcePath, preview) {
139
+ const candidates = new Set();
140
+ const basename = path.posix.basename(String(sourcePath || '').replace(/\\/g, '/'))
141
+ .replace(/\.[^.]+$/u, '');
142
+ if (basename) {
143
+ candidates.add(basename);
144
+ }
145
+ String(preview || '').split(/\r?\n/u).forEach((line) => {
146
+ const trimmed = line.trim();
147
+ if (!trimmed) {
148
+ return;
149
+ }
150
+ const heading = trimmed.match(/^#{1,6}\s+(.+?)\s*#*$/u);
151
+ if (heading?.[1]) {
152
+ candidates.add(heading[1]);
153
+ return;
154
+ }
155
+ if (candidates.size <= 1 && !/^```|^[-*+]\s|^\d+[.)]\s/u.test(trimmed)) {
156
+ candidates.add(trimmed);
157
+ }
158
+ });
159
+ return Array.from(candidates.values()).map(normalizeTitleCandidate).filter(Boolean);
160
+ }
161
+ function markdownPreviewMatchesTitleLikeQueries(input) {
162
+ const queries = Array.from(new Set(input.titleLikeQueries.map(normalizeTitleCandidate).filter((query) => query.length >= 2)));
163
+ if (queries.length <= 0) {
164
+ return false;
165
+ }
166
+ const candidates = collectPreviewTitleCandidates(input.sourcePath, input.preview);
167
+ return candidates.some((candidate) => queries.some((query) => candidate === query || candidate.includes(query)));
168
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const workspaceHydration_1 = require("./workspaceHydration");
4
+ describe('workspaceHydration', () => {
5
+ test('detects source files that exist on disk but are absent from the indexed workspace', () => {
6
+ const diff = (0, workspaceHydration_1.buildKnowledgeSourceInventoryDiff)({
7
+ diskSourcePaths: [
8
+ 'Knowledge_Base/waterglass/water glass.md',
9
+ 'Knowledge_Base/waterglass/Amorphous ice.md',
10
+ ],
11
+ indexedSourcePaths: [
12
+ 'knowledge_base\\waterglass\\water glass.md',
13
+ ],
14
+ });
15
+ expect(diff.addedSourcePaths).toEqual([
16
+ 'Knowledge_Base/waterglass/Amorphous ice.md',
17
+ ]);
18
+ expect(diff.removedSourcePaths).toEqual([]);
19
+ });
20
+ test('matches a Chinese heading when the file basename uses the English concept name', () => {
21
+ expect((0, workspaceHydration_1.markdownPreviewMatchesTitleLikeQueries)({
22
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
23
+ preview: '## 非晶冰\n\n非晶冰(Amorphous ice)是水的一种固态形式。',
24
+ titleLikeQueries: ['非晶冰'],
25
+ })).toBe(true);
26
+ });
27
+ test('keeps the definition subject from a compound Chinese conversation query', () => {
28
+ expect((0, workspaceHydration_1.deriveKnowledgeTargetLookupQueries)('什么是非晶冰?我应该通过哪些知识点学习?')).toEqual(expect.arrayContaining(['非晶冰']));
29
+ });
30
+ });
@@ -44,6 +44,7 @@ describe('license policy contract', () => {
44
44
  const tauriCargoTomlPath = path.join(repoRoot, 'src-tauri', 'Cargo.toml');
45
45
  const licensePath = path.join(repoRoot, 'LICENSE');
46
46
  const readmePath = path.join(repoRoot, 'README.md');
47
+ const chineseReadmePath = path.join(repoRoot, 'README_zh.md');
47
48
  test('root package manifest is pinned to GPL-3.0-only', () => {
48
49
  const pkg = readJson(packageJsonPath);
49
50
  expect(pkg.license).toBe('GPL-3.0-only');
@@ -57,10 +58,14 @@ describe('license policy contract', () => {
57
58
  expect(license).toContain('GNU GENERAL PUBLIC LICENSE');
58
59
  expect(license).toContain('Version 3, 29 June 2007');
59
60
  });
60
- test('README license section advertises GPL-3.0-only in both locales', () => {
61
+ test('README license sections advertise GPL-3.0-only in both locales', () => {
61
62
  const readme = fs.readFileSync(readmePath, 'utf8');
62
- expect(readme).toContain('License / 开源许可');
63
- expect(readme).toContain('GNU General Public License v3.0 (GPL-3.0-only)');
64
- expect(readme).toContain('GNU General Public License v3.0(GPL-3.0-only)');
63
+ const chineseReadme = fs.readFileSync(chineseReadmePath, 'utf8');
64
+ expect(readme).toContain('## License');
65
+ expect(readme).toContain('GNU General Public License v3.0');
66
+ expect(readme).toContain('GPL-3.0-only');
67
+ expect(chineseReadme).toContain('## 开源许可');
68
+ expect(chineseReadme).toContain('GNU General Public License v3.0');
69
+ expect(chineseReadme).toContain('GPL-3.0-only');
65
70
  });
66
71
  });
@@ -697,6 +697,10 @@ class MarkdownGateway {
697
697
  candidates.push(path.join(path.dirname(process.execPath), binaryName));
698
698
  candidates.push(path.join(path.dirname(process.execPath), '..', 'Resources', binaryName));
699
699
  }
700
+ // Tauri removes the target triple from installed external binary names.
701
+ const installedBinaryName = process.platform === 'win32' ? 'markdown-worker.exe' : 'markdown-worker';
702
+ candidates.push(path.join(path.dirname(process.execPath), installedBinaryName));
703
+ candidates.push(path.join(path.dirname(process.execPath), '..', 'Resources', installedBinaryName));
700
704
  for (const candidate of candidates) {
701
705
  try {
702
706
  if (!candidate) {
@@ -37,6 +37,7 @@ const fs = __importStar(require("fs"));
37
37
  const os = __importStar(require("os"));
38
38
  const path = __importStar(require("path"));
39
39
  const MarkdownGateway_1 = require("./MarkdownGateway");
40
+ const { resolveHostMarkdownWorkerBinaryName } = require('../../scripts/tauri-sidecar-utils');
40
41
  class TempDir {
41
42
  constructor(prefix) {
42
43
  this.path = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), `${prefix}-`));
@@ -192,4 +193,62 @@ describe('MarkdownGateway', () => {
192
193
  }
193
194
  }
194
195
  });
196
+ const testUnix = process.platform === 'win32' ? test.skip : test;
197
+ testUnix.each(['beside-server', 'Resources', 'explicit-override', 'build-filename'])('buildIndex discovers the %s worker and preserves lookup priority', async (location) => {
198
+ const installedDirectory = ctx.temp.mkdir('installed/bin');
199
+ const originalExecPath = process.execPath;
200
+ const originalWorkerPath = process.env.NOTE_CONNECTION_MARKDOWN_WORKER_PATH;
201
+ const writeWorker = (workerPath, anchorId) => {
202
+ fs.mkdirSync(path.dirname(workerPath), { recursive: true });
203
+ fs.writeFileSync(workerPath, `#!/usr/bin/env node
204
+ const fs = require('fs');
205
+ let input = '';
206
+ process.stdin.on('data', chunk => { input += chunk; });
207
+ process.stdin.on('end', () => {
208
+ const request = JSON.parse(input);
209
+ if (request.kind !== 'build_index') process.exit(1);
210
+ const content = fs.readFileSync(request.filePath);
211
+ const block = { id: 0, type: 'heading', startByte: 0, endByte: content.length, startLine: 1, endLine: 1, anchorId: ${JSON.stringify(anchorId)} };
212
+ process.stdout.write(JSON.stringify({ ok: true, index: { totalBytes: content.length, totalLines: 1, blocks: [block], anchors: [], wikiLinks: [] } }));
213
+ });
214
+ `, { mode: 0o755 });
215
+ };
216
+ try {
217
+ process.execPath = path.join(installedDirectory, 'server');
218
+ delete process.env.NOTE_CONNECTION_MARKDOWN_WORKER_PATH;
219
+ const installedWorker = location === 'Resources'
220
+ ? path.join(installedDirectory, '..', 'Resources', 'markdown-worker')
221
+ : path.join(installedDirectory, 'markdown-worker');
222
+ writeWorker(installedWorker, 'installed-worker');
223
+ if (location === 'explicit-override' || location === 'build-filename') {
224
+ writeWorker(path.join(ctx.projectRoot, 'src-tauri', 'bin', resolveHostMarkdownWorkerBinaryName()), 'build-worker');
225
+ }
226
+ if (location === 'explicit-override') {
227
+ const explicitWorker = path.join(ctx.temp.path, 'explicit-worker');
228
+ writeWorker(explicitWorker, 'explicit-worker');
229
+ process.env.NOTE_CONNECTION_MARKDOWN_WORKER_PATH = explicitWorker;
230
+ }
231
+ const filePath = ctx.temp.file(path.join('project', 'Knowledge_Base', 'installed.md'), '# Installed markdown worker');
232
+ const index = await ctx.gateway.buildIndex({ filePath }, {
233
+ markdownEngine: 'pulldown',
234
+ chunkBlockSize: 8,
235
+ prefetchBlocks: 2,
236
+ indexCacheTtlSec: 1800,
237
+ maxDocBytes: 32 * 1024 * 1024,
238
+ });
239
+ expect(index.engine).toBe('pulldown');
240
+ expect(index.fallbackReason).toBeUndefined();
241
+ const chunk = await ctx.gateway.getChunk({ indexId: index.indexId, startBlock: 0, blockCount: 1 });
242
+ const expectedAnchor = location === 'explicit-override' ? 'explicit-worker'
243
+ : location === 'build-filename' ? 'build-worker' : 'installed-worker';
244
+ expect(chunk.blocks[0].anchorId).toBe(expectedAnchor);
245
+ }
246
+ finally {
247
+ process.execPath = originalExecPath;
248
+ if (originalWorkerPath === undefined)
249
+ delete process.env.NOTE_CONNECTION_MARKDOWN_WORKER_PATH;
250
+ else
251
+ process.env.NOTE_CONNECTION_MARKDOWN_WORKER_PATH = originalWorkerPath;
252
+ }
253
+ });
195
254
  });
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SseResponseWriter = void 0;
4
+ /** Owns backpressure and the single terminal outcome of one HTTP event stream. */
5
+ class SseResponseWriter {
6
+ constructor(destination, policy = {
7
+ maxBufferedBytes: 64 * 1024 * 1024 + 256 * 1024,
8
+ drainTimeoutMs: 15000,
9
+ }) {
10
+ this.destination = destination;
11
+ this.policy = policy;
12
+ this.pending = [];
13
+ this.queuedBytes = 0;
14
+ this.blocked = false;
15
+ this.finishing = false;
16
+ this.finished = false;
17
+ this.onDrain = () => {
18
+ clearTimeout(this.timer);
19
+ this.timer = undefined;
20
+ this.blocked = false;
21
+ try {
22
+ while (this.pending.length && !this.blocked && !this.failure) {
23
+ const frame = this.pending.shift();
24
+ this.queuedBytes -= Buffer.byteLength(frame);
25
+ this.send(frame);
26
+ }
27
+ if (this.finishing && !this.blocked && !this.failure)
28
+ this.destination.end();
29
+ }
30
+ catch (error) {
31
+ this.fail(error instanceof Error ? error : new Error(String(error)));
32
+ }
33
+ };
34
+ this.onError = (error) => { this.fail(error); };
35
+ this.onClose = () => {
36
+ if (!this.finished)
37
+ this.fail(new Error('sse_closed'));
38
+ this.cleanup();
39
+ };
40
+ this.onFinish = () => {
41
+ if (this.failure)
42
+ return;
43
+ this.finished = true;
44
+ this.cleanup();
45
+ this.resolveCompletion?.();
46
+ };
47
+ if (!Number.isFinite(policy.maxBufferedBytes) || policy.maxBufferedBytes < 1
48
+ || !Number.isFinite(policy.drainTimeoutMs) || policy.drainTimeoutMs < 1)
49
+ throw new TypeError('Invalid SSE delivery policy');
50
+ destination.on('drain', this.onDrain);
51
+ destination.on('error', this.onError);
52
+ destination.once('close', this.onClose);
53
+ destination.once('finish', this.onFinish);
54
+ }
55
+ get pendingBytes() { return this.failure || this.finished ? 0 : this.queuedBytes + this.destination.writableLength; }
56
+ write(frame) {
57
+ if (this.failure)
58
+ throw this.failure;
59
+ if (this.finishing || this.finished || this.destination.destroyed || this.destination.writableEnded)
60
+ throw new Error('sse_closed');
61
+ const bytes = Buffer.byteLength(frame);
62
+ if (this.pending.length >= 128 || this.pendingBytes + bytes > this.policy.maxBufferedBytes) {
63
+ this.fail(new Error('sse_buffer_limit'));
64
+ throw this.failure;
65
+ }
66
+ if (this.blocked) {
67
+ this.pending.push(frame);
68
+ this.queuedBytes += bytes;
69
+ return;
70
+ }
71
+ this.send(frame);
72
+ }
73
+ finish() {
74
+ if (this.failure)
75
+ return Promise.reject(this.failure);
76
+ if (this.finished)
77
+ return Promise.resolve();
78
+ if (this.completion)
79
+ return this.completion;
80
+ this.finishing = true;
81
+ this.completion = new Promise((resolve, reject) => {
82
+ this.resolveCompletion = resolve;
83
+ this.rejectCompletion = reject;
84
+ });
85
+ if (!this.blocked && this.pending.length === 0)
86
+ this.destination.end();
87
+ return this.completion;
88
+ }
89
+ send(frame) {
90
+ try {
91
+ if (!this.destination.write(frame)) {
92
+ this.blocked = true;
93
+ this.timer = setTimeout(() => this.fail(new Error('sse_drain_timeout')), this.policy.drainTimeoutMs);
94
+ this.timer.unref();
95
+ }
96
+ }
97
+ catch (error) {
98
+ this.fail(error instanceof Error ? error : new Error(String(error)));
99
+ throw this.failure;
100
+ }
101
+ }
102
+ fail(error) {
103
+ if (this.failure || this.finished)
104
+ return;
105
+ this.failure = error;
106
+ this.pending.length = 0;
107
+ this.queuedBytes = 0;
108
+ clearTimeout(this.timer);
109
+ this.destination.removeListener('drain', this.onDrain);
110
+ this.rejectCompletion?.(error);
111
+ // Preserve the error for finish(); destroy without an unhandled second error event.
112
+ this.destination.destroy();
113
+ }
114
+ cleanup() {
115
+ clearTimeout(this.timer);
116
+ this.destination.removeListener('drain', this.onDrain);
117
+ this.destination.removeListener('error', this.onError);
118
+ this.destination.removeListener('close', this.onClose);
119
+ this.destination.removeListener('finish', this.onFinish);
120
+ }
121
+ }
122
+ exports.SseResponseWriter = SseResponseWriter;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const stream_1 = require("stream");
4
+ const SseResponseWriter_1 = require("./SseResponseWriter");
5
+ describe('SSE delivery lifecycle', () => {
6
+ function slowDestination() {
7
+ const frames = [];
8
+ const callbacks = [];
9
+ const destination = new stream_1.Writable({ highWaterMark: 1, write(chunk, _encoding, callback) {
10
+ frames.push(chunk.toString());
11
+ callbacks.push(callback);
12
+ } });
13
+ return { destination, frames, callbacks };
14
+ }
15
+ test('waits for drain, preserves frame order and completes exactly once', async () => {
16
+ const { destination, frames, callbacks } = slowDestination();
17
+ const writer = new SseResponseWriter_1.SseResponseWriter(destination, { maxBufferedBytes: 64, drainTimeoutMs: 1000 });
18
+ const finish = jest.fn();
19
+ destination.on('finish', finish);
20
+ writer.write('first');
21
+ writer.write('second');
22
+ const completed = writer.finish();
23
+ expect(frames).toEqual(['first']);
24
+ callbacks.shift()();
25
+ expect(frames).toEqual(['first', 'second']);
26
+ callbacks.shift()();
27
+ await completed;
28
+ await writer.finish();
29
+ expect(finish).toHaveBeenCalledTimes(1);
30
+ expect(writer.pendingBytes).toBe(0);
31
+ });
32
+ test('bounds pending bytes and destroys an overproducing stream', async () => {
33
+ const { destination } = slowDestination();
34
+ const writer = new SseResponseWriter_1.SseResponseWriter(destination, { maxBufferedBytes: 8, drainTimeoutMs: 1000 });
35
+ writer.write('123456');
36
+ expect(() => writer.write('789')).toThrow('sse_buffer_limit');
37
+ expect(destination.destroyed).toBe(true);
38
+ await expect(writer.finish()).rejects.toThrow('sse_buffer_limit');
39
+ });
40
+ test.each(['close', 'error', 'timeout'])('settles %s while waiting for a slow client', async (cause) => {
41
+ const { destination } = slowDestination();
42
+ const writer = new SseResponseWriter_1.SseResponseWriter(destination, { maxBufferedBytes: 64, drainTimeoutMs: 10 });
43
+ writer.write('first');
44
+ writer.write('second');
45
+ const completed = writer.finish();
46
+ if (cause === 'close')
47
+ destination.destroy();
48
+ if (cause === 'error')
49
+ destination.destroy(new Error('socket_failure'));
50
+ await expect(completed).rejects.toThrow(cause === 'timeout' ? 'sse_drain_timeout' : cause === 'error' ? 'socket_failure' : 'sse_closed');
51
+ expect(writer.pendingBytes).toBe(0);
52
+ expect(destination.listenerCount('drain')).toBe(0);
53
+ });
54
+ });