noteconnection 1.7.0 → 1.8.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 +242 -62
- package/dist/src/agent_workspace.contract.parity.test.js +475 -0
- package/dist/src/agent_workspace.frontend.test.js +9989 -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/copy.assets.contract.test.js +130 -0
- package/dist/src/core/PathBridge.js +28 -3
- package/dist/src/export/WorkspaceExportBundle.js +1130 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +1480 -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 +406 -0
- package/dist/src/foundation.sqlite.runtime.contract.test.js +82 -0
- package/dist/src/frontend/README.md +242 -62
- package/dist/src/frontend/agent_workspace.js +5353 -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 +641 -266
- package/dist/src/frontend/layout_gpu.js +12 -4
- package/dist/src/frontend/locales/en.json +721 -5
- package/dist/src/frontend/locales/zh.json +721 -5
- package/dist/src/frontend/main.mjs +60 -0
- package/dist/src/frontend/markdown_runtime.js +827 -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 +107 -0
- 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/source_manager.js +190 -21
- package/dist/src/frontend/styles.css +2720 -72
- package/dist/src/frontend/workbench_state.mjs +101 -0
- package/dist/src/frontend/workspace_panes.js +10168 -0
- package/dist/src/frontend.locale.contract.test.js +62 -0
- package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
- package/dist/src/indexing/IndexLifecycle.js +195 -0
- package/dist/src/indexing/IndexLifecycle.test.js +49 -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 +170 -0
- package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
- package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
- package/dist/src/learning/answerReleaseReview.js +4319 -0
- package/dist/src/learning/answerReleaseReview.test.js +2888 -0
- package/dist/src/learning/api.js +2 -0
- package/dist/src/learning/conversationComposer.js +1480 -0
- package/dist/src/learning/conversationComposer.test.js +1817 -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 +1176 -0
- package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
- package/dist/src/learning/graphContextAssembler.js +870 -0
- package/dist/src/learning/graphContextAssembler.test.js +1033 -0
- package/dist/src/learning/index.js +28 -0
- package/dist/src/learning/queryBackend.js +1898 -0
- package/dist/src/learning/queryBackend.test.js +955 -0
- package/dist/src/learning/ragContextPack.js +257 -0
- package/dist/src/learning/ragContextPack.test.js +160 -0
- package/dist/src/learning/ragPublicText.js +38 -0
- package/dist/src/learning/ragSufficiencyJudge.js +161 -0
- package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
- package/dist/src/learning/requestNormalization.js +198 -0
- package/dist/src/learning/runtimeCapability.js +4677 -0
- package/dist/src/learning/runtimeCapability.test.js +3635 -0
- package/dist/src/learning/store.js +1240 -0
- package/dist/src/learning/store.test.js +1126 -0
- package/dist/src/learning/tutorAdapter.js +2 -0
- package/dist/src/learning/types.js +2 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
- package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
- package/dist/src/lfs.asset.policy.contract.test.js +153 -0
- package/dist/src/license.policy.contract.test.js +66 -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/auth.js +17 -0
- package/dist/src/middleware/body-parser.js +45 -0
- package/dist/src/middleware/cors.js +44 -0
- package/dist/src/middleware/index.js +21 -0
- package/dist/src/middleware/request-trace.js +96 -0
- package/dist/src/notemd/AppConfigToml.js +6 -4
- package/dist/src/notemd/MermaidProcessor.js +400 -50
- package/dist/src/notemd/NotemdService.js +498 -13
- package/dist/src/notemd/PromptManager.js +15 -0
- 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/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 +143 -35
- package/dist/src/notemd.workflow.pipeline.test.js +162 -0
- package/dist/src/pathbridge.handshake.contract.test.js +16 -2
- 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 +58 -0
- package/dist/src/platform/PlatformCapabilities.js +45 -0
- package/dist/src/platform/PlatformCapabilities.test.js +30 -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 +73 -0
- package/dist/src/resources/ResourceRegistry.js +223 -0
- package/dist/src/resources/ResourceRegistry.test.js +61 -0
- package/dist/src/resources/types.js +2 -0
- package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
- package/dist/src/routes/data.js +267 -0
- package/dist/src/routes/diagnostics.js +51 -0
- package/dist/src/routes/index.js +23 -0
- package/dist/src/routes/knowledge.js +968 -0
- package/dist/src/routes/markdown.js +287 -0
- package/dist/src/routes/notemd.js +565 -0
- package/dist/src/routes/registry.contract.test.js +130 -0
- package/dist/src/routes/render.js +285 -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.transport.adapter.contract.test.js +81 -0
- package/dist/src/server.js +11090 -1549
- package/dist/src/server.migration.test.js +193 -21
- 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.replacement.boundary.contract.test.js +128 -0
- package/dist/src/sidecar.supply.readiness.contract.test.js +144 -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/tauri.frontend.build.contract.test.js +60 -0
- package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
- package/dist/src/utils/RuntimePaths.js +4 -13
- package/dist/src/utils/platform.js +153 -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 +122 -0
- package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
- package/dist/src/workspace/types.js +2 -0
- package/package.json +53 -10
|
@@ -0,0 +1,1480 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeAgentConversationKnowledgePoints = mergeAgentConversationKnowledgePoints;
|
|
4
|
+
exports.collectAgentConversationAtomIds = collectAgentConversationAtomIds;
|
|
5
|
+
exports.buildScopedConversationReply = buildScopedConversationReply;
|
|
6
|
+
const answerReleaseReview_1 = require("./answerReleaseReview");
|
|
7
|
+
const graphContextAssembler_1 = require("./graphContextAssembler");
|
|
8
|
+
const ragPublicText_1 = require("./ragPublicText");
|
|
9
|
+
function normalizeWhitespace(value) {
|
|
10
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
11
|
+
}
|
|
12
|
+
function humanizeRelationKind(value) {
|
|
13
|
+
return normalizeWhitespace(String(value || '').replace(/_/g, ' '));
|
|
14
|
+
}
|
|
15
|
+
function formatGraphConnectionPath(connectionPath) {
|
|
16
|
+
const titles = Array.isArray(connectionPath.pathTitles)
|
|
17
|
+
? connectionPath.pathTitles.map((title) => normalizeWhitespace(String(title || '').trim())).filter(Boolean)
|
|
18
|
+
: [];
|
|
19
|
+
const edges = Array.isArray(connectionPath.pathEdges)
|
|
20
|
+
? connectionPath.pathEdges
|
|
21
|
+
: [];
|
|
22
|
+
if (titles.length <= 1 || edges.length <= 0) {
|
|
23
|
+
return titles.join(' -> ');
|
|
24
|
+
}
|
|
25
|
+
const segments = [titles[0]];
|
|
26
|
+
edges.forEach((edge, index) => {
|
|
27
|
+
const nextTitle = titles[index + 1];
|
|
28
|
+
if (!nextTitle) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
segments.push(humanizeRelationKind(edge && edge.relationKind ? edge.relationKind : 'link'), nextTitle);
|
|
32
|
+
});
|
|
33
|
+
return segments.join(' -> ');
|
|
34
|
+
}
|
|
35
|
+
function clampUnit(value) {
|
|
36
|
+
if (!Number.isFinite(value)) {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
return Number(Math.max(0, Math.min(1, value)).toFixed(4));
|
|
40
|
+
}
|
|
41
|
+
function addDaysIso(value, days) {
|
|
42
|
+
const parsed = Date.parse(value);
|
|
43
|
+
const baseTimestamp = Number.isFinite(parsed) ? parsed : 0;
|
|
44
|
+
return new Date(baseTimestamp + days * 24 * 60 * 60 * 1000).toISOString();
|
|
45
|
+
}
|
|
46
|
+
function buildFallbackKnowledgeRunId(params, generatedAt) {
|
|
47
|
+
const seed = [
|
|
48
|
+
generatedAt,
|
|
49
|
+
params.message,
|
|
50
|
+
params.usedScope.workspaceId || '',
|
|
51
|
+
params.usedScope.corpusId || '',
|
|
52
|
+
params.citations.map((citation) => citation.citationId).join('|'),
|
|
53
|
+
].join('|');
|
|
54
|
+
let hash = 0;
|
|
55
|
+
for (let index = 0; index < seed.length; index += 1) {
|
|
56
|
+
hash = ((hash << 5) - hash + seed.charCodeAt(index)) | 0;
|
|
57
|
+
}
|
|
58
|
+
return `knowledge_run_${Math.abs(hash) || 1}`;
|
|
59
|
+
}
|
|
60
|
+
function escapeRegExpLiteral(value) {
|
|
61
|
+
return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
62
|
+
}
|
|
63
|
+
function buildKnowledgeCitation(item, index) {
|
|
64
|
+
const atom = item.atom;
|
|
65
|
+
const evidence = item.evidenceSpans[0];
|
|
66
|
+
return {
|
|
67
|
+
citationId: String(evidence?.id || `citation_${atom.id}_${index + 1}`).trim(),
|
|
68
|
+
atomId: atom.id,
|
|
69
|
+
documentId: atom.documentId,
|
|
70
|
+
sourcePath: atom.sourcePath,
|
|
71
|
+
title: atom.title,
|
|
72
|
+
snippet: normalizeWhitespace(String(evidence?.snippet
|
|
73
|
+
|| atom.content
|
|
74
|
+
|| atom.title
|
|
75
|
+
|| '').slice(0, 280)),
|
|
76
|
+
startOffset: evidence?.startOffset,
|
|
77
|
+
endOffset: evidence?.endOffset,
|
|
78
|
+
startLine: evidence?.startLine,
|
|
79
|
+
endLine: evidence?.endLine,
|
|
80
|
+
score: Number(Number(item.score || 0).toFixed(4)),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function buildAgentConversationKnowledgePoint(item, index, buildCapabilities) {
|
|
84
|
+
const atom = item.atom;
|
|
85
|
+
const citation = buildKnowledgeCitation(item, index);
|
|
86
|
+
const summary = normalizeWhitespace(String(atom.content || atom.title || '').slice(0, 240)) || atom.title;
|
|
87
|
+
return {
|
|
88
|
+
atomId: atom.id,
|
|
89
|
+
atomIds: [atom.id],
|
|
90
|
+
documentId: atom.documentId,
|
|
91
|
+
sourcePath: atom.sourcePath,
|
|
92
|
+
title: atom.title,
|
|
93
|
+
summary,
|
|
94
|
+
evidenceSnippet: citation.snippet || summary || atom.title,
|
|
95
|
+
score: Number(Number(item.score || 0).toFixed(4)),
|
|
96
|
+
citation,
|
|
97
|
+
citations: citation ? [citation] : [],
|
|
98
|
+
matchedSpans: [
|
|
99
|
+
{
|
|
100
|
+
atomId: atom.id,
|
|
101
|
+
title: atom.title,
|
|
102
|
+
snippet: citation.snippet || summary || atom.title,
|
|
103
|
+
sourcePath: atom.sourcePath,
|
|
104
|
+
startOffset: citation.startOffset,
|
|
105
|
+
endOffset: citation.endOffset,
|
|
106
|
+
startLine: citation.startLine,
|
|
107
|
+
endLine: citation.endLine,
|
|
108
|
+
score: Number(Number(item.score || 0).toFixed(4)),
|
|
109
|
+
citation,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
matchCount: 1,
|
|
113
|
+
relationPath: item.relationPath.map((edge) => ({
|
|
114
|
+
edgeId: edge.id,
|
|
115
|
+
sourceAtomId: edge.sourceAtomId,
|
|
116
|
+
targetAtomId: edge.targetAtomId,
|
|
117
|
+
relationKind: edge.relationKind,
|
|
118
|
+
confidence: Number(Number(edge.confidence || 0).toFixed(4)),
|
|
119
|
+
})),
|
|
120
|
+
relationPathAtomIds: Array.from(new Set(item.relationPath.flatMap((edge) => [edge.sourceAtomId, edge.targetAtomId]))).filter((atomId) => atomId !== atom.id),
|
|
121
|
+
relationKinds: Array.from(new Set(item.relationPath.map((edge) => edge.relationKind))),
|
|
122
|
+
temporalValidity: {
|
|
123
|
+
isValid: item.temporalValidity.isValid,
|
|
124
|
+
checkedAt: item.temporalValidity.checkedAt,
|
|
125
|
+
reasons: [...item.temporalValidity.reasons],
|
|
126
|
+
details: Array.isArray(item.temporalValidity.details)
|
|
127
|
+
? item.temporalValidity.details.map((detail) => ({ ...detail }))
|
|
128
|
+
: [],
|
|
129
|
+
},
|
|
130
|
+
capabilities: buildCapabilities(atom.id),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function mergeAgentConversationKnowledgePoints(items, buildCapabilities) {
|
|
134
|
+
const groups = new Map();
|
|
135
|
+
items.forEach((item, index) => {
|
|
136
|
+
const atom = item.atom;
|
|
137
|
+
const groupKey = String(atom.documentId || atom.id || `atom_${index}`).trim();
|
|
138
|
+
const citation = buildKnowledgeCitation(item, index);
|
|
139
|
+
const snippet = citation.snippet
|
|
140
|
+
|| normalizeWhitespace(String(atom.content || atom.title || '').slice(0, 280))
|
|
141
|
+
|| atom.title;
|
|
142
|
+
let group = groups.get(groupKey);
|
|
143
|
+
if (!group) {
|
|
144
|
+
const point = buildAgentConversationKnowledgePoint(item, index, buildCapabilities);
|
|
145
|
+
point.citation = citation;
|
|
146
|
+
point.citations = [];
|
|
147
|
+
point.matchedSpans = [];
|
|
148
|
+
point.atomIds = [];
|
|
149
|
+
point.matchCount = 0;
|
|
150
|
+
point.relationPath = [];
|
|
151
|
+
point.relationPathAtomIds = [];
|
|
152
|
+
point.relationKinds = [];
|
|
153
|
+
point.temporalValidity = undefined;
|
|
154
|
+
group = {
|
|
155
|
+
point,
|
|
156
|
+
atomIds: new Set(),
|
|
157
|
+
citationKeys: new Set(),
|
|
158
|
+
spanKeys: new Set(),
|
|
159
|
+
relationEdgeIds: new Set(),
|
|
160
|
+
relationPathAtomIds: new Set(),
|
|
161
|
+
relationKinds: new Set(),
|
|
162
|
+
temporalValidityCheckedAt: String(item.temporalValidity.checkedAt || ''),
|
|
163
|
+
temporalValidityReasons: new Set(),
|
|
164
|
+
temporalValidityDetails: new Map(),
|
|
165
|
+
};
|
|
166
|
+
groups.set(groupKey, group);
|
|
167
|
+
}
|
|
168
|
+
group.atomIds.add(atom.id);
|
|
169
|
+
group.point.atomIds = Array.from(group.atomIds.values());
|
|
170
|
+
group.point.score = Math.max(group.point.score, Number(Number(item.score || 0).toFixed(4)));
|
|
171
|
+
const citationKey = [
|
|
172
|
+
citation.documentId,
|
|
173
|
+
citation.sourcePath,
|
|
174
|
+
citation.startLine || '',
|
|
175
|
+
citation.endLine || '',
|
|
176
|
+
citation.snippet,
|
|
177
|
+
].join('|');
|
|
178
|
+
if (!group.citationKeys.has(citationKey)) {
|
|
179
|
+
group.citationKeys.add(citationKey);
|
|
180
|
+
group.point.citations = [...(group.point.citations || []), citation];
|
|
181
|
+
if (!group.point.citation) {
|
|
182
|
+
group.point.citation = citation;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const spanKey = [
|
|
186
|
+
atom.id,
|
|
187
|
+
citation.startLine || '',
|
|
188
|
+
citation.endLine || '',
|
|
189
|
+
snippet,
|
|
190
|
+
].join('|');
|
|
191
|
+
if (!group.spanKeys.has(spanKey)) {
|
|
192
|
+
group.spanKeys.add(spanKey);
|
|
193
|
+
group.point.matchedSpans = [
|
|
194
|
+
...(group.point.matchedSpans || []),
|
|
195
|
+
{
|
|
196
|
+
atomId: atom.id,
|
|
197
|
+
title: atom.title,
|
|
198
|
+
snippet,
|
|
199
|
+
sourcePath: atom.sourcePath,
|
|
200
|
+
startOffset: citation.startOffset,
|
|
201
|
+
endOffset: citation.endOffset,
|
|
202
|
+
startLine: citation.startLine,
|
|
203
|
+
endLine: citation.endLine,
|
|
204
|
+
score: Number(Number(item.score || 0).toFixed(4)),
|
|
205
|
+
citation,
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
group.point.matchCount = group.point.matchedSpans.length;
|
|
209
|
+
}
|
|
210
|
+
const relationPath = Array.isArray(item.relationPath) ? item.relationPath : [];
|
|
211
|
+
relationPath.forEach((edge) => {
|
|
212
|
+
const edgeId = String(edge && edge.id || '').trim();
|
|
213
|
+
if (!edgeId) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!Array.isArray(group.point.relationPath)) {
|
|
217
|
+
group.point.relationPath = [];
|
|
218
|
+
}
|
|
219
|
+
if (!group.relationEdgeIds.has(edgeId)) {
|
|
220
|
+
group.relationEdgeIds.add(edgeId);
|
|
221
|
+
group.point.relationPath.push({
|
|
222
|
+
edgeId,
|
|
223
|
+
sourceAtomId: String(edge.sourceAtomId || '').trim(),
|
|
224
|
+
targetAtomId: String(edge.targetAtomId || '').trim(),
|
|
225
|
+
relationKind: edge.relationKind,
|
|
226
|
+
confidence: Number(Number(edge.confidence || 0).toFixed(4)),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
[edge.sourceAtomId, edge.targetAtomId]
|
|
230
|
+
.map((candidateAtomId) => String(candidateAtomId || '').trim())
|
|
231
|
+
.filter(Boolean)
|
|
232
|
+
.filter((candidateAtomId) => !group.atomIds.has(candidateAtomId))
|
|
233
|
+
.forEach((candidateAtomId) => group.relationPathAtomIds.add(candidateAtomId));
|
|
234
|
+
if (edge.relationKind) {
|
|
235
|
+
group.relationKinds.add(edge.relationKind);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
const temporalValidity = item.temporalValidity && typeof item.temporalValidity === 'object'
|
|
239
|
+
? item.temporalValidity
|
|
240
|
+
: null;
|
|
241
|
+
if (temporalValidity) {
|
|
242
|
+
const checkedAt = String(temporalValidity.checkedAt || '').trim();
|
|
243
|
+
if (checkedAt && checkedAt > group.temporalValidityCheckedAt) {
|
|
244
|
+
group.temporalValidityCheckedAt = checkedAt;
|
|
245
|
+
}
|
|
246
|
+
if (temporalValidity.isValid === false) {
|
|
247
|
+
group.point.temporalValidity = group.point.temporalValidity || {
|
|
248
|
+
isValid: true,
|
|
249
|
+
checkedAt: checkedAt,
|
|
250
|
+
reasons: [],
|
|
251
|
+
details: [],
|
|
252
|
+
};
|
|
253
|
+
group.point.temporalValidity.isValid = false;
|
|
254
|
+
}
|
|
255
|
+
if (!group.point.temporalValidity) {
|
|
256
|
+
group.point.temporalValidity = {
|
|
257
|
+
isValid: temporalValidity.isValid !== false,
|
|
258
|
+
checkedAt,
|
|
259
|
+
reasons: [],
|
|
260
|
+
details: [],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
else if (temporalValidity.isValid === false) {
|
|
264
|
+
group.point.temporalValidity.isValid = false;
|
|
265
|
+
}
|
|
266
|
+
(Array.isArray(temporalValidity.reasons) ? temporalValidity.reasons : [])
|
|
267
|
+
.map((reason) => String(reason || '').trim())
|
|
268
|
+
.filter(Boolean)
|
|
269
|
+
.forEach((reason) => group.temporalValidityReasons.add(reason));
|
|
270
|
+
(Array.isArray(temporalValidity.details) ? temporalValidity.details : [])
|
|
271
|
+
.filter((detail) => Boolean(detail && typeof detail === 'object'))
|
|
272
|
+
.forEach((detail) => {
|
|
273
|
+
const detailKey = [
|
|
274
|
+
String(detail.edgeId || '').trim(),
|
|
275
|
+
String(detail.edgeKind || '').trim(),
|
|
276
|
+
String(detail.sourceAtomId || '').trim(),
|
|
277
|
+
String(detail.targetAtomId || '').trim(),
|
|
278
|
+
String(detail.validFrom || '').trim(),
|
|
279
|
+
String(detail.validTo || '').trim(),
|
|
280
|
+
].join('|');
|
|
281
|
+
if (!group.temporalValidityDetails.has(detailKey)) {
|
|
282
|
+
group.temporalValidityDetails.set(detailKey, {
|
|
283
|
+
edgeId: String(detail.edgeId || '').trim(),
|
|
284
|
+
edgeKind: detail.edgeKind,
|
|
285
|
+
sourceAtomId: String(detail.sourceAtomId || '').trim(),
|
|
286
|
+
targetAtomId: String(detail.targetAtomId || '').trim(),
|
|
287
|
+
validFrom: String(detail.validFrom || '').trim(),
|
|
288
|
+
validTo: detail.validTo ? String(detail.validTo).trim() : undefined,
|
|
289
|
+
isActive: detail.isActive !== false,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
return Array.from(groups.values()).map((group) => {
|
|
296
|
+
const citations = group.point.citations || [];
|
|
297
|
+
const spans = group.point.matchedSpans || [];
|
|
298
|
+
return {
|
|
299
|
+
...group.point,
|
|
300
|
+
citation: group.point.citation || citations[0] || null,
|
|
301
|
+
citations,
|
|
302
|
+
matchedSpans: spans,
|
|
303
|
+
matchCount: spans.length,
|
|
304
|
+
evidenceSnippet: spans[0]?.snippet || group.point.evidenceSnippet,
|
|
305
|
+
relationPath: Array.isArray(group.point.relationPath) ? group.point.relationPath : [],
|
|
306
|
+
relationPathAtomIds: Array.from(group.relationPathAtomIds.values()),
|
|
307
|
+
relationKinds: Array.from(group.relationKinds.values()),
|
|
308
|
+
temporalValidity: group.point.temporalValidity
|
|
309
|
+
? {
|
|
310
|
+
isValid: group.point.temporalValidity.isValid !== false,
|
|
311
|
+
checkedAt: group.temporalValidityCheckedAt || group.point.temporalValidity.checkedAt,
|
|
312
|
+
reasons: Array.from(group.temporalValidityReasons.values()),
|
|
313
|
+
details: Array.from(group.temporalValidityDetails.values()),
|
|
314
|
+
}
|
|
315
|
+
: undefined,
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function collectAgentConversationAtomIds(knowledgePoints) {
|
|
320
|
+
const atomIds = new Set();
|
|
321
|
+
knowledgePoints.forEach((point) => {
|
|
322
|
+
const groupedAtomIds = Array.isArray(point.atomIds) && point.atomIds.length > 0
|
|
323
|
+
? point.atomIds
|
|
324
|
+
: [point.atomId];
|
|
325
|
+
groupedAtomIds
|
|
326
|
+
.map((atomId) => String(atomId || '').trim())
|
|
327
|
+
.filter(Boolean)
|
|
328
|
+
.forEach((atomId) => atomIds.add(atomId));
|
|
329
|
+
});
|
|
330
|
+
return Array.from(atomIds.values());
|
|
331
|
+
}
|
|
332
|
+
function cleanConversationAnswerCandidate(value, point) {
|
|
333
|
+
let cleaned = String(value || '')
|
|
334
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
335
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
336
|
+
.replace(/[*_~`>#|]/g, ' ')
|
|
337
|
+
.trim();
|
|
338
|
+
const title = normalizeWhitespace(String(point.title || '').replace(/[#*_~`]/g, ' '));
|
|
339
|
+
if (title) {
|
|
340
|
+
cleaned = cleaned
|
|
341
|
+
.replace(new RegExp(`^${escapeRegExpLiteral(title)}\\s*(?:[:\\uFF1A.\\-\\u2014]|\\r?\\n)+\\s*`, 'i'), '')
|
|
342
|
+
.trim();
|
|
343
|
+
const flattenedHeadingMatch = cleaned.match(new RegExp(`^${escapeRegExpLiteral(title)}\\s+(.+)$`, 'i'));
|
|
344
|
+
if (flattenedHeadingMatch) {
|
|
345
|
+
const remainder = normalizeWhitespace(flattenedHeadingMatch[1] || '');
|
|
346
|
+
const titleWordCount = title.split(/\s+/).filter(Boolean).length;
|
|
347
|
+
const repeatsTitle = new RegExp(`^${escapeRegExpLiteral(title)}(?:\\b|\\s)`, 'i').test(remainder);
|
|
348
|
+
const startsWithArticle = /^(?:a|an|the)\s+/i.test(remainder);
|
|
349
|
+
if (repeatsTitle || (titleWordCount > 1 && startsWithArticle)) {
|
|
350
|
+
cleaned = remainder;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return normalizeWhitespace(cleaned);
|
|
355
|
+
}
|
|
356
|
+
function selectScopedConversationDirectSentence(message, point) {
|
|
357
|
+
const candidates = [
|
|
358
|
+
...(Array.isArray(point.matchedSpans) ? point.matchedSpans.map((span) => span.snippet) : []),
|
|
359
|
+
point.summary,
|
|
360
|
+
point.evidenceSnippet,
|
|
361
|
+
]
|
|
362
|
+
.map((candidate) => cleanConversationAnswerCandidate(String(candidate || ''), point))
|
|
363
|
+
.filter(Boolean);
|
|
364
|
+
for (const candidate of candidates) {
|
|
365
|
+
const sentences = candidate.match(/[^.!?\u3002\uFF01\uFF1F]+[.!?\u3002\uFF01\uFF1F]?/g) || [candidate];
|
|
366
|
+
const directSentence = sentences
|
|
367
|
+
.map((sentence) => normalizeWhitespace(sentence))
|
|
368
|
+
.find((sentence) => {
|
|
369
|
+
const lower = sentence.toLowerCase();
|
|
370
|
+
return (lower.includes(' is ')
|
|
371
|
+
|| lower.includes(' are ')
|
|
372
|
+
|| lower.includes(' refers to ')
|
|
373
|
+
|| lower.includes(' means ')
|
|
374
|
+
|| sentence.includes('是')
|
|
375
|
+
|| sentence.includes('指')) && sentence.length >= 12;
|
|
376
|
+
});
|
|
377
|
+
if (directSentence) {
|
|
378
|
+
return directSentence;
|
|
379
|
+
}
|
|
380
|
+
const firstSentence = normalizeWhitespace(sentences[0] || '');
|
|
381
|
+
if (firstSentence.length >= 12) {
|
|
382
|
+
return firstSentence;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return normalizeWhitespace(String(point.summary || point.evidenceSnippet || point.title || message || ''));
|
|
386
|
+
}
|
|
387
|
+
function classifyScopedConversationIntent(message) {
|
|
388
|
+
const normalized = String(message || '').trim().toLowerCase();
|
|
389
|
+
if (!normalized) {
|
|
390
|
+
return 'generic';
|
|
391
|
+
}
|
|
392
|
+
if (normalized.includes('compare')
|
|
393
|
+
|| normalized.includes('difference')
|
|
394
|
+
|| normalized.includes('vs')
|
|
395
|
+
|| normalized.includes('区别')
|
|
396
|
+
|| normalized.includes('对比')) {
|
|
397
|
+
return 'compare';
|
|
398
|
+
}
|
|
399
|
+
if (normalized.includes('how to')
|
|
400
|
+
|| normalized.includes('how do')
|
|
401
|
+
|| normalized.includes('steps')
|
|
402
|
+
|| normalized.includes('plan')
|
|
403
|
+
|| normalized.includes('如何')
|
|
404
|
+
|| normalized.includes('怎么')
|
|
405
|
+
|| normalized.includes('步骤')
|
|
406
|
+
|| normalized.includes('方案')) {
|
|
407
|
+
return 'how_to';
|
|
408
|
+
}
|
|
409
|
+
if (/\b(?:why|cause|causes|caused|causal|because|reason|mechanism|mechanisms)\b/u.test(normalized)
|
|
410
|
+
|| normalized.includes('为什么')
|
|
411
|
+
|| normalized.includes('為什麼')
|
|
412
|
+
|| normalized.includes('为何')
|
|
413
|
+
|| normalized.includes('為何')
|
|
414
|
+
|| normalized.includes('原因')
|
|
415
|
+
|| normalized.includes('因果')
|
|
416
|
+
|| normalized.includes('机制')
|
|
417
|
+
|| normalized.includes('機制')
|
|
418
|
+
|| normalized.includes('导致')
|
|
419
|
+
|| normalized.includes('導致')) {
|
|
420
|
+
return 'causal_explain';
|
|
421
|
+
}
|
|
422
|
+
if (normalized.includes('what is')
|
|
423
|
+
|| normalized.includes('explain')
|
|
424
|
+
|| normalized.includes('解释')
|
|
425
|
+
|| normalized.includes('什么是')) {
|
|
426
|
+
return 'explain';
|
|
427
|
+
}
|
|
428
|
+
return 'generic';
|
|
429
|
+
}
|
|
430
|
+
function resolveRagAnswerProfile(message) {
|
|
431
|
+
const intent = classifyScopedConversationIntent(message);
|
|
432
|
+
if (intent === 'compare') {
|
|
433
|
+
return {
|
|
434
|
+
directSupportSentenceCount: 4,
|
|
435
|
+
documentContextSentenceCount: 1,
|
|
436
|
+
graphNeighborSentenceCount: 2,
|
|
437
|
+
publicSentenceCount: 6,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (intent === 'how_to') {
|
|
441
|
+
return {
|
|
442
|
+
directSupportSentenceCount: 3,
|
|
443
|
+
documentContextSentenceCount: 1,
|
|
444
|
+
graphNeighborSentenceCount: 2,
|
|
445
|
+
publicSentenceCount: 6,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (intent === 'causal_explain') {
|
|
449
|
+
return {
|
|
450
|
+
directSupportSentenceCount: 2,
|
|
451
|
+
documentContextSentenceCount: 2,
|
|
452
|
+
graphNeighborSentenceCount: 2,
|
|
453
|
+
publicSentenceCount: 6,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (intent === 'generic') {
|
|
457
|
+
return {
|
|
458
|
+
directSupportSentenceCount: 2,
|
|
459
|
+
documentContextSentenceCount: 2,
|
|
460
|
+
graphNeighborSentenceCount: 1,
|
|
461
|
+
publicSentenceCount: 6,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
directSupportSentenceCount: 1,
|
|
466
|
+
documentContextSentenceCount: 2,
|
|
467
|
+
graphNeighborSentenceCount: 1,
|
|
468
|
+
publicSentenceCount: 6,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function containsCjk(value) {
|
|
472
|
+
return /[\u3040-\u30ff\u3400-\u9fff\uf900-\ufaff]/u.test(String(value || ''));
|
|
473
|
+
}
|
|
474
|
+
function stripConversationAnswerTerminalPunctuation(value) {
|
|
475
|
+
return normalizeWhitespace(String(value || '').replace(/[.!?\u3002\uFF01\uFF1F]+$/u, ''));
|
|
476
|
+
}
|
|
477
|
+
function normalizeConversationAnswerSentence(value, useChinese) {
|
|
478
|
+
const normalized = normalizeWhitespace(String(value || ''));
|
|
479
|
+
if (!normalized) {
|
|
480
|
+
return '';
|
|
481
|
+
}
|
|
482
|
+
if (useChinese && /[.!?]$/u.test(normalized)) {
|
|
483
|
+
return normalized.replace(/[.!?]+$/u, '。');
|
|
484
|
+
}
|
|
485
|
+
return /[.!?\u3002\uFF01\uFF1F]$/u.test(normalized)
|
|
486
|
+
? normalized
|
|
487
|
+
: `${normalized}${useChinese ? '。' : '.'}`;
|
|
488
|
+
}
|
|
489
|
+
function appendConversationAnswerSentence(sentences, sentence, useChinese) {
|
|
490
|
+
const normalizedSentence = normalizeConversationAnswerSentence(sentence, useChinese);
|
|
491
|
+
if (!normalizedSentence) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const normalizedKey = stripConversationAnswerTerminalPunctuation(normalizedSentence).toLowerCase();
|
|
495
|
+
const alreadyPresent = sentences.some((existingSentence) => (stripConversationAnswerTerminalPunctuation(existingSentence).toLowerCase() === normalizedKey));
|
|
496
|
+
if (!alreadyPresent) {
|
|
497
|
+
sentences.push(normalizedSentence);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function buildGraphConnectionPathAnswerSentence(graphContext, useChinese) {
|
|
501
|
+
const connectionPath = graphContext && Array.isArray(graphContext.connectionPaths)
|
|
502
|
+
? graphContext.connectionPaths[0]
|
|
503
|
+
: null;
|
|
504
|
+
const pathTitles = connectionPath && Array.isArray(connectionPath.pathTitles)
|
|
505
|
+
? connectionPath.pathTitles.map((title) => normalizeWhitespace(String(title || '').trim())).filter(Boolean)
|
|
506
|
+
: [];
|
|
507
|
+
if (pathTitles.length <= 1) {
|
|
508
|
+
return '';
|
|
509
|
+
}
|
|
510
|
+
if (useChinese) {
|
|
511
|
+
return `当前图中的关键路径是 ${pathTitles.join(' -> ')}`;
|
|
512
|
+
}
|
|
513
|
+
return `The strongest graph path runs through ${pathTitles.join(' -> ')}`;
|
|
514
|
+
}
|
|
515
|
+
function normalizeGraphAnswerDisplayTitle(value) {
|
|
516
|
+
return normalizeWhitespace(String(value || '')
|
|
517
|
+
.replace(/\s*\((?:mermaid|code|diagram)\s+block\)\s*$/iu, '')
|
|
518
|
+
.trim());
|
|
519
|
+
}
|
|
520
|
+
function normalizeGraphAnswerComparableTitle(value) {
|
|
521
|
+
return normalizeGraphAnswerDisplayTitle(value).toLowerCase();
|
|
522
|
+
}
|
|
523
|
+
function collectGraphWindowTitles(graphContext, windowKey, limit) {
|
|
524
|
+
if (!graphContext || !Array.isArray(graphContext[windowKey])) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
const anchorAtomId = normalizeWhitespace(String(graphContext.anchorAtomId || '').trim());
|
|
528
|
+
const anchorTitle = normalizeGraphAnswerComparableTitle(graphContext.anchorTitle);
|
|
529
|
+
const seen = new Set();
|
|
530
|
+
const titles = [];
|
|
531
|
+
for (const node of graphContext[windowKey] || []) {
|
|
532
|
+
const atomId = normalizeWhitespace(String(node && node.atomId || '').trim());
|
|
533
|
+
const title = normalizeGraphAnswerDisplayTitle(String(node && node.title || ''));
|
|
534
|
+
const comparableTitle = normalizeGraphAnswerComparableTitle(title);
|
|
535
|
+
if (!title || (atomId && atomId === anchorAtomId) || (comparableTitle && comparableTitle === anchorTitle)) {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
const key = comparableTitle || atomId;
|
|
539
|
+
if (seen.has(key)) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
seen.add(key);
|
|
543
|
+
titles.push(title);
|
|
544
|
+
if (titles.length >= limit) {
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return titles;
|
|
549
|
+
}
|
|
550
|
+
function buildGraphProfileAnswerSentence(graphContext, useChinese) {
|
|
551
|
+
if (!graphContext) {
|
|
552
|
+
return '';
|
|
553
|
+
}
|
|
554
|
+
const anchorProfile = graphContext.anchorGraphProfile && typeof graphContext.anchorGraphProfile === 'object'
|
|
555
|
+
? graphContext.anchorGraphProfile
|
|
556
|
+
: null;
|
|
557
|
+
const anchorTitle = normalizeWhitespace(String(graphContext.anchorTitle || anchorProfile?.title || '').trim());
|
|
558
|
+
const predecessorTitles = collectGraphWindowTitles(graphContext, 'predecessorWindow', 2);
|
|
559
|
+
const successorTitles = collectGraphWindowTitles(graphContext, 'successorWindow', 2);
|
|
560
|
+
const degreeParts = [];
|
|
561
|
+
const inDegree = anchorProfile ? Number(anchorProfile.inDegree) : NaN;
|
|
562
|
+
const outDegree = anchorProfile ? Number(anchorProfile.outDegree) : NaN;
|
|
563
|
+
if (Number.isFinite(inDegree)) {
|
|
564
|
+
degreeParts.push(useChinese ? `入度为 ${inDegree}` : `${inDegree} incoming`);
|
|
565
|
+
}
|
|
566
|
+
if (Number.isFinite(outDegree)) {
|
|
567
|
+
degreeParts.push(useChinese ? `出度为 ${outDegree}` : `${outDegree} outgoing`);
|
|
568
|
+
}
|
|
569
|
+
if (degreeParts.length <= 0 && predecessorTitles.length <= 0 && successorTitles.length <= 0) {
|
|
570
|
+
return '';
|
|
571
|
+
}
|
|
572
|
+
if (useChinese) {
|
|
573
|
+
const fragments = [];
|
|
574
|
+
if (degreeParts.length > 0) {
|
|
575
|
+
fragments.push(`${anchorTitle || '当前锚点'}在当前图中的${degreeParts.join(',')}`);
|
|
576
|
+
}
|
|
577
|
+
if (predecessorTitles.length > 0) {
|
|
578
|
+
fragments.push(`紧邻前置节点包括 ${predecessorTitles.join('、')}`);
|
|
579
|
+
}
|
|
580
|
+
if (successorTitles.length > 0) {
|
|
581
|
+
fragments.push(`后续分支包括 ${successorTitles.join('、')}`);
|
|
582
|
+
}
|
|
583
|
+
return fragments.join(',');
|
|
584
|
+
}
|
|
585
|
+
const fragments = [];
|
|
586
|
+
if (degreeParts.length > 0) {
|
|
587
|
+
fragments.push(`${anchorTitle || 'The current anchor'} has ${degreeParts.join(' and ')} links in the current graph`);
|
|
588
|
+
}
|
|
589
|
+
if (predecessorTitles.length > 0 && successorTitles.length > 0) {
|
|
590
|
+
fragments.push(`its immediate predecessors include ${predecessorTitles.join(', ')}, and likely next nodes include ${successorTitles.join(', ')}`);
|
|
591
|
+
}
|
|
592
|
+
else if (predecessorTitles.length > 0) {
|
|
593
|
+
fragments.push(`its immediate predecessors include ${predecessorTitles.join(', ')}`);
|
|
594
|
+
}
|
|
595
|
+
else if (successorTitles.length > 0) {
|
|
596
|
+
fragments.push(`its likely next nodes include ${successorTitles.join(', ')}`);
|
|
597
|
+
}
|
|
598
|
+
return fragments.join('; ');
|
|
599
|
+
}
|
|
600
|
+
const RAG_ANSWER_QUERY_STOPWORDS = new Set([
|
|
601
|
+
'a', 'about', 'an', 'and', 'are', 'as', 'at', 'be', 'between', 'by', 'compare',
|
|
602
|
+
'contrast', 'difference', 'differences', 'do', 'does', 'from', 'how',
|
|
603
|
+
'in', 'is', 'it', 'me', 'of', 'on', 'or', 'plan', 'step', 'steps', 'tell', 'the', 'to', 'versus', 'vs', 'what',
|
|
604
|
+
'which', 'with',
|
|
605
|
+
]);
|
|
606
|
+
function extractRagAnswerQueryTerms(message) {
|
|
607
|
+
const terms = (String(message || '').toLowerCase().match(/[\p{L}\p{N}]+/gu) || [])
|
|
608
|
+
.map((term) => term.trim())
|
|
609
|
+
.filter((term) => term.length >= 2 && !RAG_ANSWER_QUERY_STOPWORDS.has(term));
|
|
610
|
+
return Array.from(new Set(terms));
|
|
611
|
+
}
|
|
612
|
+
function normalizeMermaidEvidenceLabel(value) {
|
|
613
|
+
return normalizeWhitespace(String(value || '')
|
|
614
|
+
.replace(/<br\s*\/?>/giu, ' ')
|
|
615
|
+
.replace(/\\n/gu, ' ')
|
|
616
|
+
.replace(/[`*_~#|{}]/gu, ' ')
|
|
617
|
+
.replace(/\s{2,}/gu, ' ')
|
|
618
|
+
.trim());
|
|
619
|
+
}
|
|
620
|
+
function extractMermaidEvidenceLabels(fenceText) {
|
|
621
|
+
const normalizedFence = String(fenceText || '').trim();
|
|
622
|
+
if (!/^mermaid\b/iu.test(normalizedFence)) {
|
|
623
|
+
return '';
|
|
624
|
+
}
|
|
625
|
+
const mermaidBody = normalizedFence.replace(/^mermaid\b/iu, '').trim();
|
|
626
|
+
const labels = [];
|
|
627
|
+
const appendLabel = (value) => {
|
|
628
|
+
const label = normalizeMermaidEvidenceLabel(value);
|
|
629
|
+
if (label && !labels.includes(label)) {
|
|
630
|
+
labels.push(label);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
mermaidBody.replace(/\[([^\]]+)\]/gu, (_match, label) => {
|
|
634
|
+
appendLabel(label);
|
|
635
|
+
return '';
|
|
636
|
+
});
|
|
637
|
+
mermaidBody.replace(/"([^"]+)"/gu, (_match, label) => {
|
|
638
|
+
appendLabel(label);
|
|
639
|
+
return '';
|
|
640
|
+
});
|
|
641
|
+
return labels.join('. ');
|
|
642
|
+
}
|
|
643
|
+
function cleanRagEvidenceText(value) {
|
|
644
|
+
return normalizeWhitespace(String(value || '')
|
|
645
|
+
.replace(/```([\s\S]*?)(?:```|$)/gu, (_match, fenceText) => (` ${extractMermaidEvidenceLabels(fenceText)} `))
|
|
646
|
+
.split(/\r?\n/u)
|
|
647
|
+
.filter((line) => !/^#{1,6}\s+/u.test(line.trim()))
|
|
648
|
+
.join(' ')
|
|
649
|
+
.replace(/\[[^\]]+\]\([^)]*\)/gu, ' ')
|
|
650
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/gu, ' ')
|
|
651
|
+
.replace(/[*_~`>#|]/gu, ' ')
|
|
652
|
+
.replace(/\s{2,}/gu, ' '));
|
|
653
|
+
}
|
|
654
|
+
function splitRagEvidenceSentences(fragment) {
|
|
655
|
+
const cleaned = cleanRagEvidenceText(fragment.text);
|
|
656
|
+
if (!cleaned) {
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
const sentences = [];
|
|
660
|
+
let start = 0;
|
|
661
|
+
for (let index = 0; index < cleaned.length; index += 1) {
|
|
662
|
+
const char = cleaned[index];
|
|
663
|
+
const isDecimalPoint = char === '.'
|
|
664
|
+
&& /\d/u.test(cleaned[index - 1] || '')
|
|
665
|
+
&& /\d/u.test(cleaned[index + 1] || '');
|
|
666
|
+
const isBoundary = !isDecimalPoint && /[.!?\u3002\uFF01\uFF1F]/u.test(char);
|
|
667
|
+
if (!isBoundary) {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
sentences.push(cleaned.slice(start, index + 1));
|
|
671
|
+
start = index + 1;
|
|
672
|
+
}
|
|
673
|
+
if (start < cleaned.length) {
|
|
674
|
+
sentences.push(cleaned.slice(start));
|
|
675
|
+
}
|
|
676
|
+
return (sentences.length > 0 ? sentences : [cleaned])
|
|
677
|
+
.map((sentence) => normalizeWhitespace(sentence))
|
|
678
|
+
.filter((sentence) => sentence.length >= 16);
|
|
679
|
+
}
|
|
680
|
+
function sentenceComparableKey(value) {
|
|
681
|
+
return stripConversationAnswerTerminalPunctuation(value).toLowerCase();
|
|
682
|
+
}
|
|
683
|
+
function appendRagEvidenceSentence(sentences, candidate, useChinese) {
|
|
684
|
+
const normalized = normalizeConversationAnswerSentence((0, ragPublicText_1.naturalizeRagPublicEvidenceClause)(candidate), useChinese);
|
|
685
|
+
if (!normalized) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const candidateKey = sentenceComparableKey(normalized);
|
|
689
|
+
const alreadyCovered = sentences.some((sentence) => {
|
|
690
|
+
const existingKey = sentenceComparableKey(sentence);
|
|
691
|
+
return existingKey === candidateKey
|
|
692
|
+
|| (candidateKey.length >= 32 && existingKey.includes(candidateKey))
|
|
693
|
+
|| (existingKey.length >= 32 && candidateKey.includes(existingKey));
|
|
694
|
+
});
|
|
695
|
+
if (!alreadyCovered) {
|
|
696
|
+
sentences.push(normalized);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function collectSentenceQueryTerms(sentence, queryTerms) {
|
|
700
|
+
const lower = sentence.toLowerCase();
|
|
701
|
+
return new Set(queryTerms.filter((term) => lower.includes(term)));
|
|
702
|
+
}
|
|
703
|
+
function collectFragmentLeafHeadingQueryTerms(fragment, queryTerms) {
|
|
704
|
+
const headingPath = Array.isArray(fragment.headingPath) ? fragment.headingPath : [];
|
|
705
|
+
const leafHeading = normalizeWhitespace(String(headingPath[headingPath.length - 1] || ''));
|
|
706
|
+
if (!leafHeading) {
|
|
707
|
+
return new Set();
|
|
708
|
+
}
|
|
709
|
+
return collectSentenceQueryTerms(leafHeading, queryTerms);
|
|
710
|
+
}
|
|
711
|
+
function rankRagEvidenceSentenceCandidates(candidates, limit, options = {}) {
|
|
712
|
+
const remaining = candidates.slice();
|
|
713
|
+
const ranked = [];
|
|
714
|
+
const coveredTerms = new Set();
|
|
715
|
+
while (remaining.length > 0 && ranked.length < limit) {
|
|
716
|
+
let bestIndex = 0;
|
|
717
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
718
|
+
remaining.forEach((candidate, index) => {
|
|
719
|
+
const totalTermCount = candidate.queryTerms.size;
|
|
720
|
+
const headingTermCount = options.useLeafHeadingScore ? candidate.headingQueryTerms.size : 0;
|
|
721
|
+
const uncoveredTermCount = Array.from(candidate.queryTerms)
|
|
722
|
+
.filter((term) => !coveredTerms.has(term))
|
|
723
|
+
.length;
|
|
724
|
+
const score = uncoveredTermCount * 4
|
|
725
|
+
+ (uncoveredTermCount > 0 ? totalTermCount : 0)
|
|
726
|
+
+ (totalTermCount > 0 ? candidate.fragmentQueryTermCount / 2 : 0)
|
|
727
|
+
+ headingTermCount * 6
|
|
728
|
+
- candidate.order / 10000;
|
|
729
|
+
if (score > bestScore) {
|
|
730
|
+
bestScore = score;
|
|
731
|
+
bestIndex = index;
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
const [selected] = remaining.splice(bestIndex, 1);
|
|
735
|
+
ranked.push(selected);
|
|
736
|
+
selected.queryTerms.forEach((term) => coveredTerms.add(term));
|
|
737
|
+
}
|
|
738
|
+
return ranked;
|
|
739
|
+
}
|
|
740
|
+
function selectRagEvidenceSentences(fragments, roles, limit, queryTerms = [], options = {}) {
|
|
741
|
+
const selected = [];
|
|
742
|
+
const candidates = [];
|
|
743
|
+
let sentenceOrder = 0;
|
|
744
|
+
fragments
|
|
745
|
+
.filter((fragment) => roles.has(fragment.role))
|
|
746
|
+
.forEach((fragment) => {
|
|
747
|
+
const sentences = splitRagEvidenceSentences(fragment);
|
|
748
|
+
const headingQueryTerms = collectFragmentLeafHeadingQueryTerms(fragment, queryTerms);
|
|
749
|
+
const fragmentQueryTerms = new Set(sentences.flatMap((sentence) => Array.from(collectSentenceQueryTerms(sentence, queryTerms))));
|
|
750
|
+
sentences.forEach((sentence) => {
|
|
751
|
+
if (options.rejectSentence?.(sentence)) {
|
|
752
|
+
sentenceOrder += 1;
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const comparable = sentenceComparableKey(sentence);
|
|
756
|
+
if (!candidates.some((existing) => sentenceComparableKey(existing.sentence) === comparable)) {
|
|
757
|
+
candidates.push({
|
|
758
|
+
sentence,
|
|
759
|
+
queryTerms: collectSentenceQueryTerms(sentence, queryTerms),
|
|
760
|
+
headingQueryTerms,
|
|
761
|
+
fragmentQueryTermCount: fragmentQueryTerms.size,
|
|
762
|
+
order: sentenceOrder,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
sentenceOrder += 1;
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
const maxHeadingTermCount = candidates.reduce((max, candidate) => Math.max(max, candidate.headingQueryTerms.size), 0);
|
|
769
|
+
const selectionCandidates = options.preferBestLeafHeadingMatch && maxHeadingTermCount > 0
|
|
770
|
+
? candidates.filter((candidate) => candidate.headingQueryTerms.size === maxHeadingTermCount)
|
|
771
|
+
: candidates;
|
|
772
|
+
const hasQuerySignal = queryTerms.length > 0 && selectionCandidates.some((candidate) => (candidate.queryTerms.size > 0 || candidate.headingQueryTerms.size > 0));
|
|
773
|
+
const orderedCandidates = hasQuerySignal
|
|
774
|
+
? rankRagEvidenceSentenceCandidates(selectionCandidates, limit, options)
|
|
775
|
+
: selectionCandidates;
|
|
776
|
+
orderedCandidates.forEach((candidate) => {
|
|
777
|
+
if (selected.length >= limit) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const comparable = sentenceComparableKey(candidate.sentence);
|
|
781
|
+
if (!selected.some((existing) => sentenceComparableKey(existing) === comparable)) {
|
|
782
|
+
selected.push(candidate.sentence);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
return selected;
|
|
786
|
+
}
|
|
787
|
+
function buildRagAugmentedConversationAnswer(params, graphContext, useChinese) {
|
|
788
|
+
const pack = params.ragContextPack;
|
|
789
|
+
if (!pack || !Array.isArray(pack.fragments) || pack.fragments.length <= 0) {
|
|
790
|
+
return '';
|
|
791
|
+
}
|
|
792
|
+
if (params.ragSufficiencyReview?.status === 'insufficient') {
|
|
793
|
+
return '';
|
|
794
|
+
}
|
|
795
|
+
const profile = resolveRagAnswerProfile(params.message);
|
|
796
|
+
const intent = classifyScopedConversationIntent(params.message);
|
|
797
|
+
const answerQueryTerms = extractRagAnswerQueryTerms(params.message);
|
|
798
|
+
const rejectCompareProcedureSentence = intent === 'compare'
|
|
799
|
+
? (sentence) => (0, ragPublicText_1.shouldRejectCompareProcedureEvidenceClause)(sentence, params.message)
|
|
800
|
+
: undefined;
|
|
801
|
+
const answerSentences = [];
|
|
802
|
+
selectRagEvidenceSentences(pack.fragments, new Set(['direct_support']), profile.directSupportSentenceCount, answerQueryTerms, {
|
|
803
|
+
rejectSentence: rejectCompareProcedureSentence,
|
|
804
|
+
})
|
|
805
|
+
.forEach((sentence) => appendRagEvidenceSentence(answerSentences, sentence, useChinese));
|
|
806
|
+
const hasConflictEvidence = params.ragSufficiencyReview?.degradationState === 'conflict'
|
|
807
|
+
|| (params.ragSufficiencyReview?.reasons || []).some((reason) => String(reason || '').includes('conflict_evidence_present'));
|
|
808
|
+
if (hasConflictEvidence) {
|
|
809
|
+
selectRagEvidenceSentences(pack.fragments, new Set(['conflict']), 3, answerQueryTerms)
|
|
810
|
+
.forEach((sentence) => appendRagEvidenceSentence(answerSentences, sentence, useChinese));
|
|
811
|
+
}
|
|
812
|
+
selectRagEvidenceSentences(pack.fragments, new Set(['parent_context', 'adjacent_context']), profile.documentContextSentenceCount, answerQueryTerms, {
|
|
813
|
+
useLeafHeadingScore: intent !== 'compare',
|
|
814
|
+
preferBestLeafHeadingMatch: intent !== 'compare',
|
|
815
|
+
rejectSentence: rejectCompareProcedureSentence,
|
|
816
|
+
})
|
|
817
|
+
.forEach((sentence) => appendRagEvidenceSentence(answerSentences, sentence, useChinese));
|
|
818
|
+
selectRagEvidenceSentences(pack.fragments, new Set(['graph_neighbor_support']), profile.graphNeighborSentenceCount, answerQueryTerms, {
|
|
819
|
+
rejectSentence: rejectCompareProcedureSentence,
|
|
820
|
+
})
|
|
821
|
+
.forEach((sentence) => appendRagEvidenceSentence(answerSentences, sentence, useChinese));
|
|
822
|
+
if (params.ragSufficiencyReview?.status === 'borderline') {
|
|
823
|
+
appendRagEvidenceSentence(answerSentences, useChinese
|
|
824
|
+
? '当前证据覆盖仍然有限,因此这只能作为基于已命中材料的部分回答'
|
|
825
|
+
: 'The available evidence is still partial, so this answer stays within the matched material', useChinese);
|
|
826
|
+
}
|
|
827
|
+
appendConversationAnswerSentence(answerSentences, buildGraphConnectionPathAnswerSentence(graphContext, useChinese), useChinese);
|
|
828
|
+
appendConversationAnswerSentence(answerSentences, buildGraphProfileAnswerSentence(graphContext, useChinese), useChinese);
|
|
829
|
+
return answerSentences.slice(0, profile.publicSentenceCount).join(useChinese ? '' : ' ');
|
|
830
|
+
}
|
|
831
|
+
function buildScopedConversationAnswer(params, graphContext) {
|
|
832
|
+
if (params.knowledgePoints.length <= 0) {
|
|
833
|
+
const readinessMessage = String(params.usedScope.readiness?.message || '').trim();
|
|
834
|
+
const missMessage = String(params.usedScope.missDiagnostics?.message || '').trim();
|
|
835
|
+
if (params.recalledMemories.length > 0) {
|
|
836
|
+
return `No scoped knowledge points matched "${params.message || 'your query'}", but I recovered ${params.recalledMemories.length} relevant conversation memory note(s). ${missMessage || readinessMessage || 'Refine the corpus scope or use the recalled memory as a follow-up anchor.'}`;
|
|
837
|
+
}
|
|
838
|
+
return `No scoped knowledge points matched "${params.message || 'your query'}". ${missMessage || readinessMessage || 'Refine the scope, add more notes to the corpus, or broaden the query terms.'}`;
|
|
839
|
+
}
|
|
840
|
+
const leadingPoint = params.knowledgePoints[0];
|
|
841
|
+
const useChinese = containsCjk([
|
|
842
|
+
params.message,
|
|
843
|
+
leadingPoint.title,
|
|
844
|
+
leadingPoint.summary,
|
|
845
|
+
graphContext && graphContext.anchorTitle,
|
|
846
|
+
].filter(Boolean).join(' '));
|
|
847
|
+
const answerSentences = [];
|
|
848
|
+
const ragAnswer = buildRagAugmentedConversationAnswer(params, graphContext, useChinese);
|
|
849
|
+
if (ragAnswer) {
|
|
850
|
+
return ragAnswer;
|
|
851
|
+
}
|
|
852
|
+
const directSentence = selectScopedConversationDirectSentence(params.message, leadingPoint);
|
|
853
|
+
if (directSentence) {
|
|
854
|
+
appendConversationAnswerSentence(answerSentences, directSentence, useChinese);
|
|
855
|
+
}
|
|
856
|
+
if (answerSentences.length <= 0) {
|
|
857
|
+
const title = normalizeWhitespace(String(leadingPoint.title || '').trim());
|
|
858
|
+
const fallback = normalizeWhitespace(String(leadingPoint.evidenceSnippet || leadingPoint.summary || '').trim());
|
|
859
|
+
if (title && fallback && title !== fallback) {
|
|
860
|
+
appendConversationAnswerSentence(answerSentences, `${title}: ${fallback}`, useChinese);
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
appendConversationAnswerSentence(answerSentences, fallback || title || normalizeWhitespace(String(params.message || '')), useChinese);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
appendConversationAnswerSentence(answerSentences, buildGraphConnectionPathAnswerSentence(graphContext, useChinese), useChinese);
|
|
867
|
+
appendConversationAnswerSentence(answerSentences, buildGraphProfileAnswerSentence(graphContext, useChinese), useChinese);
|
|
868
|
+
if (answerSentences.length < 2 && params.knowledgePoints.length > 1) {
|
|
869
|
+
appendConversationAnswerSentence(answerSentences, selectScopedConversationDirectSentence(params.message, params.knowledgePoints[1]), useChinese);
|
|
870
|
+
}
|
|
871
|
+
return answerSentences.slice(0, 3).join(useChinese ? '' : ' ');
|
|
872
|
+
}
|
|
873
|
+
function buildScopedConversationOverviewMarkdown(params, graphContext) {
|
|
874
|
+
const strongestPoint = params.knowledgePoints[0];
|
|
875
|
+
const lines = [
|
|
876
|
+
'## Answer Context',
|
|
877
|
+
'',
|
|
878
|
+
];
|
|
879
|
+
if (strongestPoint) {
|
|
880
|
+
lines.push(`Best scoped anchor: **${strongestPoint.title}**.`, '');
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
lines.push('No scoped knowledge point produced a strong match for the current request.', '');
|
|
884
|
+
}
|
|
885
|
+
lines.push(`- Relevant knowledge points: **${params.knowledgePoints.length}**`, `- Citations returned: **${params.citations.length}**`, `- Scoped memories recalled: **${params.recalledMemories.length}**`);
|
|
886
|
+
if (graphContext && graphContext.relationKinds.length > 0) {
|
|
887
|
+
lines.push(`- Graph-supported relations: **${graphContext.relationKinds.join(', ')}**`);
|
|
888
|
+
}
|
|
889
|
+
if (graphContext) {
|
|
890
|
+
lines.push(`- Temporal validity: **${graphContext.temporalValidity.allPointsValid ? 'valid' : 'warning'}**`);
|
|
891
|
+
}
|
|
892
|
+
if (graphContext && Array.isArray(graphContext.connectionPaths) && graphContext.connectionPaths.length > 0) {
|
|
893
|
+
lines.push(`- Explicit connection paths: **${graphContext.connectionPaths.length}**`);
|
|
894
|
+
}
|
|
895
|
+
if (graphContext && Array.isArray(graphContext.predecessorWindow) && graphContext.predecessorWindow.length > 0) {
|
|
896
|
+
lines.push(`- Immediate predecessors: **${graphContext.predecessorWindow.length}**`);
|
|
897
|
+
}
|
|
898
|
+
if (graphContext && Array.isArray(graphContext.successorWindow) && graphContext.successorWindow.length > 0) {
|
|
899
|
+
lines.push(`- Immediate successors: **${graphContext.successorWindow.length}**`);
|
|
900
|
+
}
|
|
901
|
+
return lines.join('\n');
|
|
902
|
+
}
|
|
903
|
+
function buildScopedConversationExplanationMarkdown(params, graphContext) {
|
|
904
|
+
if (params.knowledgePoints.length <= 0) {
|
|
905
|
+
return '## Explanation\n\nThe current scope did not return a strong enough knowledge point to explain the request directly.';
|
|
906
|
+
}
|
|
907
|
+
const intent = classifyScopedConversationIntent(params.message);
|
|
908
|
+
const strongestPoint = params.knowledgePoints[0];
|
|
909
|
+
const explanationLines = [
|
|
910
|
+
'## Explanation',
|
|
911
|
+
'',
|
|
912
|
+
];
|
|
913
|
+
if (intent === 'compare') {
|
|
914
|
+
explanationLines.push(`Use **${strongestPoint.title}** as the comparison baseline inside the current scope.`);
|
|
915
|
+
}
|
|
916
|
+
else if (intent === 'how_to') {
|
|
917
|
+
explanationLines.push(`Use **${strongestPoint.title}** as the starting anchor for the next concrete steps.`);
|
|
918
|
+
}
|
|
919
|
+
else if (intent === 'explain') {
|
|
920
|
+
explanationLines.push(`**${strongestPoint.title}** is the current best scoped anchor for the explanation.`);
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
explanationLines.push(`**${strongestPoint.title}** is the current best scoped anchor.`);
|
|
924
|
+
}
|
|
925
|
+
const summary = normalizeWhitespace(String(strongestPoint.summary || strongestPoint.evidenceSnippet || '').trim());
|
|
926
|
+
if (summary) {
|
|
927
|
+
explanationLines.push('', summary);
|
|
928
|
+
}
|
|
929
|
+
if (graphContext && graphContext.relationKinds.length > 0) {
|
|
930
|
+
explanationLines.push('', `Graph support around **${graphContext.anchorTitle}** includes: ${graphContext.relationKinds.join(', ')}.`);
|
|
931
|
+
}
|
|
932
|
+
if (graphContext && Array.isArray(graphContext.knowledgePointRelations) && graphContext.knowledgePointRelations.length > 0) {
|
|
933
|
+
const relationPreview = graphContext.knowledgePointRelations
|
|
934
|
+
.slice(0, 2)
|
|
935
|
+
.map((relation) => `${relation.sourceTitle} -> ${relation.relationKind} -> ${relation.targetTitle}`)
|
|
936
|
+
.join('; ');
|
|
937
|
+
explanationLines.push('', `Direct graph links inside the current result set: ${relationPreview}.`);
|
|
938
|
+
}
|
|
939
|
+
if (graphContext && Array.isArray(graphContext.connectionPaths) && graphContext.connectionPaths.length > 0) {
|
|
940
|
+
explanationLines.push('', `Explicit graph path: ${formatGraphConnectionPath(graphContext.connectionPaths[0])}.`);
|
|
941
|
+
}
|
|
942
|
+
if (graphContext) {
|
|
943
|
+
const predecessorTitles = collectGraphWindowTitles(graphContext, 'predecessorWindow', 3);
|
|
944
|
+
if (predecessorTitles.length > 0) {
|
|
945
|
+
explanationLines.push('', 'Immediate predecessor window: ' + predecessorTitles.join(', ') + '.');
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (graphContext) {
|
|
949
|
+
const successorTitles = collectGraphWindowTitles(graphContext, 'successorWindow', 3);
|
|
950
|
+
if (successorTitles.length > 0) {
|
|
951
|
+
explanationLines.push('', 'Immediate successor window: ' + successorTitles.join(', ') + '.');
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (graphContext && graphContext.temporalValidity.allPointsValid === false) {
|
|
955
|
+
const reasonSummary = graphContext.temporalValidity.warningReasons.length > 0
|
|
956
|
+
? graphContext.temporalValidity.warningReasons.join(', ')
|
|
957
|
+
: 'temporal validity checks reported a warning';
|
|
958
|
+
explanationLines.push('', `Temporal validity warning: ${reasonSummary}.`);
|
|
959
|
+
}
|
|
960
|
+
const supersedesCount = graphContext
|
|
961
|
+
? (Array.isArray(graphContext.temporalValidity.details)
|
|
962
|
+
? graphContext.temporalValidity.details.filter((detail) => detail.edgeKind === 'supersedes').length
|
|
963
|
+
: 0)
|
|
964
|
+
: 0;
|
|
965
|
+
if (supersedesCount > 0) {
|
|
966
|
+
explanationLines.push('', `Temporal lineage indicates this anchor supersedes ${supersedesCount} earlier revision${supersedesCount === 1 ? '' : 's'}.`);
|
|
967
|
+
}
|
|
968
|
+
const supportingTitles = params.knowledgePoints
|
|
969
|
+
.slice(1, 3)
|
|
970
|
+
.map((point) => normalizeWhitespace(String(point.title || '').trim()))
|
|
971
|
+
.filter(Boolean);
|
|
972
|
+
if (supportingTitles.length > 0) {
|
|
973
|
+
explanationLines.push('', intent === 'compare'
|
|
974
|
+
? `Supporting comparison nodes: ${supportingTitles.join(', ')}.`
|
|
975
|
+
: `Supporting scoped nodes: ${supportingTitles.join(', ')}.`);
|
|
976
|
+
}
|
|
977
|
+
if (params.recalledMemories.length > 0) {
|
|
978
|
+
explanationLines.push('', `Scoped memory recall contributed ${params.recalledMemories.length} prior note(s) to this explanation.`);
|
|
979
|
+
}
|
|
980
|
+
if (params.citations.length > 0) {
|
|
981
|
+
explanationLines.push('', `The explanation is grounded by ${params.citations.length} citation(s) from the current scope.`);
|
|
982
|
+
}
|
|
983
|
+
return explanationLines.join('\n');
|
|
984
|
+
}
|
|
985
|
+
function buildScopedConversationEvidenceMarkdown(params) {
|
|
986
|
+
const evidenceLines = params.citations.slice(0, 3).map((citation, index) => (`${index + 1}. **${citation.title}** (${citation.sourcePath}${citation.startLine ? `:${citation.startLine}` : ''})\n - ${citation.snippet}`));
|
|
987
|
+
if (evidenceLines.length <= 0) {
|
|
988
|
+
return '## Evidence Summary\n\nNo scoped citations were returned.';
|
|
989
|
+
}
|
|
990
|
+
return [
|
|
991
|
+
'## Evidence Summary',
|
|
992
|
+
'',
|
|
993
|
+
...evidenceLines,
|
|
994
|
+
].join('\n');
|
|
995
|
+
}
|
|
996
|
+
function buildScopedConversationMemoryNotice(params) {
|
|
997
|
+
if (params.recalledMemories.length <= 0) {
|
|
998
|
+
return 'No scoped memory note was recalled for this turn.';
|
|
999
|
+
}
|
|
1000
|
+
if (params.recalledMemories.length === 1) {
|
|
1001
|
+
return '1 scoped memory note was recalled and merged into the answer context.';
|
|
1002
|
+
}
|
|
1003
|
+
return `${params.recalledMemories.length} scoped memory notes were recalled and merged into the answer context.`;
|
|
1004
|
+
}
|
|
1005
|
+
function buildScopedConversationActionGuideMarkdown(params, graphContext) {
|
|
1006
|
+
if (params.knowledgePoints.length <= 0) {
|
|
1007
|
+
return '## Next Actions\n\nNo actionable scoped knowledge card is available for this turn.';
|
|
1008
|
+
}
|
|
1009
|
+
const intent = classifyScopedConversationIntent(params.message);
|
|
1010
|
+
const topTitles = params.knowledgePoints
|
|
1011
|
+
.slice(0, 3)
|
|
1012
|
+
.map((point) => `- ${point.title}`);
|
|
1013
|
+
const actionHints = params.memoryActions
|
|
1014
|
+
.slice(0, 2)
|
|
1015
|
+
.map((action) => normalizeWhitespace(String(action.reason || '').trim()))
|
|
1016
|
+
.filter(Boolean)
|
|
1017
|
+
.map((reason) => `- ${reason}`);
|
|
1018
|
+
const graphActionHints = [];
|
|
1019
|
+
if (graphContext && graphContext.relationKinds.includes('prerequisite')) {
|
|
1020
|
+
graphActionHints.push('- Inspect prerequisite-linked concepts in focus mode before guided learning.');
|
|
1021
|
+
}
|
|
1022
|
+
if (graphContext && graphContext.temporalValidity.allPointsValid === false) {
|
|
1023
|
+
graphActionHints.push('- Validate whether a fresher or superseding note should replace this anchor before promotion.');
|
|
1024
|
+
}
|
|
1025
|
+
const supersedesCount = graphContext
|
|
1026
|
+
? (Array.isArray(graphContext.temporalValidity.details)
|
|
1027
|
+
? graphContext.temporalValidity.details.filter((detail) => detail.edgeKind === 'supersedes').length
|
|
1028
|
+
: 0)
|
|
1029
|
+
: 0;
|
|
1030
|
+
if (supersedesCount > 0) {
|
|
1031
|
+
graphActionHints.push('- Trace the superseded lineage before promoting this answer.');
|
|
1032
|
+
}
|
|
1033
|
+
if (graphContext && Array.isArray(graphContext.knowledgePointRelations) && graphContext.knowledgePointRelations.length > 0) {
|
|
1034
|
+
const firstRelation = graphContext.knowledgePointRelations[0];
|
|
1035
|
+
graphActionHints.push(`- Follow the direct graph path between ${firstRelation.sourceTitle} and ${firstRelation.targetTitle} before branching to external support nodes.`);
|
|
1036
|
+
}
|
|
1037
|
+
if (graphContext && Array.isArray(graphContext.connectionPaths) && graphContext.connectionPaths.length > 0) {
|
|
1038
|
+
const firstConnectionPath = graphContext.connectionPaths[0];
|
|
1039
|
+
const titles = Array.isArray(firstConnectionPath.pathTitles)
|
|
1040
|
+
? firstConnectionPath.pathTitles.map((title) => normalizeWhitespace(String(title || '').trim())).filter(Boolean)
|
|
1041
|
+
: [];
|
|
1042
|
+
if (titles.length > 1) {
|
|
1043
|
+
graphActionHints.push(`- Review the path order: ${titles.join(' -> ')}.`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (graphContext) {
|
|
1047
|
+
const predecessorTitles = collectGraphWindowTitles(graphContext, 'predecessorWindow', 2);
|
|
1048
|
+
if (predecessorTitles.length > 0) {
|
|
1049
|
+
graphActionHints.push('- Inspect prerequisite context from ' + predecessorTitles.join(', ') + ' before expanding the answer.');
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if (graphContext) {
|
|
1053
|
+
const successorTitles = collectGraphWindowTitles(graphContext, 'successorWindow', 2);
|
|
1054
|
+
if (successorTitles.length > 0) {
|
|
1055
|
+
graphActionHints.push('- Use likely next-step nodes such as ' + successorTitles.join(', ') + ' to continue follow-through after this answer.');
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return [
|
|
1059
|
+
'## Next Actions',
|
|
1060
|
+
'',
|
|
1061
|
+
intent === 'compare'
|
|
1062
|
+
? 'Use the scoped knowledge cards below to inspect the strongest nodes side by side before deciding which distinctions matter most:'
|
|
1063
|
+
: intent === 'how_to'
|
|
1064
|
+
? 'Use the scoped knowledge cards below to move from explanation into concrete guided-learning or focus-mode steps:'
|
|
1065
|
+
: 'Use the scoped knowledge cards below to continue with focus mode or guided learning for the highest-signal nodes:',
|
|
1066
|
+
...topTitles,
|
|
1067
|
+
...(graphActionHints.length > 0 ? ['', 'Graph-aware follow-through:', ...graphActionHints] : []),
|
|
1068
|
+
...(actionHints.length > 0
|
|
1069
|
+
? ['', 'Suggested follow-through from the current turn:', ...actionHints]
|
|
1070
|
+
: []),
|
|
1071
|
+
].join('\n');
|
|
1072
|
+
}
|
|
1073
|
+
function buildKnowledgeRunClaimFromCitation(runId, citation, index) {
|
|
1074
|
+
const snippet = normalizeWhitespace(String(citation.snippet || '').trim());
|
|
1075
|
+
const hasSourcePath = normalizeWhitespace(String(citation.sourcePath || '')).length > 0;
|
|
1076
|
+
const hasLine = Number.isFinite(Number(citation.startLine)) && Number(citation.startLine) > 0;
|
|
1077
|
+
const status = hasSourcePath && snippet && hasLine
|
|
1078
|
+
? 'verified'
|
|
1079
|
+
: hasSourcePath && snippet
|
|
1080
|
+
? 'weak'
|
|
1081
|
+
: 'not_proven';
|
|
1082
|
+
return {
|
|
1083
|
+
claimId: `${runId}_claim_${index + 1}`,
|
|
1084
|
+
status,
|
|
1085
|
+
title: normalizeWhitespace(String(citation.title || '').trim()) || `Evidence claim ${index + 1}`,
|
|
1086
|
+
statement: snippet || normalizeWhitespace(String(citation.title || '').trim()) || 'Citation evidence was returned.',
|
|
1087
|
+
citationId: citation.citationId,
|
|
1088
|
+
atomId: citation.atomId,
|
|
1089
|
+
documentId: citation.documentId,
|
|
1090
|
+
sourcePath: citation.sourcePath,
|
|
1091
|
+
startLine: citation.startLine,
|
|
1092
|
+
endLine: citation.endLine,
|
|
1093
|
+
snippet,
|
|
1094
|
+
confidence: clampUnit(Number(citation.score || 0)),
|
|
1095
|
+
reason: status === 'verified'
|
|
1096
|
+
? 'The claim is backed by a cited source span with a concrete line reference.'
|
|
1097
|
+
: status === 'weak'
|
|
1098
|
+
? 'The claim has a cited source path and snippet, but no concrete line reference.'
|
|
1099
|
+
: 'The citation is missing enough source-span detail to prove the claim.',
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
function buildKnowledgeRunClaimFromPoint(runId, point, index) {
|
|
1103
|
+
const snippet = normalizeWhitespace(String(point.evidenceSnippet || point.summary || '').trim());
|
|
1104
|
+
return {
|
|
1105
|
+
claimId: `${runId}_claim_${index + 1}`,
|
|
1106
|
+
status: 'not_proven',
|
|
1107
|
+
title: normalizeWhitespace(String(point.title || '').trim()) || `Knowledge point ${index + 1}`,
|
|
1108
|
+
statement: snippet || normalizeWhitespace(String(point.title || '').trim()) || 'A knowledge point was returned without citation evidence.',
|
|
1109
|
+
atomId: point.atomId,
|
|
1110
|
+
documentId: point.documentId,
|
|
1111
|
+
sourcePath: point.sourcePath,
|
|
1112
|
+
snippet,
|
|
1113
|
+
confidence: clampUnit(Number(point.score || 0)),
|
|
1114
|
+
reason: 'The answer can point to a retrieved knowledge node, but no explicit citation span was returned.',
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function buildRejectedKnowledgeRunClaim(runId, message) {
|
|
1118
|
+
return {
|
|
1119
|
+
claimId: `${runId}_claim_1`,
|
|
1120
|
+
status: 'rejected',
|
|
1121
|
+
title: 'No scoped evidence',
|
|
1122
|
+
statement: `No scoped evidence proved the request: ${normalizeWhitespace(message || 'local knowledge request')}`,
|
|
1123
|
+
snippet: '',
|
|
1124
|
+
confidence: 0,
|
|
1125
|
+
reason: 'No citation or retrieved knowledge point was available for this turn.',
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function buildKnowledgeRunEvidenceClaims(runId, params) {
|
|
1129
|
+
const seenCitationKeys = new Set();
|
|
1130
|
+
const citationClaims = params.citations
|
|
1131
|
+
.filter((citation) => {
|
|
1132
|
+
const citationKey = [
|
|
1133
|
+
citation.citationId,
|
|
1134
|
+
citation.documentId,
|
|
1135
|
+
citation.sourcePath,
|
|
1136
|
+
citation.startLine || '',
|
|
1137
|
+
citation.endLine || '',
|
|
1138
|
+
citation.snippet,
|
|
1139
|
+
].join('|');
|
|
1140
|
+
if (seenCitationKeys.has(citationKey)) {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
seenCitationKeys.add(citationKey);
|
|
1144
|
+
return true;
|
|
1145
|
+
})
|
|
1146
|
+
.map((citation, index) => buildKnowledgeRunClaimFromCitation(runId, citation, index));
|
|
1147
|
+
if (citationClaims.length > 0) {
|
|
1148
|
+
return citationClaims;
|
|
1149
|
+
}
|
|
1150
|
+
const pointClaims = params.knowledgePoints
|
|
1151
|
+
.slice(0, 3)
|
|
1152
|
+
.map((point, index) => buildKnowledgeRunClaimFromPoint(runId, point, index));
|
|
1153
|
+
if (pointClaims.length > 0) {
|
|
1154
|
+
return pointClaims;
|
|
1155
|
+
}
|
|
1156
|
+
return [buildRejectedKnowledgeRunClaim(runId, params.message)];
|
|
1157
|
+
}
|
|
1158
|
+
function buildKnowledgeRunEvidenceRef(claim) {
|
|
1159
|
+
const sourcePath = normalizeWhitespace(String(claim.sourcePath || '').trim());
|
|
1160
|
+
if (!sourcePath) {
|
|
1161
|
+
return '';
|
|
1162
|
+
}
|
|
1163
|
+
const startLine = Number(claim.startLine);
|
|
1164
|
+
return Number.isFinite(startLine) && startLine > 0
|
|
1165
|
+
? `${sourcePath}:${startLine}`
|
|
1166
|
+
: sourcePath;
|
|
1167
|
+
}
|
|
1168
|
+
function buildKnowledgeRunReviewCards(runId, generatedAt, claims) {
|
|
1169
|
+
return claims
|
|
1170
|
+
.filter((claim) => claim.status === 'verified' || claim.status === 'weak')
|
|
1171
|
+
.slice(0, 3)
|
|
1172
|
+
.map((claim, index) => {
|
|
1173
|
+
const evidenceRef = buildKnowledgeRunEvidenceRef(claim);
|
|
1174
|
+
return {
|
|
1175
|
+
cardId: `${runId}_card_${index + 1}`,
|
|
1176
|
+
sourceClaimId: claim.claimId,
|
|
1177
|
+
atomId: claim.atomId,
|
|
1178
|
+
suggestedActionKind: 'review',
|
|
1179
|
+
prompt: `What does the cited source establish about ${claim.title}?`,
|
|
1180
|
+
expectedAnswer: claim.snippet || claim.statement,
|
|
1181
|
+
evidenceRefs: evidenceRef ? [evidenceRef] : [],
|
|
1182
|
+
nextReviewAt: addDaysIso(generatedAt, 1),
|
|
1183
|
+
};
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
function buildKnowledgeRunQuality(claims, reviewCards, params, graphContext) {
|
|
1187
|
+
const coveredClaimCount = claims.filter((claim) => claim.status === 'verified' || claim.status === 'weak').length;
|
|
1188
|
+
const evidenceCoverage = claims.length > 0 ? coveredClaimCount / claims.length : 0;
|
|
1189
|
+
const scopeDiscipline = params.usedScope.source === 'scoped'
|
|
1190
|
+
|| (params.usedScope.documentIds.length <= 0
|
|
1191
|
+
&& params.usedScope.atomIds.length <= 0
|
|
1192
|
+
&& params.usedScope.sourcePathPrefixes.length <= 0);
|
|
1193
|
+
const memoryActionsGoverned = params.memoryActions.every((action) => (Boolean(action.kind)
|
|
1194
|
+
&& Boolean(action.status)
|
|
1195
|
+
&& Boolean(action.layer)
|
|
1196
|
+
&& Boolean(action.namespace)));
|
|
1197
|
+
const intent = classifyScopedConversationIntent(params.message);
|
|
1198
|
+
const predecessorWindow = graphContext && Array.isArray(graphContext.predecessorWindow)
|
|
1199
|
+
? graphContext.predecessorWindow
|
|
1200
|
+
: [];
|
|
1201
|
+
const successorWindow = graphContext && Array.isArray(graphContext.successorWindow)
|
|
1202
|
+
? graphContext.successorWindow
|
|
1203
|
+
: [];
|
|
1204
|
+
const connectionPaths = graphContext && Array.isArray(graphContext.connectionPaths)
|
|
1205
|
+
? graphContext.connectionPaths
|
|
1206
|
+
: [];
|
|
1207
|
+
const diagnostics = graphContext && graphContext.diagnostics && typeof graphContext.diagnostics === 'object'
|
|
1208
|
+
? graphContext.diagnostics
|
|
1209
|
+
: null;
|
|
1210
|
+
const prerequisiteSignalPresent = predecessorWindow.length > 0
|
|
1211
|
+
|| connectionPaths.some((connectionPath) => (Array.isArray(connectionPath.pathEdges)
|
|
1212
|
+
&& connectionPath.pathEdges.some((edge) => edge && edge.relationKind === 'prerequisite')))
|
|
1213
|
+
|| Boolean(graphContext && graphContext.relationKinds.includes('prerequisite'));
|
|
1214
|
+
const prerequisiteOrderRequired = intent === 'how_to' || (intent === 'explain' && prerequisiteSignalPresent);
|
|
1215
|
+
const prerequisiteOrderPassed = !prerequisiteOrderRequired
|
|
1216
|
+
|| predecessorWindow.length > 0
|
|
1217
|
+
|| connectionPaths.some((connectionPath) => (Array.isArray(connectionPath.pathEdges)
|
|
1218
|
+
&& connectionPath.pathEdges.some((edge) => edge && edge.relationKind === 'prerequisite')));
|
|
1219
|
+
const comparisonBranchRequired = intent === 'compare';
|
|
1220
|
+
const comparisonBranchSignalPresent = Boolean(graphContext
|
|
1221
|
+
&& (graphContext.relationKinds.includes('contrast')
|
|
1222
|
+
|| graphContext.relationKinds.includes('analogy')
|
|
1223
|
+
|| (Array.isArray(graphContext.knowledgePointRelations)
|
|
1224
|
+
&& graphContext.knowledgePointRelations.some((relation) => (relation
|
|
1225
|
+
&& (relation.relationKind === 'contrast' || relation.relationKind === 'analogy'))))
|
|
1226
|
+
|| (Array.isArray(graphContext.supportingTitles) && graphContext.supportingTitles.length >= 2)
|
|
1227
|
+
|| connectionPaths.length >= 2));
|
|
1228
|
+
const comparisonBranchPassed = !comparisonBranchRequired
|
|
1229
|
+
|| comparisonBranchSignalPresent;
|
|
1230
|
+
const temporalWarningRequired = Boolean(graphContext
|
|
1231
|
+
&& (graphContext.temporalValidity.allPointsValid === false
|
|
1232
|
+
|| (Array.isArray(graphContext.temporalValidity.details)
|
|
1233
|
+
&& graphContext.temporalValidity.details.some((detail) => detail.edgeKind === 'supersedes'))));
|
|
1234
|
+
const temporalWarningPassed = !temporalWarningRequired
|
|
1235
|
+
|| Boolean(graphContext
|
|
1236
|
+
&& Array.isArray(graphContext.temporalValidity.warningReasons)
|
|
1237
|
+
&& graphContext.temporalValidity.warningReasons.length > 0);
|
|
1238
|
+
const graphOpFallbackPassed = !diagnostics
|
|
1239
|
+
|| diagnostics.graphOpsAvailable === true
|
|
1240
|
+
|| diagnostics.usedFallback === true;
|
|
1241
|
+
const supportNodeCount = diagnostics
|
|
1242
|
+
? Math.max(0, Math.floor(Number(diagnostics.supportNodeCount || 0)))
|
|
1243
|
+
: (graphContext && Array.isArray(graphContext.supportingAtomIds) ? graphContext.supportingAtomIds.length : 0);
|
|
1244
|
+
const supportNodeLimit = diagnostics
|
|
1245
|
+
? Math.max(1, Math.floor(Number(diagnostics.supportNodeLimit || supportNodeCount || 1)))
|
|
1246
|
+
: Math.max(supportNodeCount || 1, 1);
|
|
1247
|
+
const pathDepthLimit = diagnostics
|
|
1248
|
+
? Math.max(1, Math.floor(Number(diagnostics.pathDepthLimit || 1)))
|
|
1249
|
+
: 6;
|
|
1250
|
+
const graphBudgetPassed = connectionPaths.every((connectionPath) => Math.max(0, Math.floor(Number(connectionPath.length || 0))) <= pathDepthLimit)
|
|
1251
|
+
&& predecessorWindow.length <= Math.max(0, supportNodeLimit)
|
|
1252
|
+
&& successorWindow.length <= Math.max(0, supportNodeLimit)
|
|
1253
|
+
&& supportNodeCount <= supportNodeLimit;
|
|
1254
|
+
const gates = [
|
|
1255
|
+
{
|
|
1256
|
+
gateId: 'evidence_coverage',
|
|
1257
|
+
passed: evidenceCoverage >= 0.8,
|
|
1258
|
+
observedValue: Number(evidenceCoverage.toFixed(4)),
|
|
1259
|
+
threshold: 0.8,
|
|
1260
|
+
message: coveredClaimCount > 0
|
|
1261
|
+
? `${coveredClaimCount} of ${claims.length} claim(s) have citation evidence.`
|
|
1262
|
+
: 'No claim has enough citation evidence.',
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
gateId: 'scope_discipline',
|
|
1266
|
+
passed: scopeDiscipline,
|
|
1267
|
+
observedValue: scopeDiscipline ? 1 : 0,
|
|
1268
|
+
threshold: 1,
|
|
1269
|
+
message: scopeDiscipline
|
|
1270
|
+
? 'The answer stayed inside the resolved scope contract.'
|
|
1271
|
+
: 'The answer used a global result while scoped filters were active.',
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
gateId: 'recall_transfer',
|
|
1275
|
+
passed: reviewCards.length > 0,
|
|
1276
|
+
observedValue: reviewCards.length,
|
|
1277
|
+
threshold: 1,
|
|
1278
|
+
message: reviewCards.length > 0
|
|
1279
|
+
? `${reviewCards.length} review card(s) were generated from cited claims.`
|
|
1280
|
+
: 'No active-recall card could be generated from the available evidence.',
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
gateId: 'memory_governance',
|
|
1284
|
+
passed: memoryActionsGoverned,
|
|
1285
|
+
observedValue: memoryActionsGoverned ? 1 : 0,
|
|
1286
|
+
threshold: 1,
|
|
1287
|
+
message: memoryActionsGoverned
|
|
1288
|
+
? 'Memory actions include the governance fields needed for audit.'
|
|
1289
|
+
: 'At least one memory action is missing governance metadata.',
|
|
1290
|
+
},
|
|
1291
|
+
{
|
|
1292
|
+
gateId: 'graph_prerequisite_order',
|
|
1293
|
+
passed: prerequisiteOrderPassed,
|
|
1294
|
+
observedValue: prerequisiteOrderPassed ? 1 : 0,
|
|
1295
|
+
threshold: prerequisiteOrderRequired ? 1 : 0,
|
|
1296
|
+
message: prerequisiteOrderRequired
|
|
1297
|
+
? (prerequisiteOrderPassed
|
|
1298
|
+
? 'Prerequisite-oriented queries retained upstream path order or predecessor context.'
|
|
1299
|
+
: 'The current graph context did not preserve enough prerequisite ordering for this query.')
|
|
1300
|
+
: 'No prerequisite-ordering requirement was active for this query.',
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
gateId: 'graph_comparison_branch',
|
|
1304
|
+
passed: comparisonBranchPassed,
|
|
1305
|
+
observedValue: comparisonBranchPassed ? 1 : 0,
|
|
1306
|
+
threshold: comparisonBranchRequired ? 1 : 0,
|
|
1307
|
+
message: comparisonBranchRequired
|
|
1308
|
+
? (comparisonBranchPassed
|
|
1309
|
+
? 'Comparison intent retained support nodes or branch structure in the graph context.'
|
|
1310
|
+
: 'Comparison intent did not retain enough branch structure in the graph context.')
|
|
1311
|
+
: 'No comparison-branch requirement was active for this query.',
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
gateId: 'graph_temporal_warning',
|
|
1315
|
+
passed: temporalWarningPassed,
|
|
1316
|
+
observedValue: temporalWarningPassed ? 1 : 0,
|
|
1317
|
+
threshold: temporalWarningRequired ? 1 : 0,
|
|
1318
|
+
message: temporalWarningRequired
|
|
1319
|
+
? (temporalWarningPassed
|
|
1320
|
+
? 'Temporal invalidity or supersession was surfaced as an explicit warning.'
|
|
1321
|
+
: 'Temporal invalidity was present but not surfaced as an explicit warning.')
|
|
1322
|
+
: 'No temporal warning was required for this query.',
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
gateId: 'graph_op_fallback',
|
|
1326
|
+
passed: graphOpFallbackPassed,
|
|
1327
|
+
observedValue: graphOpFallbackPassed ? 1 : 0,
|
|
1328
|
+
threshold: 1,
|
|
1329
|
+
message: diagnostics
|
|
1330
|
+
? (diagnostics.graphOpsAvailable === true
|
|
1331
|
+
? 'Graph operations were available for this turn.'
|
|
1332
|
+
: diagnostics.usedFallback === true
|
|
1333
|
+
? 'Graph operations fell back cleanly without breaking the answer contract.'
|
|
1334
|
+
: 'Graph operation availability and fallback state were inconsistent.')
|
|
1335
|
+
: 'No graph diagnostics payload was available; compatibility fallback accepted.',
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
gateId: 'graph_budget',
|
|
1339
|
+
passed: graphBudgetPassed,
|
|
1340
|
+
observedValue: graphBudgetPassed ? 1 : 0,
|
|
1341
|
+
threshold: 1,
|
|
1342
|
+
message: graphBudgetPassed
|
|
1343
|
+
? 'Graph context stayed within bounded support/path/window budgets.'
|
|
1344
|
+
: 'Graph context exceeded the bounded support/path/window budgets for this turn.',
|
|
1345
|
+
},
|
|
1346
|
+
];
|
|
1347
|
+
const passedGateCount = gates.filter((gate) => gate.passed).length;
|
|
1348
|
+
const score = Number(((passedGateCount / gates.length) * 100).toFixed(2));
|
|
1349
|
+
const status = gates.every((gate) => gate.passed)
|
|
1350
|
+
? 'pass'
|
|
1351
|
+
: gates[0].passed && gates[1].passed
|
|
1352
|
+
? 'caution'
|
|
1353
|
+
: 'fail';
|
|
1354
|
+
return {
|
|
1355
|
+
score,
|
|
1356
|
+
status,
|
|
1357
|
+
gates,
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function buildKnowledgeRunReviewState(reviewCards) {
|
|
1361
|
+
const totalReviewCards = reviewCards.length;
|
|
1362
|
+
return {
|
|
1363
|
+
consumedCardIds: [],
|
|
1364
|
+
completedReviewCardCount: 0,
|
|
1365
|
+
remainingReviewCardCount: totalReviewCards,
|
|
1366
|
+
completedAt: null,
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function buildKnowledgeRun(params, graphContext) {
|
|
1370
|
+
const generatedAt = String(params.generatedAt || new Date().toISOString()).trim();
|
|
1371
|
+
const runId = params.nextRunId ? params.nextRunId() : buildFallbackKnowledgeRunId(params, generatedAt);
|
|
1372
|
+
const evidenceClaims = buildKnowledgeRunEvidenceClaims(runId, params);
|
|
1373
|
+
const reviewCards = buildKnowledgeRunReviewCards(runId, generatedAt, evidenceClaims);
|
|
1374
|
+
const reviewState = buildKnowledgeRunReviewState(reviewCards);
|
|
1375
|
+
const quality = buildKnowledgeRunQuality(evidenceClaims, reviewCards, params, graphContext);
|
|
1376
|
+
const countStatus = (status) => (evidenceClaims.filter((claim) => claim.status === status).length);
|
|
1377
|
+
return {
|
|
1378
|
+
runId,
|
|
1379
|
+
generatedAt,
|
|
1380
|
+
status: quality.status,
|
|
1381
|
+
scope: {
|
|
1382
|
+
source: params.usedScope.source,
|
|
1383
|
+
workspaceId: params.usedScope.workspaceId,
|
|
1384
|
+
corpusId: params.usedScope.corpusId,
|
|
1385
|
+
documentIds: [...params.usedScope.documentIds],
|
|
1386
|
+
atomIds: [...params.usedScope.atomIds],
|
|
1387
|
+
sourcePathPrefixes: [...params.usedScope.sourcePathPrefixes],
|
|
1388
|
+
languages: [...params.usedScope.languages],
|
|
1389
|
+
matchedAtomCount: params.usedScope.matchedAtomCount,
|
|
1390
|
+
scopeSource: params.usedScope.scopeSource,
|
|
1391
|
+
},
|
|
1392
|
+
evidenceClaims,
|
|
1393
|
+
quality,
|
|
1394
|
+
reviewCards,
|
|
1395
|
+
reviewState,
|
|
1396
|
+
summary: {
|
|
1397
|
+
claimCount: evidenceClaims.length,
|
|
1398
|
+
verifiedClaimCount: countStatus('verified'),
|
|
1399
|
+
weakClaimCount: countStatus('weak'),
|
|
1400
|
+
notProvenClaimCount: countStatus('not_proven'),
|
|
1401
|
+
rejectedClaimCount: countStatus('rejected'),
|
|
1402
|
+
reviewCardCount: reviewCards.length,
|
|
1403
|
+
completedReviewCardCount: reviewState.completedReviewCardCount,
|
|
1404
|
+
remainingReviewCardCount: reviewState.remainingReviewCardCount,
|
|
1405
|
+
},
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
function buildScopedConversationReply(params) {
|
|
1409
|
+
const blocks = [];
|
|
1410
|
+
const graphContext = params.graphContext || (0, graphContextAssembler_1.buildAgentConversationGraphContextFromKnowledgePoints)(params.knowledgePoints);
|
|
1411
|
+
const draftAnswer = buildScopedConversationAnswer(params, graphContext);
|
|
1412
|
+
const knowledgeRun = buildKnowledgeRun(params, graphContext);
|
|
1413
|
+
const answerReleaseReview = (0, answerReleaseReview_1.reviewAnswerRelease)({
|
|
1414
|
+
message: params.message,
|
|
1415
|
+
draftAnswer,
|
|
1416
|
+
knowledgePoints: params.knowledgePoints,
|
|
1417
|
+
citations: params.citations,
|
|
1418
|
+
usedScope: params.usedScope,
|
|
1419
|
+
graphContext,
|
|
1420
|
+
ragContextPack: params.ragContextPack,
|
|
1421
|
+
ragSufficiencyReview: params.ragSufficiencyReview,
|
|
1422
|
+
reviewedAt: params.generatedAt,
|
|
1423
|
+
});
|
|
1424
|
+
knowledgeRun.answerReleaseReview = answerReleaseReview;
|
|
1425
|
+
const answer = answerReleaseReview.publicAnswer;
|
|
1426
|
+
const overviewMarkdown = buildScopedConversationOverviewMarkdown(params, graphContext);
|
|
1427
|
+
const explanationMarkdown = buildScopedConversationExplanationMarkdown(params, graphContext);
|
|
1428
|
+
const evidenceMarkdown = buildScopedConversationEvidenceMarkdown(params);
|
|
1429
|
+
const memoryNotice = buildScopedConversationMemoryNotice(params);
|
|
1430
|
+
const actionGuideMarkdown = buildScopedConversationActionGuideMarkdown(params, graphContext);
|
|
1431
|
+
blocks.push({
|
|
1432
|
+
blockId: params.nextBlockId(),
|
|
1433
|
+
type: 'structured_answer',
|
|
1434
|
+
title: 'Grounded Answer',
|
|
1435
|
+
directAnswer: answer,
|
|
1436
|
+
overviewMarkdown,
|
|
1437
|
+
explanationMarkdown,
|
|
1438
|
+
evidenceMarkdown,
|
|
1439
|
+
nextActionsMarkdown: params.knowledgePoints.length > 0 ? actionGuideMarkdown : undefined,
|
|
1440
|
+
knowledgePointCount: params.knowledgePoints.length,
|
|
1441
|
+
citationCount: params.citations.length,
|
|
1442
|
+
recalledMemoryCount: params.recalledMemories.length,
|
|
1443
|
+
});
|
|
1444
|
+
blocks.push({
|
|
1445
|
+
blockId: params.nextBlockId(),
|
|
1446
|
+
type: 'knowledge_run_summary',
|
|
1447
|
+
title: 'Knowledge Run',
|
|
1448
|
+
knowledgeRun,
|
|
1449
|
+
});
|
|
1450
|
+
if (memoryNotice) {
|
|
1451
|
+
blocks.push({
|
|
1452
|
+
blockId: params.nextBlockId(),
|
|
1453
|
+
type: 'system_notice',
|
|
1454
|
+
text: memoryNotice,
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
if (params.citations.length > 0) {
|
|
1458
|
+
blocks.push({
|
|
1459
|
+
blockId: params.nextBlockId(),
|
|
1460
|
+
type: 'citations',
|
|
1461
|
+
title: 'Citations',
|
|
1462
|
+
citations: params.citations.map((citation) => ({ ...citation })),
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
if (params.knowledgePoints.length > 0) {
|
|
1466
|
+
blocks.push({
|
|
1467
|
+
blockId: params.nextBlockId(),
|
|
1468
|
+
type: 'knowledge_actions',
|
|
1469
|
+
title: 'Knowledge Actions',
|
|
1470
|
+
atomIds: collectAgentConversationAtomIds(params.knowledgePoints),
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
return {
|
|
1474
|
+
answer,
|
|
1475
|
+
assistantBlocks: blocks,
|
|
1476
|
+
knowledgeRun,
|
|
1477
|
+
graphContext,
|
|
1478
|
+
answerReleaseReview,
|
|
1479
|
+
};
|
|
1480
|
+
}
|