noteconnection 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -21
- package/README.md +260 -1626
- package/dist/src/agent_workspace.contract.parity.test.js +159 -0
- package/dist/src/agent_workspace.frontend.test.js +10388 -0
- package/dist/src/agent_workspace.locale.contract.test.js +95 -0
- package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
- package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
- package/dist/src/appimage.patchelf.test.js +113 -0
- package/dist/src/appimage.portability.test.js +156 -0
- package/dist/src/backend/FileLoader.js +18 -4
- package/dist/src/backend/FileLoader.test.js +125 -0
- package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
- package/dist/src/backend/GraphBuilder.js +66 -18
- package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
- package/dist/src/backend/GraphMetrics.js +0 -3
- package/dist/src/backend/ResourceIdentity.js +155 -0
- package/dist/src/backend/ResourceIdentity.test.js +113 -0
- package/dist/src/backend/algorithms/CycleDetection.js +0 -1
- package/dist/src/backend/algorithms/HybridEngine.js +0 -3
- package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
- package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
- package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
- package/dist/src/backend/build_dag.js +0 -1
- package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
- package/dist/src/capacitor.runtime.contract.test.js +11 -2
- package/dist/src/copy.assets.contract.test.js +130 -0
- package/dist/src/core/Graph.js +138 -42
- package/dist/src/core/Graph.test.js +74 -0
- package/dist/src/core/NoteConnection.js +2 -2
- package/dist/src/core/PathBridge.js +293 -5
- package/dist/src/core/ResourceReference.js +17 -0
- package/dist/src/desktop.godot.pack.test.js +142 -0
- package/dist/src/desktop.runtime.graph.test.js +66 -0
- package/dist/src/export/WorkspaceExportBundle.js +1174 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +1541 -0
- package/dist/src/export/index.js +18 -0
- package/dist/src/export/types.js +2 -0
- package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
- package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
- package/dist/src/foundation.release.evidence.contract.test.js +524 -0
- package/dist/src/foundation.sqlite.runtime.contract.test.js +88 -0
- package/dist/src/frontend/README.md +260 -1626
- package/dist/src/frontend/agent_workspace.js +6011 -0
- package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
- package/dist/src/frontend/analysis.js +31 -4
- package/dist/src/frontend/app.js +2178 -62
- package/dist/src/frontend/focus_mode_interactions.js +33 -0
- package/dist/src/frontend/godot_future_path_renderer.js +540 -0
- package/dist/src/frontend/godot_tree_interactions.js +178 -0
- package/dist/src/frontend/graph_state.mjs +105 -0
- package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
- package/dist/src/frontend/i18n.mjs +186 -0
- package/dist/src/frontend/index.html +672 -268
- package/dist/src/frontend/knowledge_projection_contract.js +230 -0
- package/dist/src/frontend/knowledge_projection_store.js +214 -0
- package/dist/src/frontend/layout_gpu.js +12 -4
- package/dist/src/frontend/libs/path_core.js +158 -42
- package/dist/src/frontend/locales/en.json +753 -5
- package/dist/src/frontend/locales/zh.json +753 -5
- package/dist/src/frontend/main.mjs +60 -0
- package/dist/src/frontend/markdown_runtime.js +827 -0
- package/dist/src/frontend/mobile_budget_runtime.js +33 -0
- package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
- package/dist/src/frontend/mobile_identity_contract.js +221 -0
- package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
- package/dist/src/frontend/notemd.css +49 -0
- package/dist/src/frontend/notemd.html +7 -1
- package/dist/src/frontend/notemd.js +64 -0
- package/dist/src/frontend/path.html +113 -2
- package/dist/src/frontend/path_app.js +2108 -130
- package/dist/src/frontend/path_layout.mjs +143 -0
- package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
- package/dist/src/frontend/path_modules_bridge.js +486 -0
- package/dist/src/frontend/path_state.mjs +118 -0
- package/dist/src/frontend/path_styles.css +146 -0
- package/dist/src/frontend/path_worker_bridge.mjs +85 -0
- package/dist/src/frontend/reader.js +393 -33
- package/dist/src/frontend/runtime_bridge.js +67 -54
- package/dist/src/frontend/runtime_bridge.mjs +279 -0
- package/dist/src/frontend/settings.js +130 -12
- package/dist/src/frontend/simulationWorker.js +1 -1
- package/dist/src/frontend/source_manager.js +297 -22
- package/dist/src/frontend/storage_provider.js +661 -71
- package/dist/src/frontend/styles.css +2814 -72
- package/dist/src/frontend/workbench_state.mjs +101 -0
- package/dist/src/frontend/workspace_panes.js +10272 -0
- package/dist/src/frontend.locale.contract.test.js +62 -0
- package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
- package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
- package/dist/src/graph.answer.operator.contract.test.js +72 -0
- package/dist/src/index.js +6 -2
- package/dist/src/index.knowledge-root.test.js +118 -0
- package/dist/src/indexing/IndexLifecycle.js +217 -0
- package/dist/src/indexing/IndexLifecycle.test.js +73 -0
- package/dist/src/indexing/SegmentBuilder.js +64 -0
- package/dist/src/indexing/UnitBuilder.js +48 -0
- package/dist/src/indexing/types.js +2 -0
- package/dist/src/knowledge.api.contract.test.js +172 -0
- package/dist/src/knowledge_projection.contract.test.js +134 -0
- package/dist/src/knowledge_projection_store.test.js +200 -0
- package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
- package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
- package/dist/src/learning/KnowledgeLearningPlatform.js +11181 -0
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +745 -0
- package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +3208 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2998 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3936 -0
- package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
- package/dist/src/learning/PublicAnswerContract.test.js +85 -0
- package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
- package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
- package/dist/src/learning/agentConversationExecution.js +211 -0
- package/dist/src/learning/agentConversationExecution.test.js +209 -0
- package/dist/src/learning/agentConversationSerialization.js +266 -0
- package/dist/src/learning/agentConversationSerialization.test.js +165 -0
- package/dist/src/learning/agentResponseBudget.js +179 -0
- package/dist/src/learning/agentResponseBudget.test.js +97 -0
- package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
- package/dist/src/learning/answerReleaseReview.js +5260 -0
- package/dist/src/learning/answerReleaseReview.test.js +4174 -0
- package/dist/src/learning/answerTaskPlan.js +473 -0
- package/dist/src/learning/api.js +2 -0
- package/dist/src/learning/conversationComposer.js +1721 -0
- package/dist/src/learning/conversationComposer.test.js +3220 -0
- package/dist/src/learning/domains/ConversationManager.js +53 -0
- package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
- package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
- package/dist/src/learning/domains/MasteryEngine.js +387 -0
- package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
- package/dist/src/learning/domains/QualityEvaluator.js +307 -0
- package/dist/src/learning/domains/TutorRouter.js +313 -0
- package/dist/src/learning/domains/index.js +33 -0
- package/dist/src/learning/domains/types.js +7 -0
- package/dist/src/learning/errors.js +29 -0
- package/dist/src/learning/evidenceContextAssembler.js +1368 -0
- package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
- package/dist/src/learning/graphAnswerCoverage.js +37 -0
- package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
- package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
- package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
- package/dist/src/learning/graphAnswerFacts.js +94 -0
- package/dist/src/learning/graphAnswerFacts.test.js +39 -0
- package/dist/src/learning/graphAnswerPlan.js +908 -0
- package/dist/src/learning/graphAnswerPlan.test.js +907 -0
- package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
- package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
- package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
- package/dist/src/learning/graphClaimMatcher.js +104 -0
- package/dist/src/learning/graphConditionedContext.js +109 -0
- package/dist/src/learning/graphConditionedContext.test.js +99 -0
- package/dist/src/learning/graphContextAssembler.js +860 -0
- package/dist/src/learning/graphContextAssembler.test.js +1033 -0
- package/dist/src/learning/graphExpansionPolicy.js +22 -0
- package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
- package/dist/src/learning/identityMetadata.test.js +26 -0
- package/dist/src/learning/index.js +29 -0
- package/dist/src/learning/mobileAnswerProjection.js +104 -0
- package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
- package/dist/src/learning/queryBackend.js +1922 -0
- package/dist/src/learning/queryBackend.test.js +976 -0
- package/dist/src/learning/ragContextPack.js +356 -0
- package/dist/src/learning/ragContextPack.test.js +311 -0
- package/dist/src/learning/ragEvidenceQuality.js +96 -0
- package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
- package/dist/src/learning/ragPublicText.js +101 -0
- package/dist/src/learning/ragPublicText.test.js +57 -0
- package/dist/src/learning/ragSufficiencyJudge.js +171 -0
- package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.js +217 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +207 -0
- package/dist/src/learning/requestNormalization.js +304 -0
- package/dist/src/learning/requestNormalization.test.js +101 -0
- package/dist/src/learning/runtimeCapability.js +4677 -0
- package/dist/src/learning/runtimeCapability.test.js +3635 -0
- package/dist/src/learning/store.js +1344 -0
- package/dist/src/learning/store.test.js +1425 -0
- package/dist/src/learning/tutorAdapter.js +2 -0
- package/dist/src/learning/types.js +2 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +958 -0
- package/dist/src/learning/vectorAccelerationAdapter.test.js +412 -0
- package/dist/src/learning/workspaceHydration.js +168 -0
- package/dist/src/learning/workspaceHydration.test.js +30 -0
- package/dist/src/lfs.asset.policy.contract.test.js +153 -0
- package/dist/src/license.policy.contract.test.js +71 -0
- package/dist/src/markdown/MarkdownGateway.js +4 -0
- package/dist/src/markdown/MarkdownGateway.test.js +59 -0
- package/dist/src/memory/MemoryGovernance.js +74 -0
- package/dist/src/memory/MemoryGovernance.test.js +46 -0
- package/dist/src/memory/types.js +2 -0
- package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
- package/dist/src/middleware/SseResponseWriter.js +122 -0
- package/dist/src/middleware/SseResponseWriter.test.js +54 -0
- package/dist/src/middleware/auth.js +38 -0
- package/dist/src/middleware/auth.test.js +33 -0
- package/dist/src/middleware/body-parser.js +45 -0
- package/dist/src/middleware/cors.js +44 -0
- package/dist/src/middleware/index.js +22 -0
- package/dist/src/middleware/request-trace.js +96 -0
- package/dist/src/mobile.android.evidence.contract.test.js +146 -0
- package/dist/src/mobile.android.signing.contract.test.js +66 -0
- package/dist/src/mobile.artifact.contract.test.js +144 -0
- package/dist/src/mobile.budget.contract.test.js +90 -0
- package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
- package/dist/src/mobile.graph.parity.contract.test.js +207 -0
- package/dist/src/mobile.identity.contract.test.js +66 -0
- package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
- package/dist/src/mobile.identity.corpus.test.js +40 -0
- package/dist/src/mobile.native.recovery.contract.test.js +64 -0
- package/dist/src/mobile.pipeline.test.js +11 -1
- package/dist/src/mobile.profile.contract.test.js +225 -0
- package/dist/src/mobile.semantic.parity.test.js +92 -0
- package/dist/src/mobile_exact_analyzer.test.js +113 -0
- package/dist/src/notemd/AppConfigToml.js +6 -4
- package/dist/src/notemd/ContentGenerator.js +4 -9
- package/dist/src/notemd/FileProcessor.js +4 -9
- package/dist/src/notemd/MermaidProcessor.js +400 -50
- package/dist/src/notemd/NotemdService.js +506 -26
- package/dist/src/notemd/PromptManager.js +15 -0
- package/dist/src/notemd/Translator.js +4 -9
- package/dist/src/notemd/cli/commands.js +357 -0
- package/dist/src/notemd/cli/dispatcher.js +225 -0
- package/dist/src/notemd/cli/index.js +169 -0
- package/dist/src/notemd/cli/parser.js +68 -0
- package/dist/src/notemd/cli/types.js +2 -0
- package/dist/src/notemd/constants.js +43 -0
- package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
- package/dist/src/notemd/diagram/diagramSpec.js +79 -0
- package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
- package/dist/src/notemd/diagram/intent.js +95 -0
- package/dist/src/notemd/diagram/planner.js +71 -0
- package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
- package/dist/src/notemd/diagram/types.js +18 -0
- package/dist/src/notemd/index.js +26 -0
- package/dist/src/notemd/operations/capabilityManifest.js +23 -0
- package/dist/src/notemd/operations/cliContracts.js +17 -0
- package/dist/src/notemd/operations/configProfileCommands.js +85 -0
- package/dist/src/notemd/operations/registry.contract.test.js +95 -0
- package/dist/src/notemd/operations/registry.js +991 -0
- package/dist/src/notemd/operations/types.js +2 -0
- package/dist/src/notemd/progressReporter.js +9 -0
- package/dist/src/notemd/providerDiagnostics.js +220 -0
- package/dist/src/notemd/providerProfiles.js +42 -0
- package/dist/src/notemd/providerTemplates.js +231 -0
- package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
- package/dist/src/notemd/search/SearchManager.js +13 -0
- package/dist/src/notemd/search/SearchProvider.js +2 -0
- package/dist/src/notemd/search/TavilyProvider.js +44 -0
- package/dist/src/notemd.agent.manifest.test.js +85 -0
- package/dist/src/notemd.api.contract.test.js +14 -1
- package/dist/src/notemd.app_config_toml.test.js +2 -1
- package/dist/src/notemd.batch.workflow.test.js +117 -0
- package/dist/src/notemd.cli.e2e.test.js +136 -0
- package/dist/src/notemd.core.test.js +51 -0
- package/dist/src/notemd.diagram.pipeline.test.js +233 -0
- package/dist/src/notemd.providerTemplates.test.js +34 -0
- package/dist/src/notemd.server.integration.test.js +262 -39
- package/dist/src/notemd.workflow.pipeline.test.js +162 -0
- package/dist/src/pathbridge.handshake.contract.test.js +45 -2
- package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
- package/dist/src/pathcore.bundle.contract.test.js +8 -0
- package/dist/src/pathmode.background.contract.test.js +69 -0
- package/dist/src/pathmode.settings.api.contract.test.js +9 -0
- package/dist/src/pkg.sidecar.contract.test.js +9 -3
- package/dist/src/platform/ExportProfile.js +86 -0
- package/dist/src/platform/MobileBudget.js +11 -0
- package/dist/src/platform/PlatformCapabilities.js +59 -0
- package/dist/src/platform/PlatformCapabilities.test.js +36 -0
- package/dist/src/platform/RenderMaterializer.js +33 -0
- package/dist/src/platform/RenderMaterializer.test.js +32 -0
- package/dist/src/query_backend.external_http.integration.test.js +410 -0
- package/dist/src/reader_renderer.js +404 -3
- package/dist/src/reader_renderer.test.js +87 -0
- package/dist/src/release.godot.mirror.contract.test.js +97 -0
- package/dist/src/resources/ResourceRegistry.js +263 -0
- package/dist/src/resources/ResourceRegistry.test.js +97 -0
- package/dist/src/resources/types.js +2 -0
- package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
- package/dist/src/routes/index.js +19 -0
- package/dist/src/routes/knowledge.js +981 -0
- package/dist/src/routes/knowledgePayload.js +185 -0
- package/dist/src/routes/knowledgePayload.test.js +53 -0
- package/dist/src/routes/markdown.js +295 -0
- package/dist/src/routes/notemd.js +1321 -0
- package/dist/src/routes/registry.contract.test.js +133 -0
- package/dist/src/routes/registry.shadow.contract.test.js +22 -0
- package/dist/src/routes/render.js +248 -0
- package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
- package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
- package/dist/src/routes/settings.js +6 -0
- package/dist/src/routes/staticFiles.js +94 -0
- package/dist/src/routes/types.js +2 -0
- package/dist/src/runtime.capabilities.test.js +18 -2
- package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
- package/dist/src/server.js +10342 -1325
- package/dist/src/server.migration.test.js +584 -10
- package/dist/src/server.port.fallback.contract.test.js +63 -0
- package/dist/src/session/SessionStateStore.js +81 -0
- package/dist/src/session/SessionStateStore.test.js +58 -0
- package/dist/src/session/types.js +2 -0
- package/dist/src/settings.runtime.contract.test.js +50 -0
- package/dist/src/shared/types.contract.test.js +107 -0
- package/dist/src/shared/types.js +22 -0
- package/dist/src/sidecar.build.lock.contract.test.js +201 -0
- package/dist/src/sidecar.freshness.contract.test.js +184 -0
- package/dist/src/sidecar.replacement.boundary.contract.test.js +131 -0
- package/dist/src/sidecar.supply.readiness.contract.test.js +155 -0
- package/dist/src/simulation.worker.offline.test.js +104 -0
- package/dist/src/source_manager.loadflow.test.js +46 -0
- package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
- package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
- package/dist/src/storage.provider.contract.test.js +152 -1
- package/dist/src/tauri.frontend.build.contract.test.js +68 -0
- package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
- package/dist/src/tauri.window.evidence.execution.test.js +68 -0
- package/dist/src/utils/RuntimePaths.js +10 -13
- package/dist/src/utils/RuntimePaths.test.js +27 -0
- package/dist/src/utils/platform.js +153 -0
- package/dist/src/windows.installer.build.test.js +109 -0
- package/dist/src/windows.installer.payload.test.js +76 -0
- package/dist/src/windows.installer.runtime.test.js +69 -0
- package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
- package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
- package/dist/src/workflows/types.js +2 -0
- package/dist/src/workspace/WorkspaceRegistry.js +135 -0
- package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
- package/dist/src/workspace/types.js +2 -0
- package/package.json +80 -15
|
@@ -102,7 +102,7 @@ function deferred() {
|
|
|
102
102
|
});
|
|
103
103
|
return { promise, resolve };
|
|
104
104
|
}
|
|
105
|
-
function requestJson(port, method, requestPath, body) {
|
|
105
|
+
function requestJson(port, method, requestPath, body, extraHeaders) {
|
|
106
106
|
return new Promise((resolve, reject) => {
|
|
107
107
|
const payload = typeof body === 'undefined' ? undefined : JSON.stringify(body);
|
|
108
108
|
const req = http.request({
|
|
@@ -113,9 +113,10 @@ function requestJson(port, method, requestPath, body) {
|
|
|
113
113
|
headers: payload
|
|
114
114
|
? {
|
|
115
115
|
'Content-Type': 'application/json',
|
|
116
|
-
'Content-Length': Buffer.byteLength(payload)
|
|
116
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
117
|
+
...(extraHeaders || {})
|
|
117
118
|
}
|
|
118
|
-
: undefined
|
|
119
|
+
: (extraHeaders || undefined)
|
|
119
120
|
}, (res) => {
|
|
120
121
|
let text = '';
|
|
121
122
|
res.setEncoding('utf8');
|
|
@@ -134,7 +135,8 @@ function requestJson(port, method, requestPath, body) {
|
|
|
134
135
|
}
|
|
135
136
|
resolve({
|
|
136
137
|
status: res.statusCode || 0,
|
|
137
|
-
body: parsed
|
|
138
|
+
body: parsed,
|
|
139
|
+
headers: res.headers
|
|
138
140
|
});
|
|
139
141
|
});
|
|
140
142
|
});
|
|
@@ -178,7 +180,8 @@ function requestRaw(port, method, requestPath, payload, extraHeaders) {
|
|
|
178
180
|
}
|
|
179
181
|
resolve({
|
|
180
182
|
status: res.statusCode || 0,
|
|
181
|
-
body: parsed
|
|
183
|
+
body: parsed,
|
|
184
|
+
headers: res.headers
|
|
182
185
|
});
|
|
183
186
|
});
|
|
184
187
|
});
|
|
@@ -217,10 +220,18 @@ function requestBinary(port, method, requestPath, payload, extraHeaders) {
|
|
|
217
220
|
parsed = text;
|
|
218
221
|
}
|
|
219
222
|
}
|
|
220
|
-
|
|
223
|
+
const response = {
|
|
221
224
|
status: res.statusCode || 0,
|
|
222
|
-
body: parsed
|
|
223
|
-
|
|
225
|
+
body: parsed,
|
|
226
|
+
headers: res.headers
|
|
227
|
+
};
|
|
228
|
+
// A 413 response can precede upload completion; teardown must wait until the request finishes writing.
|
|
229
|
+
if (req.writableFinished) {
|
|
230
|
+
resolve(response);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
req.once('finish', () => resolve(response));
|
|
234
|
+
}
|
|
224
235
|
});
|
|
225
236
|
});
|
|
226
237
|
req.on('error', reject);
|
|
@@ -243,7 +254,10 @@ describe('server migration settings routes', () => {
|
|
|
243
254
|
let buildGraphMock;
|
|
244
255
|
let renderMathPngMock;
|
|
245
256
|
let renderMermaidPngMock;
|
|
257
|
+
let collectMermaidRenderStageSnapshotsMock;
|
|
258
|
+
let normalizeMermaidDefinitionMock;
|
|
246
259
|
let copyPngToClipboardMock;
|
|
260
|
+
let requestFrontendMermaidRenderMock;
|
|
247
261
|
let originalArgv;
|
|
248
262
|
beforeAll(async () => {
|
|
249
263
|
temp = new TempDir('noteconnection-server');
|
|
@@ -284,7 +298,32 @@ describe('server migration settings routes', () => {
|
|
|
284
298
|
width: 640,
|
|
285
299
|
height: 360
|
|
286
300
|
});
|
|
301
|
+
collectMermaidRenderStageSnapshotsMock = jest.fn().mockResolvedValue([
|
|
302
|
+
{
|
|
303
|
+
stage: 'raw',
|
|
304
|
+
svg: '<svg xmlns="http://www.w3.org/2000/svg"></svg>',
|
|
305
|
+
pngBase64: 'raw-stage-png-base64',
|
|
306
|
+
width: 640,
|
|
307
|
+
height: 360
|
|
308
|
+
}
|
|
309
|
+
]);
|
|
310
|
+
normalizeMermaidDefinitionMock = jest.fn((source) => source);
|
|
287
311
|
copyPngToClipboardMock = jest.fn().mockResolvedValue(undefined);
|
|
312
|
+
requestFrontendMermaidRenderMock = jest.fn().mockResolvedValue({
|
|
313
|
+
pngBase64: 'frontend-bridge-png-base64',
|
|
314
|
+
svg: '<svg xmlns="http://www.w3.org/2000/svg"><text>frontend</text></svg>',
|
|
315
|
+
width: 800,
|
|
316
|
+
height: 480,
|
|
317
|
+
renderer: 'frontend-bridge',
|
|
318
|
+
stages: [
|
|
319
|
+
{
|
|
320
|
+
stage: 'final',
|
|
321
|
+
svg: '<svg xmlns="http://www.w3.org/2000/svg"><text>frontend</text></svg>',
|
|
322
|
+
width: 800,
|
|
323
|
+
height: 480,
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
});
|
|
288
327
|
jest.resetModules();
|
|
289
328
|
originalArgv = [...process.argv];
|
|
290
329
|
process.argv = process.argv.slice(0, 2);
|
|
@@ -292,11 +331,15 @@ describe('server migration settings routes', () => {
|
|
|
292
331
|
buildGraph: buildGraphMock
|
|
293
332
|
}));
|
|
294
333
|
jest.doMock('./core/PathBridge', () => ({
|
|
295
|
-
PathBridge: jest.fn().mockImplementation(() => ({
|
|
334
|
+
PathBridge: jest.fn().mockImplementation(() => ({
|
|
335
|
+
requestFrontendMermaidRender: requestFrontendMermaidRenderMock
|
|
336
|
+
}))
|
|
296
337
|
}));
|
|
297
338
|
jest.doMock('./reader_renderer', () => ({
|
|
298
339
|
renderMathPng: renderMathPngMock,
|
|
299
|
-
renderMermaidPng: renderMermaidPngMock
|
|
340
|
+
renderMermaidPng: renderMermaidPngMock,
|
|
341
|
+
collectMermaidRenderStageSnapshots: collectMermaidRenderStageSnapshotsMock,
|
|
342
|
+
normalizeMermaidDefinition: normalizeMermaidDefinitionMock
|
|
300
343
|
}));
|
|
301
344
|
jest.doMock('./native_clipboard', () => ({
|
|
302
345
|
copyPngToClipboard: copyPngToClipboardMock
|
|
@@ -441,6 +484,13 @@ describe('server migration settings routes', () => {
|
|
|
441
484
|
expect(typeof response.body).toBe('string');
|
|
442
485
|
expect(response.body).toContain('const graphData');
|
|
443
486
|
});
|
|
487
|
+
test('returns current KB path when cache-busting query string is present', async () => {
|
|
488
|
+
const response = await requestJson(port, 'GET', '/api/kb-path?v=12345');
|
|
489
|
+
expect(response.status).toBe(200);
|
|
490
|
+
expect(response.body).toEqual(expect.objectContaining({
|
|
491
|
+
kbPath: kbRoot,
|
|
492
|
+
}));
|
|
493
|
+
});
|
|
444
494
|
test('rejects static traversal attempts with raw parent-segment path', async () => {
|
|
445
495
|
const response = await requestJson(port, 'GET', '/../../outside/sensitive.md');
|
|
446
496
|
expect(response.status).toBe(403);
|
|
@@ -511,6 +561,17 @@ describe('server migration settings routes', () => {
|
|
|
511
561
|
const firstResponse = await firstRequest;
|
|
512
562
|
expect(firstResponse.status).toBe(200);
|
|
513
563
|
});
|
|
564
|
+
test.each(['incremental', 'none', 'full'])('graph build applies the requested %s recompute contract', async (mode) => {
|
|
565
|
+
const response = await requestJson(port, 'POST', '/api/build', { target: 'financial', relationRecomputeMode: mode });
|
|
566
|
+
expect(response.status).toBe(200);
|
|
567
|
+
expect(response.body.sync.summary.resolvedRelationRecomputeMode).toBe(mode);
|
|
568
|
+
});
|
|
569
|
+
test('graph build rejects unsupported recompute before construction', async () => {
|
|
570
|
+
const calls = buildGraphMock.mock.calls.length;
|
|
571
|
+
const response = await requestJson(port, 'POST', '/api/build', { target: 'financial', relationRecomputeMode: 'auto' });
|
|
572
|
+
expect(response.status).toBe(400);
|
|
573
|
+
expect(buildGraphMock.mock.calls.length).toBe(calls);
|
|
574
|
+
});
|
|
514
575
|
test('returns 413 when /api/build request body exceeds size limit', async () => {
|
|
515
576
|
const oversizedPayload = {
|
|
516
577
|
target: 'financial',
|
|
@@ -546,6 +607,519 @@ describe('server migration settings routes', () => {
|
|
|
546
607
|
expect(response.body.svg).toBeUndefined();
|
|
547
608
|
expect(renderMermaidPngMock).toHaveBeenCalled();
|
|
548
609
|
});
|
|
610
|
+
test('prefers frontend bridge for /api/render/mermaid when renderer is auto and a frontend bridge is available', async () => {
|
|
611
|
+
requestFrontendMermaidRenderMock.mockClear();
|
|
612
|
+
renderMermaidPngMock.mockClear();
|
|
613
|
+
const response = await requestJson(port, 'POST', '/api/render/mermaid', {
|
|
614
|
+
source: 'graph TD; A-->B',
|
|
615
|
+
renderer: 'auto'
|
|
616
|
+
});
|
|
617
|
+
expect(response.status).toBe(200);
|
|
618
|
+
expect(response.body).toEqual(expect.objectContaining({
|
|
619
|
+
pngBase64: 'frontend-bridge-png-base64',
|
|
620
|
+
width: 800,
|
|
621
|
+
height: 480,
|
|
622
|
+
renderer: 'frontend-bridge'
|
|
623
|
+
}));
|
|
624
|
+
expect(requestFrontendMermaidRenderMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
625
|
+
source: 'graph TD; A-->B',
|
|
626
|
+
theme: 'dark',
|
|
627
|
+
includeStages: false,
|
|
628
|
+
includeSvg: false
|
|
629
|
+
}));
|
|
630
|
+
expect(renderMermaidPngMock).not.toHaveBeenCalled();
|
|
631
|
+
});
|
|
632
|
+
test('suppresses svg for Godot export profile even when includeSvg is requested', async () => {
|
|
633
|
+
const response = await requestJson(port, 'POST', '/api/render/mermaid', {
|
|
634
|
+
source: 'graph TD; A-->B',
|
|
635
|
+
renderer: 'local',
|
|
636
|
+
includeSvg: true,
|
|
637
|
+
exportProfileId: 'godot-path-mode'
|
|
638
|
+
});
|
|
639
|
+
expect(response.status).toBe(200);
|
|
640
|
+
expect(response.body.svg).toBeUndefined();
|
|
641
|
+
expect(response.body.materialization).toEqual(expect.objectContaining({
|
|
642
|
+
exportProfileId: 'godot-path-mode',
|
|
643
|
+
vectorSuppressed: true,
|
|
644
|
+
responseArtifact: 'png'
|
|
645
|
+
}));
|
|
646
|
+
});
|
|
647
|
+
test('conversation route uses turn-cache inline flow with scoped grounded response and replay headers', async () => {
|
|
648
|
+
const ingestResponse = await requestJson(port, 'POST', '/api/knowledge/ingest', {
|
|
649
|
+
incremental: true,
|
|
650
|
+
documents: [
|
|
651
|
+
{
|
|
652
|
+
documentId: 'doc_server_conversation',
|
|
653
|
+
sourcePath: 'Knowledge_Base/optics/absorption.md',
|
|
654
|
+
language: 'zh',
|
|
655
|
+
content: '# 吸收\n吸收系数与光学衰减决定材料中的能量损失。'
|
|
656
|
+
}
|
|
657
|
+
]
|
|
658
|
+
});
|
|
659
|
+
expect(ingestResponse.status).toBe(200);
|
|
660
|
+
const turnId = 'turn_test_conversation_scope';
|
|
661
|
+
const firstResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', {
|
|
662
|
+
userId: 'server_conversation_user',
|
|
663
|
+
sessionId: 'server_session_scope',
|
|
664
|
+
activeTarget: 'optics',
|
|
665
|
+
message: '解释吸收系数和光学衰减',
|
|
666
|
+
answerLanguage: 'zh',
|
|
667
|
+
scope: {
|
|
668
|
+
corpusId: 'optics',
|
|
669
|
+
languages: ['zh']
|
|
670
|
+
},
|
|
671
|
+
persistMemory: true
|
|
672
|
+
}, {
|
|
673
|
+
'X-Agent-Conversation-Turn-Id': turnId
|
|
674
|
+
});
|
|
675
|
+
expect(firstResponse.status).toBe(200);
|
|
676
|
+
expect(firstResponse.headers?.['x-agent-conversation-turn-id']).toBe(turnId);
|
|
677
|
+
expect(firstResponse.headers?.['x-agent-conversation-replay']).toBe('miss');
|
|
678
|
+
expect(firstResponse.body.result.trace.usedScope).toEqual(expect.objectContaining({
|
|
679
|
+
corpusId: 'optics',
|
|
680
|
+
source: 'scoped'
|
|
681
|
+
}));
|
|
682
|
+
expect(firstResponse.body.result.citations.length).toBeGreaterThan(0);
|
|
683
|
+
expect(firstResponse.body.result.summary.appliedMemoryCount).toBeGreaterThan(0);
|
|
684
|
+
expect(firstResponse.body.result.assistantBlocks).toEqual(expect.arrayContaining([
|
|
685
|
+
expect.objectContaining({
|
|
686
|
+
type: 'structured_answer',
|
|
687
|
+
title: '可信回答',
|
|
688
|
+
nextActionsMarkdown: expect.stringContaining('## 下一步行动'),
|
|
689
|
+
}),
|
|
690
|
+
expect.objectContaining({ type: 'knowledge_run_summary', title: '知识运行' }),
|
|
691
|
+
expect.objectContaining({ type: 'citations', title: '引用' }),
|
|
692
|
+
expect.objectContaining({ type: 'knowledge_actions', title: '知识操作' }),
|
|
693
|
+
]));
|
|
694
|
+
const firstStructuredBlock = firstResponse.body.result.assistantBlocks.find((block) => block.type === 'structured_answer');
|
|
695
|
+
expect(String(firstStructuredBlock?.overviewMarkdown || '')).toContain('## 回答上下文');
|
|
696
|
+
expect(String(firstStructuredBlock?.explanationMarkdown || '')).toContain('## 说明');
|
|
697
|
+
expect(String(firstStructuredBlock?.evidenceMarkdown || '')).toContain('## 证据摘要');
|
|
698
|
+
expect(firstResponse.body.result.assistantBlocks).toEqual(expect.arrayContaining([
|
|
699
|
+
expect.objectContaining({ type: 'system_notice', text: expect.stringContaining('未召回') }),
|
|
700
|
+
]));
|
|
701
|
+
const replayResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', {
|
|
702
|
+
userId: 'server_conversation_user',
|
|
703
|
+
sessionId: 'server_session_scope',
|
|
704
|
+
activeTarget: 'optics',
|
|
705
|
+
message: '解释吸收系数和光学衰减',
|
|
706
|
+
answerLanguage: 'zh',
|
|
707
|
+
scope: {
|
|
708
|
+
corpusId: 'optics',
|
|
709
|
+
languages: ['zh']
|
|
710
|
+
},
|
|
711
|
+
persistMemory: true
|
|
712
|
+
}, {
|
|
713
|
+
'X-Agent-Conversation-Turn-Id': turnId
|
|
714
|
+
});
|
|
715
|
+
expect(replayResponse.status).toBe(200);
|
|
716
|
+
expect(replayResponse.headers?.['x-agent-conversation-replay']).toBe('hit');
|
|
717
|
+
expect(replayResponse.body.result.assistantBlocks).toEqual(expect.arrayContaining([
|
|
718
|
+
expect.objectContaining({ type: 'knowledge_run_summary', title: '知识运行' }),
|
|
719
|
+
expect.objectContaining({ type: 'citations', title: '引用' }),
|
|
720
|
+
expect.objectContaining({ type: 'knowledge_actions', title: '知识操作' }),
|
|
721
|
+
expect.objectContaining({ type: 'system_notice', text: expect.stringContaining('未召回') }),
|
|
722
|
+
]));
|
|
723
|
+
const languageConflictResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', {
|
|
724
|
+
userId: 'server_conversation_user',
|
|
725
|
+
sessionId: 'server_session_scope',
|
|
726
|
+
activeTarget: 'optics',
|
|
727
|
+
message: '解释吸收系数和光学衰减',
|
|
728
|
+
answerLanguage: 'en',
|
|
729
|
+
scope: {
|
|
730
|
+
corpusId: 'optics',
|
|
731
|
+
languages: ['zh']
|
|
732
|
+
},
|
|
733
|
+
persistMemory: true
|
|
734
|
+
}, {
|
|
735
|
+
'X-Agent-Conversation-Turn-Id': turnId
|
|
736
|
+
});
|
|
737
|
+
expect(languageConflictResponse.status).toBe(422);
|
|
738
|
+
expect(languageConflictResponse.body).toEqual(expect.objectContaining({
|
|
739
|
+
errorCode: 'turn_id_conflict',
|
|
740
|
+
}));
|
|
741
|
+
});
|
|
742
|
+
test('conversation turn cache separates full and slim response modes', async () => {
|
|
743
|
+
const ingestResponse = await requestJson(port, 'POST', '/api/knowledge/ingest', {
|
|
744
|
+
incremental: true,
|
|
745
|
+
documents: [{
|
|
746
|
+
documentId: 'doc_server_response_mode',
|
|
747
|
+
sourcePath: 'Knowledge_Base/response-mode/mode.md',
|
|
748
|
+
language: 'en',
|
|
749
|
+
workspaceId: 'response-mode',
|
|
750
|
+
corpusId: 'response-mode',
|
|
751
|
+
content: '# Response Mode Probe\nA response mode probe is a bounded technical fixture used to verify answer detail selection.\n\n## Thermal model\nThe temperature field follows $$\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T$$.',
|
|
752
|
+
}],
|
|
753
|
+
});
|
|
754
|
+
expect(ingestResponse.status).toBe(200);
|
|
755
|
+
const baseRequest = {
|
|
756
|
+
userId: 'server_response_mode_user',
|
|
757
|
+
sessionId: 'server_response_mode_session',
|
|
758
|
+
message: 'what is response mode probe?',
|
|
759
|
+
scope: {
|
|
760
|
+
workspaceId: 'response-mode',
|
|
761
|
+
corpusId: 'response-mode',
|
|
762
|
+
sourcePathPrefixes: ['Knowledge_Base/response-mode'],
|
|
763
|
+
},
|
|
764
|
+
persistMemory: false,
|
|
765
|
+
};
|
|
766
|
+
const fullResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', { ...baseRequest, responseMode: 'full' }, { 'X-Agent-Conversation-Turn-Id': 'turn_response_mode_full' });
|
|
767
|
+
expect(fullResponse.status).toBe(200);
|
|
768
|
+
expect(fullResponse.body.result.responseMode).toBe('full');
|
|
769
|
+
expect(fullResponse.body.result.answer).toContain('Thermal model');
|
|
770
|
+
const slimResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', { ...baseRequest, responseMode: 'slim' }, { 'X-Agent-Conversation-Turn-Id': 'turn_response_mode_slim' });
|
|
771
|
+
expect(slimResponse.status).toBe(200);
|
|
772
|
+
expect(slimResponse.body.result.responseMode).toBe('slim');
|
|
773
|
+
expect(slimResponse.body.result.answer).not.toContain('## Thermal model');
|
|
774
|
+
const fullReplay = await requestJson(port, 'POST', '/api/knowledge/conversation', { ...baseRequest, responseMode: 'full' }, { 'X-Agent-Conversation-Turn-Id': 'turn_response_mode_full' });
|
|
775
|
+
expect(fullReplay.status).toBe(200);
|
|
776
|
+
expect(fullReplay.headers?.['x-agent-conversation-replay']).toBe('hit');
|
|
777
|
+
expect(fullReplay.body.result.responseMode).toBe('full');
|
|
778
|
+
const definitionAliasResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', { ...baseRequest, response_mode: 'definition' }, { 'X-Agent-Conversation-Turn-Id': 'turn_response_mode_definition_alias' });
|
|
779
|
+
expect(definitionAliasResponse.status).toBe(200);
|
|
780
|
+
expect(definitionAliasResponse.body.result.responseMode).toBe('slim');
|
|
781
|
+
const comprehensiveAliasResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', { ...baseRequest, detailMode: 'comprehensive' }, { 'X-Agent-Conversation-Turn-Id': 'turn_response_mode_comprehensive_alias' });
|
|
782
|
+
expect(comprehensiveAliasResponse.status).toBe(200);
|
|
783
|
+
expect(comprehensiveAliasResponse.body.result.responseMode).toBe('full');
|
|
784
|
+
expect(comprehensiveAliasResponse.body.result.answer).toContain('Thermal model');
|
|
785
|
+
});
|
|
786
|
+
test('disconnect cancels a running turn once and its cached failure cannot write memory on replay', async () => {
|
|
787
|
+
const { KnowledgeLearningPlatform } = require('./learning/KnowledgeLearningPlatform');
|
|
788
|
+
const started = deferred();
|
|
789
|
+
const cancelled = deferred();
|
|
790
|
+
const run = jest.spyOn(KnowledgeLearningPlatform.prototype, 'runAgentConversation').mockImplementationOnce(async (...args) => {
|
|
791
|
+
const signal = args[1];
|
|
792
|
+
started.resolve();
|
|
793
|
+
return await new Promise((_resolve, reject) => {
|
|
794
|
+
const abort = () => { cancelled.resolve(); reject(signal.reason); };
|
|
795
|
+
if (signal.aborted)
|
|
796
|
+
abort();
|
|
797
|
+
else
|
|
798
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
const payload = { userId: 'disconnected-user', message: 'Financial overview' };
|
|
802
|
+
const client = http.request({ host: '127.0.0.1', port, path: '/api/knowledge/conversation', method: 'POST', headers: {
|
|
803
|
+
'content-type': 'application/json', accept: 'text/event-stream', 'x-agent-conversation-turn-id': 'turn_disconnect_regression',
|
|
804
|
+
} }, response => response.resume());
|
|
805
|
+
client.on('error', () => undefined);
|
|
806
|
+
try {
|
|
807
|
+
client.end(JSON.stringify(payload));
|
|
808
|
+
await started.promise;
|
|
809
|
+
client.destroy();
|
|
810
|
+
await cancelled.promise;
|
|
811
|
+
const replay = await requestJson(port, 'POST', '/api/knowledge/conversation', payload, { 'x-agent-conversation-turn-id': 'turn_disconnect_regression' });
|
|
812
|
+
expect(replay.status).toBe(499);
|
|
813
|
+
expect(replay.body.errorCode).toBe('runtime_cancelled');
|
|
814
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
815
|
+
const memory = await requestJson(port, 'POST', '/api/knowledge/conversation-memory/search', { userId: 'disconnected-user', query: 'Financial overview' });
|
|
816
|
+
expect(memory.body.result.entries).toHaveLength(0);
|
|
817
|
+
}
|
|
818
|
+
finally {
|
|
819
|
+
client.destroy();
|
|
820
|
+
run.mockRestore();
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
test('conversation budget mode is adaptive by default, supports unbounded desktop full, and stays isolated in cache', async () => {
|
|
824
|
+
const baseRequest = {
|
|
825
|
+
userId: 'server_response_budget_user',
|
|
826
|
+
sessionId: 'server_response_budget_session',
|
|
827
|
+
message: 'what is water glass?',
|
|
828
|
+
responseMode: 'full',
|
|
829
|
+
persistMemory: false,
|
|
830
|
+
};
|
|
831
|
+
const adaptiveResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', baseRequest, { 'X-Agent-Conversation-Turn-Id': 'turn_response_budget_adaptive' });
|
|
832
|
+
expect(adaptiveResponse.status).toBe(200);
|
|
833
|
+
expect(adaptiveResponse.body.result.responseBudget).toEqual(expect.objectContaining({
|
|
834
|
+
mode: 'adaptive',
|
|
835
|
+
tier: 'standard',
|
|
836
|
+
productCapDisabled: false,
|
|
837
|
+
}));
|
|
838
|
+
expect(adaptiveResponse.body.result.summary).toEqual(expect.objectContaining({
|
|
839
|
+
responseBudgetMode: 'adaptive',
|
|
840
|
+
responseBudgetTier: 'standard',
|
|
841
|
+
}));
|
|
842
|
+
const unboundedResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', { ...baseRequest, responseBudgetMode: 'unbounded' }, { 'X-Agent-Conversation-Turn-Id': 'turn_response_budget_unbounded' });
|
|
843
|
+
expect(unboundedResponse.status).toBe(200);
|
|
844
|
+
expect(unboundedResponse.body.result.responseBudget).toEqual(expect.objectContaining({
|
|
845
|
+
mode: 'unbounded',
|
|
846
|
+
tier: 'unbounded',
|
|
847
|
+
productCapDisabled: true,
|
|
848
|
+
}));
|
|
849
|
+
expect(unboundedResponse.body.result.responseBudget.rag.productCapDisabled).toBe(true);
|
|
850
|
+
expect(unboundedResponse.body.result.summary.responseBudgetMode).toBe('unbounded');
|
|
851
|
+
const cacheConflict = await requestJson(port, 'POST', '/api/knowledge/conversation', { ...baseRequest, responseBudgetMode: 'unbounded' }, { 'X-Agent-Conversation-Turn-Id': 'turn_response_budget_adaptive' });
|
|
852
|
+
expect(cacheConflict.status).toBe(422);
|
|
853
|
+
expect(cacheConflict.body).toEqual(expect.objectContaining({ errorCode: 'turn_id_conflict' }));
|
|
854
|
+
const mobileResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', { ...baseRequest, responseBudgetMode: 'unbounded', responseProfile: 'mobile_compact' }, { 'X-Agent-Conversation-Turn-Id': 'turn_response_budget_mobile' });
|
|
855
|
+
expect(mobileResponse.status).toBe(200);
|
|
856
|
+
expect(mobileResponse.body.result.responseMode).toBe('slim');
|
|
857
|
+
expect(mobileResponse.body.result.responseProfile).toBe('mobile_compact');
|
|
858
|
+
expect(mobileResponse.body.result.responseBudget).toBeUndefined();
|
|
859
|
+
expect(mobileResponse.body.result.summary.responseBudgetMode).toBe('adaptive');
|
|
860
|
+
});
|
|
861
|
+
test('disconnecting one joined consumer does not cancel the remaining consumer or duplicate execution', async () => {
|
|
862
|
+
const { KnowledgeLearningPlatform } = require('./learning/KnowledgeLearningPlatform');
|
|
863
|
+
const original = KnowledgeLearningPlatform.prototype.runAgentConversation;
|
|
864
|
+
const started = deferred();
|
|
865
|
+
const held = deferred();
|
|
866
|
+
let signal;
|
|
867
|
+
const run = jest.spyOn(KnowledgeLearningPlatform.prototype, 'runAgentConversation').mockImplementationOnce(async function (...args) {
|
|
868
|
+
signal = args[1];
|
|
869
|
+
started.resolve();
|
|
870
|
+
await held.promise;
|
|
871
|
+
return original.apply(this, args);
|
|
872
|
+
});
|
|
873
|
+
const before = await requestJson(port, 'GET', '/api/knowledge/conversation/turn-cache/diagnostics');
|
|
874
|
+
const previousJoins = before.body.result.counters.inFlightJoinCount;
|
|
875
|
+
const payload = { userId: 'joined-consumer', message: 'Financial overview', persistMemory: false };
|
|
876
|
+
const headers = { 'x-agent-conversation-turn-id': 'turn_joined_consumer' };
|
|
877
|
+
const client = http.request({ host: '127.0.0.1', port, path: '/api/knowledge/conversation', method: 'POST', headers: {
|
|
878
|
+
...headers, 'content-type': 'application/json', accept: 'text/event-stream',
|
|
879
|
+
} }, response => response.resume());
|
|
880
|
+
client.on('error', () => undefined);
|
|
881
|
+
let second;
|
|
882
|
+
try {
|
|
883
|
+
client.end(JSON.stringify(payload));
|
|
884
|
+
await started.promise;
|
|
885
|
+
second = requestJson(port, 'POST', '/api/knowledge/conversation', payload, headers);
|
|
886
|
+
const deadline = Date.now() + 2000;
|
|
887
|
+
let joins = previousJoins;
|
|
888
|
+
while (joins === previousJoins && Date.now() < deadline) {
|
|
889
|
+
joins = (await requestJson(port, 'GET', '/api/knowledge/conversation/turn-cache/diagnostics')).body.result.counters.inFlightJoinCount;
|
|
890
|
+
if (joins === previousJoins)
|
|
891
|
+
await new Promise(resolve => setTimeout(resolve, 5));
|
|
892
|
+
}
|
|
893
|
+
expect(joins).toBeGreaterThan(previousJoins);
|
|
894
|
+
client.destroy();
|
|
895
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
896
|
+
expect(signal?.aborted).toBe(false);
|
|
897
|
+
held.resolve();
|
|
898
|
+
expect((await second).status).toBe(200);
|
|
899
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
900
|
+
}
|
|
901
|
+
finally {
|
|
902
|
+
held.resolve();
|
|
903
|
+
client.destroy();
|
|
904
|
+
await second;
|
|
905
|
+
run.mockRestore();
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
test('conversation JSON and SSE responses preserve an explicit Chinese answer language for an English no-match query', async () => {
|
|
909
|
+
const requestPayload = {
|
|
910
|
+
userId: 'server_language_no_match_user',
|
|
911
|
+
sessionId: 'server_language_no_match_session',
|
|
912
|
+
activeTarget: 'empty-language-contract',
|
|
913
|
+
message: 'what is water glass?',
|
|
914
|
+
answerLanguage: 'zh',
|
|
915
|
+
scope: {
|
|
916
|
+
corpusId: 'empty-language-contract',
|
|
917
|
+
sourcePathPrefixes: ['Knowledge_Base/empty-language-contract']
|
|
918
|
+
},
|
|
919
|
+
persistMemory: false
|
|
920
|
+
};
|
|
921
|
+
const jsonResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', requestPayload, {
|
|
922
|
+
'X-Agent-Conversation-Turn-Id': 'turn_language_no_match_json'
|
|
923
|
+
});
|
|
924
|
+
expect(jsonResponse.status).toBe(200);
|
|
925
|
+
expect(jsonResponse.body.result.answer).toContain('当前范围');
|
|
926
|
+
expect(jsonResponse.body.result.answer).toContain('我暂时不能');
|
|
927
|
+
expect(jsonResponse.body.result.assistantBlocks).toEqual(expect.arrayContaining([
|
|
928
|
+
expect.objectContaining({
|
|
929
|
+
type: 'structured_answer',
|
|
930
|
+
title: '可信回答'
|
|
931
|
+
}),
|
|
932
|
+
expect.objectContaining({ type: 'knowledge_run_summary', title: '知识运行' }),
|
|
933
|
+
expect.objectContaining({ type: 'system_notice', text: expect.stringContaining('未召回') }),
|
|
934
|
+
]));
|
|
935
|
+
const jsonStructuredBlock = jsonResponse.body.result.assistantBlocks.find((block) => block.type === 'structured_answer');
|
|
936
|
+
expect(String(jsonStructuredBlock?.overviewMarkdown || '')).toContain('## 回答上下文');
|
|
937
|
+
expect(String(jsonStructuredBlock?.explanationMarkdown || '')).toContain('## 说明');
|
|
938
|
+
expect(String(jsonStructuredBlock?.evidenceMarkdown || '')).toContain('## 证据摘要');
|
|
939
|
+
const sseResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', requestPayload, {
|
|
940
|
+
Accept: 'text/event-stream',
|
|
941
|
+
'X-Agent-Conversation-Turn-Id': 'turn_language_no_match_sse'
|
|
942
|
+
});
|
|
943
|
+
expect(sseResponse.status).toBe(200);
|
|
944
|
+
expect(String(sseResponse.headers?.['content-type'] || '')).toContain('text/event-stream');
|
|
945
|
+
const completedPayload = String(sseResponse.body || '')
|
|
946
|
+
.split(/\n\n/)
|
|
947
|
+
.map((eventChunk) => eventChunk.split(/\n/).find((line) => line.startsWith('data: ')))
|
|
948
|
+
.filter((line) => Boolean(line))
|
|
949
|
+
.map((line) => JSON.parse(line.slice('data: '.length)))
|
|
950
|
+
.find((event) => event && event.type === 'turn_completed');
|
|
951
|
+
expect(completedPayload?.result?.answer).toContain('当前范围');
|
|
952
|
+
expect(completedPayload?.result?.answer).toContain('我暂时不能');
|
|
953
|
+
expect(completedPayload?.result?.assistantBlocks).toEqual(expect.arrayContaining([
|
|
954
|
+
expect.objectContaining({
|
|
955
|
+
type: 'structured_answer',
|
|
956
|
+
title: '可信回答'
|
|
957
|
+
}),
|
|
958
|
+
expect.objectContaining({ type: 'knowledge_run_summary', title: '知识运行' }),
|
|
959
|
+
expect.objectContaining({ type: 'system_notice', text: expect.stringContaining('未召回') }),
|
|
960
|
+
]));
|
|
961
|
+
const sseStructuredBlock = completedPayload?.result?.assistantBlocks?.find((block) => block.type === 'structured_answer');
|
|
962
|
+
expect(String(sseStructuredBlock?.overviewMarkdown || '')).toContain('## 回答上下文');
|
|
963
|
+
expect(String(sseStructuredBlock?.explanationMarkdown || '')).toContain('## 说明');
|
|
964
|
+
expect(String(sseStructuredBlock?.evidenceMarkdown || '')).toContain('## 证据摘要');
|
|
965
|
+
});
|
|
966
|
+
test('conversation route auto-hydrates the active knowledge target when the learning workspace store is empty', async () => {
|
|
967
|
+
temp.mkdir(path.join('project', 'Knowledge_Base', 'waterglass'));
|
|
968
|
+
temp.file(path.join('project', 'Knowledge_Base', 'waterglass', 'water glass.md'), '# Water Glass\nwater glass 是一个装有水的玻璃容器系统。');
|
|
969
|
+
const response = await requestJson(port, 'POST', '/api/knowledge/conversation', {
|
|
970
|
+
userId: 'server_auto_hydrate_user',
|
|
971
|
+
sessionId: 'server_auto_hydrate_session',
|
|
972
|
+
activeTarget: 'waterglass',
|
|
973
|
+
message: '什么是waterglass?',
|
|
974
|
+
persistMemory: false
|
|
975
|
+
}, {
|
|
976
|
+
'X-Agent-Conversation-Turn-Id': 'turn_auto_hydrate_scope'
|
|
977
|
+
});
|
|
978
|
+
expect(response.status).toBe(200);
|
|
979
|
+
expect(response.body.result.trace.usedScope).toEqual(expect.objectContaining({
|
|
980
|
+
workspaceId: 'waterglass',
|
|
981
|
+
corpusId: 'waterglass',
|
|
982
|
+
source: 'scoped',
|
|
983
|
+
}));
|
|
984
|
+
expect(response.body.result.trace.workspaceReadiness).toEqual(expect.objectContaining({
|
|
985
|
+
status: 'ready',
|
|
986
|
+
workspaceId: 'waterglass',
|
|
987
|
+
}));
|
|
988
|
+
expect(response.body.result.trace.planner).toEqual(expect.objectContaining({
|
|
989
|
+
titleLikeQueries: expect.arrayContaining(['water glass', 'waterglass']),
|
|
990
|
+
}));
|
|
991
|
+
expect(response.body.result.citations.length).toBeGreaterThan(0);
|
|
992
|
+
const answer = String(response.body.result.answer || '');
|
|
993
|
+
expect(answer.toLowerCase()).toContain('water glass');
|
|
994
|
+
expect(answer).not.toContain('Grounded by');
|
|
995
|
+
expect(answer).not.toContain('Key evidence');
|
|
996
|
+
});
|
|
997
|
+
test('conversation route returns a bounded mobile projection without internal diagnostics', async () => {
|
|
998
|
+
const ingestResponse = await requestJson(port, 'POST', '/api/knowledge/ingest', {
|
|
999
|
+
incremental: true,
|
|
1000
|
+
documents: [
|
|
1001
|
+
{
|
|
1002
|
+
documentId: 'doc_mobile_amorphous_ice',
|
|
1003
|
+
sourcePath: 'Knowledge_Base/mobile-waterglass/amorphous-ice.md',
|
|
1004
|
+
language: 'en',
|
|
1005
|
+
content: '# Amorphous Ice\nAmorphous ice is a solid form of water without long-range crystalline order.\n\n## Structure\nIts structure is described by the radial distribution function $g(r)$.',
|
|
1006
|
+
},
|
|
1007
|
+
],
|
|
1008
|
+
});
|
|
1009
|
+
expect(ingestResponse.status).toBe(200);
|
|
1010
|
+
const response = await requestJson(port, 'POST', '/api/knowledge/conversation', {
|
|
1011
|
+
userId: 'mobile_projection_user',
|
|
1012
|
+
sessionId: 'mobile_projection_session',
|
|
1013
|
+
message: 'what is amorphous ice?',
|
|
1014
|
+
responseMode: 'full',
|
|
1015
|
+
responseProfile: 'mobile_compact',
|
|
1016
|
+
persistMemory: false,
|
|
1017
|
+
}, {
|
|
1018
|
+
'X-Agent-Conversation-Turn-Id': 'turn_mobile_projection',
|
|
1019
|
+
});
|
|
1020
|
+
expect(response.status).toBe(200);
|
|
1021
|
+
const result = response.body.result;
|
|
1022
|
+
expect(result).toEqual(expect.objectContaining({
|
|
1023
|
+
responseMode: 'slim',
|
|
1024
|
+
responseProfile: 'mobile_compact',
|
|
1025
|
+
mobileProjection: expect.objectContaining({
|
|
1026
|
+
schemaVersion: 1,
|
|
1027
|
+
primarySubject: expect.stringContaining('Amorphous Ice'),
|
|
1028
|
+
directAnswer: expect.stringContaining('Amorphous ice'),
|
|
1029
|
+
}),
|
|
1030
|
+
assistantBlocks: [],
|
|
1031
|
+
knowledgePoints: [],
|
|
1032
|
+
citations: [],
|
|
1033
|
+
recalledMemories: [],
|
|
1034
|
+
memoryActions: [],
|
|
1035
|
+
}));
|
|
1036
|
+
expect(result.trace).toBeUndefined();
|
|
1037
|
+
expect(JSON.stringify(result)).not.toContain('ragContextPack');
|
|
1038
|
+
expect(JSON.stringify(result)).not.toContain('knowledgeRun');
|
|
1039
|
+
const jsonReplayResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', {
|
|
1040
|
+
userId: 'mobile_projection_user',
|
|
1041
|
+
sessionId: 'mobile_projection_session',
|
|
1042
|
+
message: 'what is amorphous ice?',
|
|
1043
|
+
responseMode: 'full',
|
|
1044
|
+
responseProfile: 'mobile_compact',
|
|
1045
|
+
persistMemory: false,
|
|
1046
|
+
}, {
|
|
1047
|
+
'X-Agent-Conversation-Turn-Id': 'turn_mobile_projection',
|
|
1048
|
+
});
|
|
1049
|
+
expect(jsonReplayResponse.status).toBe(200);
|
|
1050
|
+
expect(jsonReplayResponse.headers?.['x-agent-conversation-replay']).toBe('hit');
|
|
1051
|
+
expect(jsonReplayResponse.body.result).toEqual(expect.objectContaining({
|
|
1052
|
+
responseMode: 'slim',
|
|
1053
|
+
responseProfile: 'mobile_compact',
|
|
1054
|
+
assistantBlocks: [],
|
|
1055
|
+
knowledgePoints: [],
|
|
1056
|
+
citations: [],
|
|
1057
|
+
}));
|
|
1058
|
+
expect(jsonReplayResponse.body.result.trace).toBeUndefined();
|
|
1059
|
+
const parseCompletedMobileEvent = (body) => String(body || '')
|
|
1060
|
+
.split(/\n\n/)
|
|
1061
|
+
.map((eventChunk) => eventChunk.split(/\n/).find((line) => line.startsWith('data: ')))
|
|
1062
|
+
.filter((line) => Boolean(line))
|
|
1063
|
+
.map((line) => JSON.parse(line.slice('data: '.length)))
|
|
1064
|
+
.find((event) => event && event.type === 'turn_completed');
|
|
1065
|
+
const sseInitialResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', {
|
|
1066
|
+
userId: 'mobile_projection_user',
|
|
1067
|
+
sessionId: 'mobile_projection_session',
|
|
1068
|
+
message: 'what is amorphous ice?',
|
|
1069
|
+
responseMode: 'full',
|
|
1070
|
+
responseProfile: 'mobile_compact',
|
|
1071
|
+
persistMemory: false,
|
|
1072
|
+
}, {
|
|
1073
|
+
Accept: 'text/event-stream',
|
|
1074
|
+
'X-Agent-Conversation-Turn-Id': 'turn_mobile_projection_sse',
|
|
1075
|
+
});
|
|
1076
|
+
expect(sseInitialResponse.status).toBe(200);
|
|
1077
|
+
expect(String(sseInitialResponse.headers?.['content-type'] || '')).toContain('text/event-stream');
|
|
1078
|
+
expect(sseInitialResponse.headers?.['x-agent-conversation-replay']).toBe('miss');
|
|
1079
|
+
const sseInitialCompleted = parseCompletedMobileEvent(sseInitialResponse.body);
|
|
1080
|
+
expect(sseInitialCompleted?.result).toEqual(expect.objectContaining({
|
|
1081
|
+
responseMode: 'slim',
|
|
1082
|
+
responseProfile: 'mobile_compact',
|
|
1083
|
+
assistantBlocks: [],
|
|
1084
|
+
knowledgePoints: [],
|
|
1085
|
+
citations: [],
|
|
1086
|
+
}));
|
|
1087
|
+
expect(sseInitialCompleted?.result?.trace).toBeUndefined();
|
|
1088
|
+
expect(JSON.stringify(sseInitialCompleted?.result || {})).not.toContain('ragContextPack');
|
|
1089
|
+
const sseReplayResponse = await requestJson(port, 'POST', '/api/knowledge/conversation', {
|
|
1090
|
+
userId: 'mobile_projection_user',
|
|
1091
|
+
sessionId: 'mobile_projection_session',
|
|
1092
|
+
message: 'what is amorphous ice?',
|
|
1093
|
+
responseMode: 'full',
|
|
1094
|
+
responseProfile: 'mobile_compact',
|
|
1095
|
+
persistMemory: false,
|
|
1096
|
+
}, {
|
|
1097
|
+
Accept: 'text/event-stream',
|
|
1098
|
+
'X-Agent-Conversation-Turn-Id': 'turn_mobile_projection_sse',
|
|
1099
|
+
});
|
|
1100
|
+
expect(sseReplayResponse.status).toBe(200);
|
|
1101
|
+
expect(sseReplayResponse.headers?.['x-agent-conversation-replay']).toBe('hit');
|
|
1102
|
+
const sseReplayCompleted = parseCompletedMobileEvent(sseReplayResponse.body);
|
|
1103
|
+
expect(sseReplayCompleted?.result).toEqual(expect.objectContaining({
|
|
1104
|
+
responseMode: 'slim',
|
|
1105
|
+
responseProfile: 'mobile_compact',
|
|
1106
|
+
assistantBlocks: [],
|
|
1107
|
+
knowledgePoints: [],
|
|
1108
|
+
citations: [],
|
|
1109
|
+
}));
|
|
1110
|
+
expect(sseReplayCompleted?.result?.trace).toBeUndefined();
|
|
1111
|
+
});
|
|
1112
|
+
test('returns 400 from /api/render/math when expression is empty', async () => {
|
|
1113
|
+
const response = await requestJson(port, 'POST', '/api/render/math', {
|
|
1114
|
+
expression: ' '
|
|
1115
|
+
});
|
|
1116
|
+
expect(response.status).toBe(400);
|
|
1117
|
+
expect(response.body).toEqual(expect.objectContaining({
|
|
1118
|
+
success: false,
|
|
1119
|
+
error: 'Missing expression'
|
|
1120
|
+
}));
|
|
1121
|
+
expect(renderMathPngMock).not.toHaveBeenCalled();
|
|
1122
|
+
});
|
|
549
1123
|
test('returns svg from /api/render/mermaid when includeSvg is explicitly enabled', async () => {
|
|
550
1124
|
const response = await requestJson(port, 'POST', '/api/render/mermaid', {
|
|
551
1125
|
source: 'graph TD; A-->B',
|