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,94 @@
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
+ const fs = __importStar(require("fs"));
37
+ const path = __importStar(require("path"));
38
+ const KnowledgeLearningPlatform_1 = require("./KnowledgeLearningPlatform");
39
+ const regressionCorpus = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../fixtures/answer-quality/v2.json'), 'utf8'));
40
+ async function answer(message, documents, language = 'en') {
41
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({ autoPersist: false });
42
+ await platform.ingestKnowledge({ documents, relationRecomputeMode: 'none' });
43
+ const request = { message, answerLanguage: language, responseMode: 'full', persistMemory: false,
44
+ scope: { documentIds: documents.map(document => document.documentId) } };
45
+ return { platform, response: await platform.agentConversation(request) };
46
+ }
47
+ describe('public evidence source selection', () => {
48
+ test.each(['en', 'zh'])('keeps scoped topic distractors out of the %s answer, citations and required claims', async (language) => {
49
+ const entry = regressionCorpus.cases.find((candidate) => candidate.id === `confirm-wal-${language}`);
50
+ const { platform, response } = await answer(entry.query, entry.documents, language);
51
+ const excludedDocumentId = entry.documents[1].documentId;
52
+ expect(response.answer).not.toMatch(/cardinals|bird.watching|红雀|观鸟/iu);
53
+ expect(response.answer).toMatch(language === 'en' ? /durable log.*reconstruct/iu : /持久日志重建/u);
54
+ expect(response.citations.every(citation => citation.documentId !== excludedDocumentId)).toBe(true);
55
+ expect(response.answerReleaseReview?.auditGraphAnswerPlan?.claims.every(claim => (claim.evidenceRefs.every(reference => reference.sourcePath !== entry.documents[1].sourcePath)))).toBe(true);
56
+ expect(response.trace.ragContextPack?.sourceDecisions).toContainEqual(expect.objectContaining({
57
+ documentId: excludedDocumentId, status: 'fragment_dropped', reason: 'query_subject_mismatch', charsRead: 0,
58
+ }));
59
+ const retrieval = await platform.queryKnowledge({ query: entry.query, topK: 12,
60
+ scope: { documentIds: entry.documents.map((document) => document.documentId) } });
61
+ expect(retrieval.items.some(item => item.atom.documentId === excludedDocumentId)).toBe(true);
62
+ });
63
+ test('retains a second source whose body establishes the requested subject', async () => {
64
+ const { response } = await answer('Explain recovery point objectives.', [
65
+ { documentId: 'rpo-definition', sourcePath: 'quality/rpo.md', content: '# Recovery Point Objectives\nA recovery point objective bounds the acceptable age of recovered data.' },
66
+ { documentId: 'rpo-scheduling', sourcePath: 'quality/schedule.md', content: '# Scheduling Policy\nRecovery point objectives determine how frequently snapshots must be taken.' },
67
+ { documentId: 'business-objectives', sourcePath: 'quality/business.md', content: '# Business Objectives\nBusiness objectives guide revenue targets and quarterly sales reviews.' },
68
+ ]);
69
+ expect(response.answer).toMatch(/acceptable age/iu);
70
+ expect(response.answer).toMatch(/how frequently snapshots/iu);
71
+ expect(response.answer).not.toMatch(/revenue|quarterly sales/iu);
72
+ expect(response.citations.map(citation => citation.documentId)).toEqual(expect.arrayContaining(['rpo-definition', 'rpo-scheduling']));
73
+ });
74
+ test('retains both comparison operands while rejecting a shared-word distractor', async () => {
75
+ const { response } = await answer('Compare an exclusive lock and a shared lock.', [
76
+ { documentId: 'exclusive', sourcePath: 'quality/exclusive.md', content: '# Exclusive Lock\nAn exclusive lock permits only one owner to access the protected resource.' },
77
+ { documentId: 'shared', sourcePath: 'quality/shared.md', content: '# Shared Lock\nA shared lock permits several readers to access the protected resource concurrently.' },
78
+ { documentId: 'door', sourcePath: 'quality/door.md', content: '# Door Lock\nA door lock may be painted bright yellow to match a wooden door.' },
79
+ ]);
80
+ expect(response.answer).toMatch(/only one owner/iu);
81
+ expect(response.answer).toMatch(/several readers/iu);
82
+ expect(response.answer).not.toMatch(/bright yellow|wooden door/iu);
83
+ expect(response.citations.map(citation => citation.documentId)).not.toContain('door');
84
+ });
85
+ test('does not choose a winner among competing observations of the same subject', async () => {
86
+ const { response } = await answer('What is the Echo clock rate?', [
87
+ { documentId: 'echo-a', sourcePath: 'quality/echo-a.md', content: '# Echo Clock Rate\nThe Echo clock rate is 4 Hz.' },
88
+ { documentId: 'echo-b', sourcePath: 'quality/echo-b.md', content: '# Echo Clock Rate Record\nThe Echo clock rate is 9 Hz.' },
89
+ ]);
90
+ expect(response.answer).toMatch(/4\s*Hz/iu);
91
+ expect(response.answer).toMatch(/9\s*Hz/iu);
92
+ expect(response.citations.map(citation => citation.documentId)).toEqual(expect.arrayContaining(['echo-a', 'echo-b']));
93
+ });
94
+ });
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const KnowledgeLearningPlatform_1 = require("./KnowledgeLearningPlatform");
4
+ const agentConversationSerialization_1 = require("./agentConversationSerialization");
5
+ async function answerMeasurements(request, first, second) {
6
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({ autoPersist: false });
7
+ await platform.ingestKnowledge({ relationRecomputeMode: 'none', documents: [
8
+ { documentId: 'clock-primary', sourcePath: 'quality/clock-primary.md', content: first },
9
+ { documentId: 'clock-secondary', sourcePath: 'quality/clock-secondary.md', content: second },
10
+ ] });
11
+ return platform.agentConversation({ ...request, persistMemory: false });
12
+ }
13
+ describe('public measurement conflict disclosure', () => {
14
+ test.each(['slim', 'full'])('keeps conflicting English observations and their citations in %s', async (responseMode) => {
15
+ const response = await answerMeasurements({ message: 'What is the Echo clock rate?', answerLanguage: 'en', responseMode }, '# Echo Clock Rate\nThe Echo clock rate is 4 Hz.', '# Echo Clock Rate Record\nThe Echo clock rate is 9 Hz.');
16
+ expect(response.trace.ragContextPack?.fragments.some(fragment => fragment.role === 'conflict')).toBe(true);
17
+ expect(response.answer).toMatch(/4\s*Hz/iu);
18
+ expect(response.answer).toMatch(/9\s*Hz/iu);
19
+ expect(response.answer).toMatch(/conflict|contradict|disagree/iu);
20
+ expect(response.citations.map(citation => citation.documentId)).toEqual(expect.arrayContaining(['clock-primary', 'clock-secondary']));
21
+ expect(response.answerReleaseReview?.decision).not.toBe('abstain');
22
+ });
23
+ test.each(['slim', 'full'])('keeps conflicting Chinese observations and their citations in %s', async (responseMode) => {
24
+ const response = await answerMeasurements({ message: '回声时钟频率是多少?', answerLanguage: 'zh', responseMode }, '# 回声时钟频率\n回声时钟频率是4赫兹。', '# 回声时钟频率记录\n回声时钟频率是9赫兹。');
25
+ expect(response.trace.ragContextPack?.fragments.some(fragment => fragment.role === 'conflict')).toBe(true);
26
+ expect(response.answer).toContain('4赫兹');
27
+ expect(response.answer).toContain('9赫兹');
28
+ expect(response.answer).toMatch(/冲突|矛盾|不一致/u);
29
+ expect(response.citations.map(citation => citation.documentId)).toEqual(expect.arrayContaining(['clock-primary', 'clock-secondary']));
30
+ expect(response.answerReleaseReview?.decision).not.toBe('abstain');
31
+ });
32
+ test.each(['slim', 'full'])('does not manufacture conflicts between equivalent observations in %s', async (responseMode) => {
33
+ const response = await answerMeasurements({ message: 'What is the Echo clock rate?', answerLanguage: 'en', responseMode }, '# Echo Clock Rate\nThe Echo clock rate is 1 kHz.', '# Echo Clock Rate Record\nThe Echo clock rate is 1000 Hz.');
34
+ expect(response.trace.ragContextPack?.fragments.some(fragment => fragment.role === 'conflict')).toBe(false);
35
+ expect(response.answer).not.toMatch(/conflict|contradict|disagree/iu);
36
+ expect(response.answer).toMatch(/(?:1\s*kHz|1000\s*Hz)/iu);
37
+ expect(response.answerReleaseReview?.decision).not.toBe('abstain');
38
+ });
39
+ test('retains the same conflict and citations through bounded JSON, SSE and replay', async () => {
40
+ const response = await answerMeasurements({ message: 'What is the Echo clock rate?', answerLanguage: 'en', responseMode: 'full' }, '# Echo Clock Rate\nThe Echo clock rate is 4 Hz.', '# Echo Clock Rate Record\nThe Echo clock rate is 9 Hz.');
41
+ response.responseBudget.runtimeGovernor.maxSerializedBytes = 6000;
42
+ const http = (0, agentConversationSerialization_1.serializeAgentConversationHttpResponse)(response);
43
+ const event = { type: 'turn_completed', turnId: 'conflict-disclosure', emittedAt: '2026-09-13T00:00:00.000Z', result: response };
44
+ const live = (0, agentConversationSerialization_1.serializeAgentConversationTurnEvent)('turn_completed', event);
45
+ const replay = (0, agentConversationSerialization_1.serializeAgentConversationTurnEvent)('turn_completed', { ...event, result: http.result });
46
+ expect(http.truncated).toBe(true);
47
+ expect(JSON.parse(live.json).result).toEqual(http.result);
48
+ expect(JSON.parse(replay.json).result).toEqual(http.result);
49
+ expect(http.result.answer).toMatch(/observations conflict/iu);
50
+ expect(http.result.answer).toMatch(/4\s*Hz/iu);
51
+ expect(http.result.answer).toMatch(/9\s*Hz/iu);
52
+ expect(http.result.citations.map(citation => citation.documentId)).toEqual(expect.arrayContaining(['clock-primary', 'clock-secondary']));
53
+ });
54
+ });
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentConversationExecution = exports.AgentConversationExecutionError = exports.DEFAULT_AGENT_CONVERSATION_EXECUTION_POLICY = void 0;
4
+ exports.normalizeAgentConversationExecutionPolicy = normalizeAgentConversationExecutionPolicy;
5
+ const promises_1 = require("fs/promises");
6
+ const async_hooks_1 = require("async_hooks");
7
+ const agentResponseBudget_1 = require("./agentResponseBudget");
8
+ exports.DEFAULT_AGENT_CONVERSATION_EXECUTION_POLICY = Object.freeze({
9
+ timeoutMs: 180000,
10
+ maxSourceBytes: 4 * 1024 * 1024,
11
+ maxTotalSourceBytes: 16 * 1024 * 1024,
12
+ maxPendingTurns: 8,
13
+ maxSourceLines: 16384,
14
+ maxSourceFacts: 4096,
15
+ });
16
+ class AgentConversationExecutionError extends Error {
17
+ constructor(code) {
18
+ super(code);
19
+ this.code = code;
20
+ this.name = 'AgentConversationExecutionError';
21
+ this.statusCode = code === 'runtime_turn_capacity' ? 429
22
+ : code === 'runtime_timeout' ? 504
23
+ : code === 'runtime_cancelled' ? 499 : 413;
24
+ }
25
+ }
26
+ exports.AgentConversationExecutionError = AgentConversationExecutionError;
27
+ function normalizeAgentConversationExecutionPolicy(input = {}) {
28
+ const policy = { ...exports.DEFAULT_AGENT_CONVERSATION_EXECUTION_POLICY };
29
+ for (const key of Object.keys(policy)) {
30
+ const candidate = input[key];
31
+ if (candidate === undefined)
32
+ continue;
33
+ if (!Number.isFinite(candidate) || candidate < 1) {
34
+ throw new TypeError(`Invalid agent conversation execution policy: ${key}`);
35
+ }
36
+ policy[key] = Math.min(policy[key], Math.floor(candidate));
37
+ }
38
+ policy.maxSourceBytes = Math.min(policy.maxSourceBytes, policy.maxTotalSourceBytes);
39
+ return policy;
40
+ }
41
+ /** Owns one turn's cooperative deadline and bounded source I/O, including queued time. */
42
+ class AgentConversationExecution {
43
+ constructor(budget, policy, parentSignal) {
44
+ this.budget = budget;
45
+ this.policy = policy;
46
+ this.parentSignal = parentSignal;
47
+ this.active = true;
48
+ this.controller = new AbortController();
49
+ this.startedAtMs = Date.now();
50
+ this.sourceBytes = 0;
51
+ this.sourceReads = 0;
52
+ this.processedFragments = 0;
53
+ this.sourceFacts = 0;
54
+ this.timeoutMs = Math.min(policy.timeoutMs, budget.runtimeGovernor.timeoutMs);
55
+ this.budget = { ...budget, runtimeGovernor: { ...budget.runtimeGovernor, timeoutMs: this.timeoutMs } };
56
+ this.onParentAbort = () => this.controller.abort(new AgentConversationExecutionError('runtime_cancelled'));
57
+ this.timer = setTimeout(() => {
58
+ this.controller.abort(new AgentConversationExecutionError('runtime_timeout'));
59
+ }, this.timeoutMs);
60
+ this.timer.unref();
61
+ if (parentSignal?.aborted)
62
+ this.onParentAbort();
63
+ else
64
+ parentSignal?.addEventListener('abort', this.onParentAbort, { once: true });
65
+ }
66
+ static async run(options, operation) {
67
+ const existing = this.current();
68
+ if (existing) {
69
+ existing.assertActive();
70
+ return operation(existing);
71
+ }
72
+ const execution = new AgentConversationExecution(options.budget, options.policy, options.signal);
73
+ try {
74
+ execution.assertActive();
75
+ // Keep the state lease until cooperative work has actually stopped.
76
+ // Racing an uncancelled promise would allow it to mutate a later turn.
77
+ return await this.scope.run(execution, () => operation(execution));
78
+ }
79
+ finally {
80
+ execution.active = false;
81
+ clearTimeout(execution.timer);
82
+ options.signal?.removeEventListener('abort', execution.onParentAbort);
83
+ }
84
+ }
85
+ get signal() { return this.controller.signal; }
86
+ get maxFragmentChars() { return this.budget.rag.maxCharsPerFragment; }
87
+ get truncationReason() { return this.sourceStop; }
88
+ static current() {
89
+ const execution = this.scope.getStore();
90
+ return execution?.active ? execution : undefined;
91
+ }
92
+ isResourceLimit(error) {
93
+ return error instanceof AgentConversationExecutionError && (error.code === 'runtime_source_bytes_limit'
94
+ || error.code === 'runtime_total_source_bytes_limit'
95
+ || error.code === 'runtime_fragment_limit'
96
+ || error.code === 'runtime_source_lines_limit'
97
+ || error.code === 'runtime_source_facts_limit');
98
+ }
99
+ assertActive() {
100
+ if (!this.active)
101
+ throw new AgentConversationExecutionError('runtime_cancelled');
102
+ if (this.signal.aborted)
103
+ throw this.signal.reason;
104
+ const decision = (0, agentResponseBudget_1.applyRuntimeGovernor)({
105
+ budget: this.budget,
106
+ startedAtMs: this.startedAtMs, nowMs: Date.now(),
107
+ processedFragments: this.processedFragments, serializedBytes: 0, reportChars: 0,
108
+ });
109
+ if (!decision.allowed) {
110
+ const code = decision.reason === 'runtime_timeout' ? 'runtime_timeout' : 'runtime_fragment_limit';
111
+ throw new AgentConversationExecutionError(code);
112
+ }
113
+ }
114
+ async yieldCheckpoint() {
115
+ this.assertActive();
116
+ await new Promise(resolve => setImmediate(resolve));
117
+ this.assertActive();
118
+ }
119
+ recordFragment() {
120
+ if (this.processedFragments >= this.budget.runtimeGovernor.maxFragmentsProcessed) {
121
+ this.sourceStop = 'runtime_fragment_limit';
122
+ throw new AgentConversationExecutionError(this.sourceStop);
123
+ }
124
+ this.processedFragments++;
125
+ this.assertActive();
126
+ }
127
+ checkSourceLineCount(count) {
128
+ this.assertActive();
129
+ if (count > this.policy.maxSourceLines) {
130
+ this.sourceStop = 'runtime_source_lines_limit';
131
+ throw new AgentConversationExecutionError(this.sourceStop);
132
+ }
133
+ }
134
+ recordSourceFact() {
135
+ this.assertActive();
136
+ if (this.sourceFacts >= this.policy.maxSourceFacts) {
137
+ this.sourceStop = 'runtime_source_facts_limit';
138
+ throw new AgentConversationExecutionError(this.sourceStop);
139
+ }
140
+ this.sourceFacts++;
141
+ }
142
+ acceptSourceText(content) {
143
+ this.assertActive();
144
+ const bytes = Buffer.byteLength(content, 'utf8');
145
+ this.assertSourceFits(bytes, bytes);
146
+ this.sourceBytes += bytes;
147
+ this.sourceReads++;
148
+ return content;
149
+ }
150
+ async readSourceFile(filePath) {
151
+ this.assertActive();
152
+ const file = await (0, promises_1.open)(filePath, 'r');
153
+ try {
154
+ this.assertActive();
155
+ const stat = await file.stat();
156
+ if (!stat.isFile())
157
+ return null;
158
+ this.assertSourceFits(stat.size, stat.size);
159
+ const chunks = [];
160
+ let fileBytes = 0;
161
+ const stream = file.createReadStream({ autoClose: false, highWaterMark: 64 * 1024, signal: this.signal });
162
+ try {
163
+ for await (const chunk of stream) {
164
+ this.assertActive();
165
+ const bytes = chunk;
166
+ fileBytes += bytes.length;
167
+ // Check actual bytes too: the file can grow after stat().
168
+ this.assertSourceFits(fileBytes, bytes.length);
169
+ this.sourceBytes += bytes.length;
170
+ chunks.push(bytes);
171
+ }
172
+ }
173
+ catch (error) {
174
+ this.assertActive();
175
+ throw error;
176
+ }
177
+ this.sourceReads++;
178
+ return Buffer.concat(chunks, fileBytes).toString('utf8');
179
+ }
180
+ finally {
181
+ await file.close();
182
+ }
183
+ }
184
+ diagnostics() {
185
+ return {
186
+ elapsedMs: Math.max(0, Date.now() - this.startedAtMs),
187
+ timeoutMs: this.timeoutMs,
188
+ maxSourceBytes: this.policy.maxSourceBytes,
189
+ maxTotalSourceBytes: this.policy.maxTotalSourceBytes,
190
+ sourceBytes: this.sourceBytes,
191
+ sourceReads: this.sourceReads,
192
+ processedFragments: this.processedFragments,
193
+ maxSourceLines: this.policy.maxSourceLines,
194
+ maxSourceFacts: this.policy.maxSourceFacts,
195
+ sourceFacts: this.sourceFacts,
196
+ ...(this.sourceStop ? { truncationReason: this.sourceStop } : {}),
197
+ };
198
+ }
199
+ assertSourceFits(fileBytes, additionalBytes) {
200
+ if (fileBytes > this.policy.maxSourceBytes) {
201
+ this.sourceStop = 'runtime_source_bytes_limit';
202
+ throw new AgentConversationExecutionError(this.sourceStop);
203
+ }
204
+ if (this.sourceBytes + additionalBytes > this.policy.maxTotalSourceBytes) {
205
+ this.sourceStop = 'runtime_total_source_bytes_limit';
206
+ throw new AgentConversationExecutionError(this.sourceStop);
207
+ }
208
+ }
209
+ }
210
+ exports.AgentConversationExecution = AgentConversationExecution;
211
+ AgentConversationExecution.scope = new async_hooks_1.AsyncLocalStorage();
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const KnowledgeLearningPlatform_1 = require("./KnowledgeLearningPlatform");
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const agentConversationExecution_1 = require("./agentConversationExecution");
11
+ const agentResponseBudget_1 = require("./agentResponseBudget");
12
+ describe('agent conversation execution limits', () => {
13
+ const document = {
14
+ documentId: 'bounded-source',
15
+ sourcePath: 'bounded/source.md',
16
+ content: '# Bounded Source\n\nA bounded source explains safe execution and complete evidence. '.repeat(24),
17
+ };
18
+ test('client capability hints cannot exceed the declared host tier', async () => {
19
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
20
+ autoPersist: false,
21
+ responseBudgetCapability: { memoryClass: 'low', workload: 'normal' },
22
+ });
23
+ await platform.ingestKnowledge({ documents: [document] });
24
+ const response = await platform.agentConversation({
25
+ message: 'Explain bounded source', responseMode: 'full', persistMemory: false,
26
+ responseBudgetCapability: { memoryClass: 'high', workload: 'max' },
27
+ });
28
+ expect(response.responseBudget?.tier).toBe('standard');
29
+ });
30
+ test('declines oversized sources without claiming a complete source scan', async () => {
31
+ const options = {
32
+ autoPersist: false,
33
+ responseExecutionPolicy: { maxSourceBytes: 96, maxTotalSourceBytes: 128 },
34
+ };
35
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform(options);
36
+ await platform.ingestKnowledge({ documents: [document] });
37
+ const response = await platform.agentConversation({
38
+ message: 'Explain bounded source', responseMode: 'full', persistMemory: false,
39
+ });
40
+ expect(response.trace.ragContextPack?.sourceDecisions).toEqual(expect.arrayContaining([
41
+ expect.objectContaining({ status: 'source_window_unavailable', reason: 'runtime_source_bytes_limit' }),
42
+ ]));
43
+ expect(response.summary.responseTruncated).toBe(true);
44
+ expect(response.trace.ragSufficiencyReview?.status).not.toBe('sufficient');
45
+ });
46
+ test('expired query work cannot progress to memory or artifact writes', async () => {
47
+ const backend = {
48
+ id: 'delayed-query',
49
+ query: async (context) => {
50
+ await new Promise(resolve => setTimeout(resolve, 25));
51
+ return { candidates: context.atoms.map(atom => ({ atomId: atom.id, score: 1 })) };
52
+ },
53
+ };
54
+ const options = { autoPersist: false, graphQueryBackend: backend, responseExecutionPolicy: { timeoutMs: 5 } };
55
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform(options);
56
+ await platform.ingestKnowledge({ documents: [document] });
57
+ await expect(platform.agentConversation({
58
+ userId: 'deadline-user', message: 'Explain bounded source', persistMemory: true,
59
+ })).rejects.toEqual(expect.objectContaining({ code: 'runtime_timeout' }));
60
+ const memory = await platform.listConversationMemory({ userId: 'deadline-user' });
61
+ expect(memory.entries).toHaveLength(0);
62
+ expect((await platform.addConversationMemory({ userId: 'deadline-user', content: 'next operation' })).added).toBe(true);
63
+ });
64
+ test('bounds admitted turns before they enter the serialized state queue', async () => {
65
+ let release;
66
+ const held = new Promise(resolve => { release = resolve; });
67
+ const backend = {
68
+ id: 'held-query',
69
+ query: async (context) => {
70
+ await held;
71
+ return { candidates: context.atoms.map(atom => ({ atomId: atom.id, score: 1 })) };
72
+ },
73
+ };
74
+ const options = { autoPersist: false, graphQueryBackend: backend, responseExecutionPolicy: { maxPendingTurns: 2 } };
75
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform(options);
76
+ await platform.ingestKnowledge({ documents: [document] });
77
+ const first = platform.agentConversation({ message: 'First', persistMemory: false });
78
+ const second = platform.agentConversation({ message: 'Second', persistMemory: false });
79
+ const rejected = platform.agentConversation({ message: 'Third', persistMemory: false }).catch(error => error);
80
+ await new Promise(resolve => setImmediate(resolve));
81
+ release();
82
+ await Promise.all([first, second]);
83
+ expect((await rejected).code).toBe('runtime_turn_capacity');
84
+ });
85
+ test('cancels cooperative query I/O without retrying or writing memory', async () => {
86
+ const controller = new AbortController();
87
+ let entered;
88
+ const started = new Promise(resolve => { entered = resolve; });
89
+ let queries = 0;
90
+ const backend = {
91
+ id: 'cancellable-query',
92
+ query: async (context) => {
93
+ queries++;
94
+ const signal = context.signal;
95
+ entered();
96
+ await new Promise((_resolve, reject) => {
97
+ if (signal.aborted)
98
+ reject(signal.reason);
99
+ else
100
+ signal.addEventListener('abort', () => reject(signal.reason), { once: true });
101
+ });
102
+ return { candidates: [] };
103
+ },
104
+ };
105
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({ autoPersist: false, graphQueryBackend: backend });
106
+ await platform.ingestKnowledge({ documents: [document] });
107
+ const turn = platform.agentConversation({ userId: 'cancelled-user', message: 'Bounded source' }, controller.signal)
108
+ .catch(error => error);
109
+ await started;
110
+ controller.abort();
111
+ expect((await turn).code).toBe('runtime_cancelled');
112
+ expect(queries).toBe(1);
113
+ expect((await platform.listConversationMemory({ userId: 'cancelled-user' })).entries).toHaveLength(0);
114
+ });
115
+ test('rejects dense source structure while preserving direct evidence', async () => {
116
+ const options = { autoPersist: false, responseExecutionPolicy: { maxSourceLines: 4 } };
117
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform(options);
118
+ await platform.ingestKnowledge({ documents: [document] });
119
+ const response = await platform.agentConversation({ message: 'Explain bounded source', persistMemory: false });
120
+ expect(response.trace.ragContextPack?.sourceDecisions.some(item => item.reason === 'runtime_source_lines_limit')).toBe(true);
121
+ expect(response.trace.ragSufficiencyReview?.status).not.toBe('sufficient');
122
+ expect(response.citations.length).toBeGreaterThan(0);
123
+ });
124
+ test('keeps the default tier conservative when only the host ceiling is configured', async () => {
125
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
126
+ autoPersist: false,
127
+ responseBudgetHostCapability: { memoryClass: 'high', workload: 'max' },
128
+ });
129
+ await platform.ingestKnowledge({ documents: [document] });
130
+ const request = { message: 'Explain bounded source', responseMode: 'full', persistMemory: false };
131
+ expect((await platform.agentConversation(request)).responseBudget?.tier).toBe('standard');
132
+ expect((await platform.agentConversation({
133
+ ...request, responseBudgetCapability: { memoryClass: 'high', workload: 'max' },
134
+ })).responseBudget?.tier).toBe('max');
135
+ });
136
+ test('a cancelled queued turn never reaches query or persistence', async () => {
137
+ let release;
138
+ let entered;
139
+ const started = new Promise(resolve => { entered = resolve; });
140
+ const held = new Promise(resolve => { release = resolve; });
141
+ const query = jest.fn(async () => { entered(); await held; return { candidates: [] }; });
142
+ const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({ autoPersist: false, graphQueryBackend: { id: 'held', query } });
143
+ const first = platform.agentConversation({ message: 'First', persistMemory: false });
144
+ await started;
145
+ const controller = new AbortController();
146
+ const second = platform.agentConversation({ userId: 'queued', message: 'Second' }, controller.signal).catch(error => error);
147
+ controller.abort();
148
+ release();
149
+ await first;
150
+ expect((await second).code).toBe('runtime_cancelled');
151
+ expect(query).toHaveBeenCalledTimes(1);
152
+ expect((await platform.listConversationMemory({ userId: 'queued' })).entries).toHaveLength(0);
153
+ });
154
+ });
155
+ describe('bounded source file I/O', () => {
156
+ let directory;
157
+ beforeEach(async () => { directory = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'source-budget-')); });
158
+ afterEach(async () => {
159
+ jest.restoreAllMocks();
160
+ await promises_1.default.rm(directory, { recursive: true, force: true });
161
+ });
162
+ test('counts UTF-8 bytes at the exact file limit and charges cumulative reads', async () => {
163
+ const filePath = path_1.default.join(directory, 'unicode.md');
164
+ const source = '知识🧠';
165
+ const bytes = Buffer.byteLength(source);
166
+ await promises_1.default.writeFile(filePath, source);
167
+ await agentConversationExecution_1.AgentConversationExecution.run({
168
+ budget: (0, agentResponseBudget_1.resolveAgentResponseBudget)(),
169
+ policy: (0, agentConversationExecution_1.normalizeAgentConversationExecutionPolicy)({ maxSourceBytes: bytes, maxTotalSourceBytes: bytes }),
170
+ }, async (execution) => {
171
+ expect(await execution.readSourceFile(filePath)).toBe(source);
172
+ expect(execution.diagnostics().sourceBytes).toBe(bytes);
173
+ await expect(execution.readSourceFile(filePath)).rejects.toHaveProperty('code', 'runtime_total_source_bytes_limit');
174
+ });
175
+ });
176
+ test('bounds actual bytes when a source grows after stat and closes the descriptor', async () => {
177
+ const filePath = path_1.default.join(directory, 'growing.md');
178
+ await promises_1.default.writeFile(filePath, 'small');
179
+ const file = await promises_1.default.open(filePath, 'r');
180
+ const oldStat = await file.stat();
181
+ await promises_1.default.appendFile(filePath, 'x'.repeat(128 * 1024));
182
+ jest.spyOn(file, 'stat').mockResolvedValueOnce(oldStat);
183
+ const close = jest.spyOn(file, 'close');
184
+ jest.spyOn(promises_1.default, 'open').mockResolvedValueOnce(file);
185
+ await expect(agentConversationExecution_1.AgentConversationExecution.run({
186
+ budget: (0, agentResponseBudget_1.resolveAgentResponseBudget)(),
187
+ policy: (0, agentConversationExecution_1.normalizeAgentConversationExecutionPolicy)({ maxSourceBytes: 16 }),
188
+ }, execution => execution.readSourceFile(filePath))).rejects.toHaveProperty('code', 'runtime_source_bytes_limit');
189
+ expect(close).toHaveBeenCalled();
190
+ await expect(file.stat()).rejects.toHaveProperty('code', 'EBADF');
191
+ });
192
+ test('aborts a source stream and closes its descriptor', async () => {
193
+ const filePath = path_1.default.join(directory, 'cancelled.md');
194
+ await promises_1.default.writeFile(filePath, Buffer.alloc(256 * 1024, 65));
195
+ const file = await promises_1.default.open(filePath, 'r');
196
+ const controller = new AbortController();
197
+ const createReadStream = file.createReadStream.bind(file);
198
+ jest.spyOn(file, 'createReadStream').mockImplementation(options => {
199
+ const stream = createReadStream(options);
200
+ stream.once('data', () => controller.abort());
201
+ return stream;
202
+ });
203
+ jest.spyOn(promises_1.default, 'open').mockResolvedValueOnce(file);
204
+ await expect(agentConversationExecution_1.AgentConversationExecution.run({
205
+ budget: (0, agentResponseBudget_1.resolveAgentResponseBudget)(), policy: (0, agentConversationExecution_1.normalizeAgentConversationExecutionPolicy)(), signal: controller.signal,
206
+ }, execution => execution.readSourceFile(filePath))).rejects.toHaveProperty('code', 'runtime_cancelled');
207
+ await expect(file.stat()).rejects.toHaveProperty('code', 'EBADF');
208
+ });
209
+ });