noteconnection 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +257 -1803
- package/dist/src/agent_workspace.contract.parity.test.js +105 -421
- package/dist/src/agent_workspace.frontend.test.js +410 -11
- 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/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 +265 -2
- 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 +44 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
- package/dist/src/foundation.release.evidence.contract.test.js +127 -9
- package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
- package/dist/src/frontend/README.md +257 -1803
- package/dist/src/frontend/agent_workspace.js +665 -7
- package/dist/src/frontend/index.html +31 -2
- package/dist/src/frontend/knowledge_projection_contract.js +230 -0
- package/dist/src/frontend/knowledge_projection_store.js +214 -0
- package/dist/src/frontend/libs/path_core.js +158 -42
- package/dist/src/frontend/locales/en.json +32 -0
- package/dist/src/frontend/locales/zh.json +32 -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/path.html +6 -2
- package/dist/src/frontend/simulationWorker.js +1 -1
- package/dist/src/frontend/source_manager.js +110 -4
- package/dist/src/frontend/storage_provider.js +661 -71
- package/dist/src/frontend/styles.css +107 -13
- package/dist/src/frontend/workspace_panes.js +111 -7
- package/dist/src/frontend.runtime.assets.contract.test.js +81 -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 +22 -0
- package/dist/src/indexing/IndexLifecycle.test.js +24 -0
- package/dist/src/knowledge.api.contract.test.js +4 -2
- 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 +4687 -3892
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
- 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 +1191 -250
- package/dist/src/learning/answerReleaseReview.test.js +1293 -7
- package/dist/src/learning/answerTaskPlan.js +473 -0
- package/dist/src/learning/conversationComposer.js +627 -386
- package/dist/src/learning/conversationComposer.test.js +1403 -0
- package/dist/src/learning/evidenceContextAssembler.js +312 -120
- 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 +3 -13
- package/dist/src/learning/graphContextAssembler.test.js +2 -2
- 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 +1 -0
- package/dist/src/learning/mobileAnswerProjection.js +104 -0
- package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
- package/dist/src/learning/queryBackend.js +34 -10
- package/dist/src/learning/queryBackend.test.js +21 -0
- package/dist/src/learning/ragContextPack.js +115 -16
- package/dist/src/learning/ragContextPack.test.js +151 -0
- package/dist/src/learning/ragEvidenceQuality.js +96 -0
- package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
- package/dist/src/learning/ragPublicText.js +67 -4
- package/dist/src/learning/ragPublicText.test.js +57 -0
- package/dist/src/learning/ragSufficiencyJudge.js +11 -1
- package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
- package/dist/src/learning/requestNormalization.js +106 -0
- package/dist/src/learning/requestNormalization.test.js +101 -0
- package/dist/src/learning/store.js +116 -12
- package/dist/src/learning/store.test.js +299 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
- package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
- package/dist/src/learning/workspaceHydration.js +168 -0
- package/dist/src/learning/workspaceHydration.test.js +30 -0
- package/dist/src/license.policy.contract.test.js +9 -4
- package/dist/src/markdown/MarkdownGateway.js +4 -0
- package/dist/src/markdown/MarkdownGateway.test.js +59 -0
- package/dist/src/middleware/SseResponseWriter.js +122 -0
- package/dist/src/middleware/SseResponseWriter.test.js +54 -0
- package/dist/src/middleware/auth.js +28 -7
- package/dist/src/middleware/auth.test.js +33 -0
- package/dist/src/middleware/index.js +2 -1
- 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/ContentGenerator.js +4 -9
- package/dist/src/notemd/FileProcessor.js +4 -9
- package/dist/src/notemd/NotemdService.js +12 -17
- package/dist/src/notemd/Translator.js +4 -9
- package/dist/src/notemd/progressReporter.js +9 -0
- package/dist/src/notemd.server.integration.test.js +119 -4
- package/dist/src/notemd.workflow.pipeline.test.js +1 -1
- package/dist/src/pathbridge.handshake.contract.test.js +29 -0
- package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
- package/dist/src/pathcore.bundle.contract.test.js +8 -0
- package/dist/src/platform/ExportProfile.js +28 -0
- package/dist/src/platform/MobileBudget.js +11 -0
- package/dist/src/platform/PlatformCapabilities.js +14 -0
- package/dist/src/platform/PlatformCapabilities.test.js +6 -0
- package/dist/src/query_backend.external_http.integration.test.js +2 -2
- package/dist/src/release.godot.mirror.contract.test.js +28 -4
- package/dist/src/resources/ResourceRegistry.js +40 -0
- package/dist/src/resources/ResourceRegistry.test.js +36 -0
- package/dist/src/routes/index.js +0 -4
- package/dist/src/routes/knowledge.js +46 -33
- package/dist/src/routes/knowledgePayload.js +185 -0
- package/dist/src/routes/knowledgePayload.test.js +53 -0
- package/dist/src/routes/markdown.js +8 -0
- package/dist/src/routes/notemd.js +990 -234
- package/dist/src/routes/registry.contract.test.js +5 -2
- package/dist/src/routes/registry.shadow.contract.test.js +22 -0
- package/dist/src/routes/render.js +14 -51
- package/dist/src/runtime.capabilities.test.js +18 -2
- package/dist/src/server.js +566 -1090
- package/dist/src/server.migration.test.js +417 -15
- 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 +6 -3
- package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
- package/dist/src/simulation.worker.offline.test.js +104 -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 +8 -0
- package/dist/src/tauri.window.evidence.execution.test.js +68 -0
- package/dist/src/utils/RuntimePaths.js +6 -0
- package/dist/src/utils/RuntimePaths.test.js +27 -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/workspace/WorkspaceRegistry.js +13 -0
- package/package.json +32 -10
- package/dist/src/routes/data.js +0 -267
- package/dist/src/routes/diagnostics.js +0 -51
|
@@ -38,217 +38,757 @@ const CrashLogger_1 = require("../backend/utils/CrashLogger");
|
|
|
38
38
|
const capabilityManifest_1 = require("../notemd/operations/capabilityManifest");
|
|
39
39
|
const cliContracts_1 = require("../notemd/operations/cliContracts");
|
|
40
40
|
const registry_1 = require("../notemd/operations/registry");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const AppConfigToml_1 = require("../notemd/AppConfigToml");
|
|
44
|
+
const providerTemplates_1 = require("../notemd/providerTemplates");
|
|
45
|
+
class NotemdOperationAdmissionError extends Error {
|
|
46
|
+
constructor(statusCode, code) {
|
|
47
|
+
super(code);
|
|
48
|
+
this.statusCode = statusCode;
|
|
49
|
+
this.code = code;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
41
52
|
function registerNotemdRoutes(ctx) {
|
|
42
|
-
const { notemdService, loadNotemdSettings, persistNotemdSettings } = ctx;
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
53
|
+
const { notemdService, loadNotemdSettings, persistNotemdSettings, LOOPBACK_HOST, finalPort } = ctx;
|
|
54
|
+
const { readJsonBody, writeBodyParseErrorResponse: writeIngressError, writeApiErrorResponse, resolvePathWithinKnowledgeBase, writeSseEvent, finishSseResponse, llmClient: notemdLlmClient } = ctx.notemdHttp;
|
|
55
|
+
const NOTEMD_ACTIVE_OPERATIONS = new Map();
|
|
56
|
+
const api = (pathname) => '/api/notemd' + pathname;
|
|
57
|
+
const json = (res, code, data) => { res.writeHead(code, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data)); };
|
|
48
58
|
const ok = (res, data) => json(res, 200, { success: true, ...data });
|
|
59
|
+
const writeBodyParseErrorResponse = (res, error) => {
|
|
60
|
+
if (error instanceof NotemdOperationAdmissionError) {
|
|
61
|
+
json(res, error.statusCode, { success: false, error: error.message, errorCode: error.code });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (error?.name === 'AbortError' && !res.headersSent) {
|
|
65
|
+
json(res, 499, { success: false, error: 'operation_cancelled', errorCode: 'operation_cancelled' });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return writeIngressError(res, error);
|
|
69
|
+
};
|
|
49
70
|
const fail = (res, error, label) => {
|
|
71
|
+
if (writeBodyParseErrorResponse(res, error))
|
|
72
|
+
return;
|
|
73
|
+
if (isAccessDeniedError(error)) {
|
|
74
|
+
json(res, 403, { success: false, error: String(error) });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
50
77
|
console.error(error);
|
|
51
78
|
CrashLogger_1.CrashLogger.log(error, label);
|
|
52
79
|
json(res, 500, { success: false, error: String(error) });
|
|
53
80
|
};
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
function isAccessDeniedError(error) {
|
|
82
|
+
const code = error?.code;
|
|
83
|
+
return code === 'EACCES' || code === 'EPERM';
|
|
84
|
+
}
|
|
85
|
+
function isObjectRecord(value) {
|
|
86
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
87
|
+
}
|
|
88
|
+
function extractNotemdWorkspaceState(settings) {
|
|
89
|
+
return {
|
|
90
|
+
filePath: String(settings.workspaceFilePath || '').trim(),
|
|
91
|
+
folderPath: String(settings.workspaceFolderPath || '').trim(),
|
|
92
|
+
outputFilePath: String(settings.workspaceOutputFilePath || '').trim(),
|
|
93
|
+
outputFolderPath: String(settings.workspaceOutputFolderPath || '').trim(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function normalizeWorkspaceField(source, keys, fallback) {
|
|
97
|
+
for (const key of keys) {
|
|
98
|
+
if (!Object.prototype.hasOwnProperty.call(source, key)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
return String(source[key] || '').trim();
|
|
102
|
+
}
|
|
103
|
+
return fallback;
|
|
104
|
+
}
|
|
105
|
+
function applyWorkspacePatchToSettings(settings, workspacePatch) {
|
|
106
|
+
const next = { ...settings };
|
|
107
|
+
if (!isObjectRecord(workspacePatch)) {
|
|
108
|
+
return next;
|
|
109
|
+
}
|
|
110
|
+
next.workspaceFilePath = normalizeWorkspaceField(workspacePatch, ['filePath', 'file_path', 'workspaceFilePath', 'workspace_file_path'], next.workspaceFilePath);
|
|
111
|
+
next.workspaceFolderPath = normalizeWorkspaceField(workspacePatch, ['folderPath', 'folder_path', 'workspaceFolderPath', 'workspace_folder_path'], next.workspaceFolderPath);
|
|
112
|
+
next.workspaceOutputFilePath = normalizeWorkspaceField(workspacePatch, ['outputFilePath', 'output_file_path', 'workspaceOutputFilePath', 'workspace_output_file_path'], next.workspaceOutputFilePath);
|
|
113
|
+
next.workspaceOutputFolderPath = normalizeWorkspaceField(workspacePatch, ['outputFolderPath', 'output_folder_path', 'workspaceOutputFolderPath', 'workspace_output_folder_path'], next.workspaceOutputFolderPath);
|
|
114
|
+
return next;
|
|
115
|
+
}
|
|
116
|
+
async function persistNotemdWorkspacePatch(workspacePatch) {
|
|
117
|
+
const settings = await loadNotemdSettings();
|
|
118
|
+
const nextSettings = applyWorkspacePatchToSettings(settings, workspacePatch);
|
|
119
|
+
const persisted = await persistNotemdSettings(nextSettings);
|
|
120
|
+
return extractNotemdWorkspaceState(persisted);
|
|
121
|
+
}
|
|
122
|
+
async function ensureNotemdProviderTemplatesPersisted() {
|
|
123
|
+
const appConfig = await (0, AppConfigToml_1.loadAppConfigToml)();
|
|
124
|
+
const currentNotemdSection = isObjectRecord(appConfig.notemd) ? appConfig.notemd : {};
|
|
125
|
+
const nextNotemdSection = (0, providerTemplates_1.mergeProviderTemplatesIntoNotemdSection)(currentNotemdSection);
|
|
126
|
+
const persisted = JSON.stringify(currentNotemdSection) !== JSON.stringify(nextNotemdSection);
|
|
127
|
+
if (persisted) {
|
|
128
|
+
await (0, AppConfigToml_1.saveAppConfigToml)({
|
|
129
|
+
...appConfig,
|
|
130
|
+
notemd: nextNotemdSection,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
configPath: (0, AppConfigToml_1.resolveAppConfigPath)(),
|
|
135
|
+
persisted,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function generateNotemdOperationId() {
|
|
139
|
+
return `notemd-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
140
|
+
}
|
|
141
|
+
function createNotemdOperation(operationIdCandidate, res) {
|
|
142
|
+
const requestedId = String(operationIdCandidate || '').trim();
|
|
143
|
+
if (requestedId.length > 160)
|
|
144
|
+
throw new NotemdOperationAdmissionError(400, 'invalid_operation_id');
|
|
145
|
+
const operationId = requestedId || generateNotemdOperationId();
|
|
146
|
+
if (NOTEMD_ACTIVE_OPERATIONS.has(operationId))
|
|
147
|
+
throw new NotemdOperationAdmissionError(409, 'notemd_operation_conflict');
|
|
148
|
+
if (Array.from(NOTEMD_ACTIVE_OPERATIONS.values()).filter(operation => operation.status === 'running').length >= 8) {
|
|
149
|
+
throw new NotemdOperationAdmissionError(429, 'notemd_operation_capacity');
|
|
150
|
+
}
|
|
151
|
+
for (const [id, operation] of NOTEMD_ACTIVE_OPERATIONS) {
|
|
152
|
+
if (NOTEMD_ACTIVE_OPERATIONS.size < 256)
|
|
153
|
+
break;
|
|
154
|
+
if (operation.status !== 'running')
|
|
155
|
+
NOTEMD_ACTIVE_OPERATIONS.delete(id);
|
|
156
|
+
}
|
|
157
|
+
const state = {
|
|
158
|
+
id: operationId,
|
|
159
|
+
controller: new AbortController(),
|
|
160
|
+
status: 'running',
|
|
161
|
+
createdAt: Date.now(),
|
|
162
|
+
updatedAt: Date.now(),
|
|
163
|
+
logs: [],
|
|
164
|
+
};
|
|
165
|
+
NOTEMD_ACTIVE_OPERATIONS.set(operationId, state);
|
|
166
|
+
res.once('close', () => {
|
|
167
|
+
if (!res.writableFinished && state.status === 'running')
|
|
168
|
+
state.controller.abort();
|
|
169
|
+
});
|
|
170
|
+
return state;
|
|
171
|
+
}
|
|
172
|
+
function finalizeNotemdOperation(state, status) {
|
|
173
|
+
if (state.status !== 'running')
|
|
174
|
+
return;
|
|
175
|
+
state.status = status;
|
|
176
|
+
state.updatedAt = Date.now();
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
const current = NOTEMD_ACTIVE_OPERATIONS.get(state.id);
|
|
179
|
+
if (current === state && current.status !== 'running') {
|
|
180
|
+
NOTEMD_ACTIVE_OPERATIONS.delete(state.id);
|
|
181
|
+
}
|
|
182
|
+
}, 60000).unref();
|
|
183
|
+
}
|
|
184
|
+
function createNotemdReporter(state, res) {
|
|
185
|
+
return {
|
|
186
|
+
report: (eventLike) => {
|
|
187
|
+
const event = {
|
|
188
|
+
...eventLike,
|
|
189
|
+
operationId: state.id,
|
|
190
|
+
timestamp: Date.now(),
|
|
191
|
+
};
|
|
192
|
+
if (state.logs.length >= 256)
|
|
193
|
+
state.logs.shift();
|
|
194
|
+
state.logs.push(event);
|
|
195
|
+
state.updatedAt = event.timestamp;
|
|
196
|
+
if (res) {
|
|
197
|
+
writeSseEvent(res, event.type, event);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
isCancelled: () => state.controller.signal.aborted,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function shouldStreamNotemdResponse(req) {
|
|
204
|
+
const acceptHeader = typeof req.headers.accept === 'string' ? req.headers.accept : '';
|
|
205
|
+
if (acceptHeader.includes('text/event-stream')) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const urlObj = new URL(req.url || '/', `http://${LOOPBACK_HOST}:${finalPort}`);
|
|
210
|
+
return urlObj.searchParams.get('stream') === '1';
|
|
211
|
+
}
|
|
212
|
+
catch (_error) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const routes = [
|
|
217
|
+
{ method: 'GET', path: '/api/notemd/settings', handler: async (req, res) => {
|
|
218
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
65
219
|
try {
|
|
66
220
|
const settings = await loadNotemdSettings();
|
|
67
|
-
|
|
221
|
+
const activeOperationCount = Array.from(NOTEMD_ACTIVE_OPERATIONS.values()).filter((operation) => operation.status === 'running').length;
|
|
222
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
223
|
+
res.end(JSON.stringify({
|
|
224
|
+
success: true,
|
|
225
|
+
settings,
|
|
226
|
+
operationSummary: {
|
|
227
|
+
total: NOTEMD_ACTIVE_OPERATIONS.size,
|
|
228
|
+
running: activeOperationCount,
|
|
229
|
+
},
|
|
230
|
+
}));
|
|
68
231
|
}
|
|
69
|
-
catch (
|
|
70
|
-
|
|
232
|
+
catch (error) {
|
|
233
|
+
writeApiErrorResponse(res, error, {
|
|
234
|
+
context: 'API:GET /api/notemd/settings',
|
|
235
|
+
requestId,
|
|
236
|
+
});
|
|
71
237
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
path: api('/settings'),
|
|
77
|
-
handler: async (req, res) => {
|
|
238
|
+
return;
|
|
239
|
+
} },
|
|
240
|
+
{ method: 'POST', path: '/api/notemd/settings', handler: async (req, res) => {
|
|
241
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
78
242
|
try {
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const nextSettings = payload && typeof payload === 'object' && payload.settings !== undefined
|
|
243
|
+
const payload = await readJsonBody(req);
|
|
244
|
+
const settingsCandidate = isObjectRecord(payload) && payload.settings !== undefined
|
|
82
245
|
? payload.settings
|
|
83
246
|
: payload;
|
|
84
|
-
const settings = persistNotemdSettings
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
247
|
+
const settings = await persistNotemdSettings(settingsCandidate);
|
|
248
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
249
|
+
res.end(JSON.stringify({ success: true, settings }));
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
writeApiErrorResponse(res, error, {
|
|
253
|
+
context: 'API:POST /api/notemd/settings',
|
|
254
|
+
requestId,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
} },
|
|
259
|
+
{ method: 'POST', path: '/api/notemd/test-llm', handler: async (req, res) => {
|
|
260
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
98
261
|
try {
|
|
99
|
-
const
|
|
262
|
+
const payload = await readJsonBody(req);
|
|
100
263
|
const settings = await loadNotemdSettings();
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
264
|
+
const providerName = String(payload.providerName || settings.activeProvider).trim();
|
|
265
|
+
const provider = settings.providers.find((item) => item.name === providerName);
|
|
266
|
+
if (!provider) {
|
|
267
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
268
|
+
res.end(JSON.stringify({ success: false, error: `Unknown provider: ${providerName}` }));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const result = await notemdLlmClient.testConnection(provider);
|
|
272
|
+
const statusCode = result.success ? 200 : 400;
|
|
273
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
274
|
+
res.end(JSON.stringify(result));
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
console.error(error);
|
|
281
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/test-llm');
|
|
282
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
283
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
} },
|
|
287
|
+
{ method: 'POST', path: '/api/notemd/process-file', handler: async (req, res) => {
|
|
288
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
289
|
+
const streamEnabled = shouldStreamNotemdResponse(req);
|
|
290
|
+
let operation = null;
|
|
113
291
|
try {
|
|
114
|
-
const
|
|
292
|
+
const payload = await readJsonBody(req);
|
|
115
293
|
const settings = await loadNotemdSettings();
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
294
|
+
operation = createNotemdOperation(payload.operationId, res);
|
|
295
|
+
const reporter = createNotemdReporter(operation, streamEnabled ? res : undefined);
|
|
296
|
+
if (streamEnabled) {
|
|
297
|
+
res.writeHead(200, {
|
|
298
|
+
'Content-Type': 'text/event-stream',
|
|
299
|
+
'Cache-Control': 'no-cache',
|
|
300
|
+
Connection: 'keep-alive',
|
|
301
|
+
});
|
|
302
|
+
writeSseEvent(res, 'operation', {
|
|
303
|
+
operationId: operation.id,
|
|
304
|
+
status: operation.status,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const resolvedFilePath = await resolvePathWithinKnowledgeBase(payload.filePath, {
|
|
308
|
+
expectedType: 'file',
|
|
309
|
+
});
|
|
310
|
+
const resolvedOutputPath = payload.outputPath
|
|
311
|
+
? await resolvePathWithinKnowledgeBase(payload.outputPath, {
|
|
312
|
+
expectedType: 'any',
|
|
313
|
+
allowMissing: true,
|
|
314
|
+
})
|
|
315
|
+
: undefined;
|
|
316
|
+
const result = await notemdService.processFile({
|
|
317
|
+
filePath: resolvedFilePath,
|
|
318
|
+
outputPath: resolvedOutputPath,
|
|
319
|
+
createConceptNotes: payload.createConceptNotes === true,
|
|
320
|
+
dryRun: payload.dryRun === true,
|
|
321
|
+
}, settings, reporter, operation.controller.signal);
|
|
322
|
+
operation.controller.signal.throwIfAborted();
|
|
323
|
+
await persistNotemdWorkspacePatch({
|
|
324
|
+
filePath: resolvedFilePath,
|
|
325
|
+
folderPath: path.dirname(resolvedFilePath),
|
|
326
|
+
outputFilePath: resolvedOutputPath || result.outputPath || '',
|
|
327
|
+
outputFolderPath: path.dirname(resolvedOutputPath || result.outputPath || resolvedFilePath),
|
|
328
|
+
}).catch((workspaceError) => {
|
|
329
|
+
console.warn('[NoteMD] Failed to persist workspace state after process-file.', workspaceError);
|
|
330
|
+
});
|
|
331
|
+
finalizeNotemdOperation(operation, 'done');
|
|
332
|
+
if (streamEnabled) {
|
|
333
|
+
writeSseEvent(res, 'done', {
|
|
334
|
+
success: true,
|
|
335
|
+
operationId: operation.id,
|
|
336
|
+
result,
|
|
337
|
+
});
|
|
338
|
+
await finishSseResponse(res);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
342
|
+
res.end(JSON.stringify({
|
|
343
|
+
success: true,
|
|
344
|
+
operationId: operation.id,
|
|
345
|
+
result,
|
|
346
|
+
logs: operation.logs,
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (operation) {
|
|
352
|
+
finalizeNotemdOperation(operation, operation.controller.signal.aborted ? 'cancelled' : 'error');
|
|
353
|
+
}
|
|
354
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (isAccessDeniedError(error)) {
|
|
358
|
+
const statusCode = operation?.controller.signal.aborted ? 499 : 403;
|
|
359
|
+
const payload = { success: false, error: String(error.message || 'Access denied') };
|
|
360
|
+
if (streamEnabled) {
|
|
361
|
+
writeSseEvent(res, 'error', payload);
|
|
362
|
+
await finishSseResponse(res);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
366
|
+
res.end(JSON.stringify(payload));
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
console.error(error);
|
|
371
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/process-file');
|
|
372
|
+
const payload = { success: false, error: String(error) };
|
|
373
|
+
if (streamEnabled) {
|
|
374
|
+
writeSseEvent(res, 'error', payload);
|
|
375
|
+
await finishSseResponse(res);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
379
|
+
res.end(JSON.stringify(payload));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
} },
|
|
384
|
+
{ method: 'POST', path: '/api/notemd/process-folder', handler: async (req, res) => {
|
|
385
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
386
|
+
const streamEnabled = shouldStreamNotemdResponse(req);
|
|
387
|
+
let operation = null;
|
|
128
388
|
try {
|
|
129
|
-
const
|
|
389
|
+
const payload = await readJsonBody(req);
|
|
130
390
|
const settings = await loadNotemdSettings();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
391
|
+
operation = createNotemdOperation(payload.operationId, res);
|
|
392
|
+
const reporter = createNotemdReporter(operation, streamEnabled ? res : undefined);
|
|
393
|
+
if (streamEnabled) {
|
|
394
|
+
res.writeHead(200, {
|
|
395
|
+
'Content-Type': 'text/event-stream',
|
|
396
|
+
'Cache-Control': 'no-cache',
|
|
397
|
+
Connection: 'keep-alive',
|
|
398
|
+
});
|
|
399
|
+
writeSseEvent(res, 'operation', {
|
|
400
|
+
operationId: operation.id,
|
|
401
|
+
status: operation.status,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
const resolvedFolderPath = await resolvePathWithinKnowledgeBase(payload.folderPath, {
|
|
405
|
+
expectedType: 'directory',
|
|
406
|
+
});
|
|
407
|
+
const resolvedOutputFolderPath = payload.outputFolderPath
|
|
408
|
+
? await resolvePathWithinKnowledgeBase(payload.outputFolderPath, {
|
|
409
|
+
expectedType: 'any',
|
|
410
|
+
allowMissing: true,
|
|
411
|
+
})
|
|
412
|
+
: undefined;
|
|
413
|
+
const result = await notemdService.processFolder({
|
|
414
|
+
folderPath: resolvedFolderPath,
|
|
415
|
+
outputFolderPath: resolvedOutputFolderPath,
|
|
416
|
+
createConceptNotes: payload.createConceptNotes === true,
|
|
417
|
+
dryRun: payload.dryRun === true,
|
|
418
|
+
}, settings, reporter, operation.controller.signal);
|
|
419
|
+
operation.controller.signal.throwIfAborted();
|
|
420
|
+
await persistNotemdWorkspacePatch({
|
|
421
|
+
folderPath: resolvedFolderPath,
|
|
422
|
+
outputFolderPath: resolvedOutputFolderPath || '',
|
|
423
|
+
}).catch((workspaceError) => {
|
|
424
|
+
console.warn('[NoteMD] Failed to persist workspace state after process-folder.', workspaceError);
|
|
425
|
+
});
|
|
426
|
+
finalizeNotemdOperation(operation, 'done');
|
|
427
|
+
if (streamEnabled) {
|
|
428
|
+
writeSseEvent(res, 'done', {
|
|
429
|
+
success: true,
|
|
430
|
+
operationId: operation.id,
|
|
431
|
+
result,
|
|
432
|
+
});
|
|
433
|
+
await finishSseResponse(res);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
437
|
+
res.end(JSON.stringify({
|
|
438
|
+
success: true,
|
|
439
|
+
operationId: operation.id,
|
|
440
|
+
result,
|
|
441
|
+
logs: operation.logs,
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
if (operation) {
|
|
447
|
+
finalizeNotemdOperation(operation, operation.controller.signal.aborted ? 'cancelled' : 'error');
|
|
448
|
+
}
|
|
449
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (isAccessDeniedError(error)) {
|
|
453
|
+
const statusCode = operation?.controller.signal.aborted ? 499 : 403;
|
|
454
|
+
const payload = { success: false, error: String(error.message || 'Access denied') };
|
|
455
|
+
if (streamEnabled) {
|
|
456
|
+
writeSseEvent(res, 'error', payload);
|
|
457
|
+
await finishSseResponse(res);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
461
|
+
res.end(JSON.stringify(payload));
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
console.error(error);
|
|
466
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/process-folder');
|
|
467
|
+
const payload = { success: false, error: String(error) };
|
|
468
|
+
if (streamEnabled) {
|
|
469
|
+
writeSseEvent(res, 'error', payload);
|
|
470
|
+
await finishSseResponse(res);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
474
|
+
res.end(JSON.stringify(payload));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return;
|
|
478
|
+
} },
|
|
479
|
+
{ method: 'POST', path: '/api/notemd/generate-content', handler: async (req, res) => {
|
|
480
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
143
481
|
try {
|
|
144
|
-
const
|
|
482
|
+
const payload = await readJsonBody(req);
|
|
145
483
|
const settings = await loadNotemdSettings();
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
484
|
+
let title = String(payload.title || '').trim();
|
|
485
|
+
const filePathCandidate = String(payload.filePath || '').trim();
|
|
486
|
+
if (!title && filePathCandidate) {
|
|
487
|
+
title = path.basename(filePathCandidate, path.extname(filePathCandidate));
|
|
488
|
+
}
|
|
489
|
+
if (!title) {
|
|
490
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
491
|
+
res.end(JSON.stringify({ success: false, error: 'Missing title or filePath' }));
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const content = await notemdService.generateContent(title, typeof payload.context === 'string' ? payload.context : undefined, settings);
|
|
495
|
+
let outputPath = null;
|
|
496
|
+
if (payload.outputPath) {
|
|
497
|
+
outputPath = await resolvePathWithinKnowledgeBase(payload.outputPath, {
|
|
498
|
+
expectedType: 'any',
|
|
499
|
+
allowMissing: true,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
else if (filePathCandidate) {
|
|
503
|
+
outputPath = await resolvePathWithinKnowledgeBase(filePathCandidate, {
|
|
504
|
+
expectedType: 'any',
|
|
505
|
+
allowMissing: true,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
if (outputPath) {
|
|
509
|
+
await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
|
|
510
|
+
await fs.promises.writeFile(outputPath, content, 'utf8');
|
|
511
|
+
}
|
|
512
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
513
|
+
res.end(JSON.stringify({
|
|
514
|
+
success: true,
|
|
515
|
+
title,
|
|
516
|
+
outputPath,
|
|
517
|
+
content,
|
|
518
|
+
}));
|
|
151
519
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
520
|
+
catch (error) {
|
|
521
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (isAccessDeniedError(error)) {
|
|
525
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
526
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
console.error(error);
|
|
530
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/generate-content');
|
|
531
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
532
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
} },
|
|
536
|
+
{ method: 'POST', path: '/api/notemd/translate-file', handler: async (req, res) => {
|
|
537
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
158
538
|
try {
|
|
159
|
-
const
|
|
539
|
+
const payload = await readJsonBody(req);
|
|
160
540
|
const settings = await loadNotemdSettings();
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
541
|
+
const resolvedFilePath = await resolvePathWithinKnowledgeBase(payload.filePath, {
|
|
542
|
+
expectedType: 'file',
|
|
543
|
+
});
|
|
544
|
+
const resolvedOutputPath = payload.outputPath
|
|
545
|
+
? await resolvePathWithinKnowledgeBase(payload.outputPath, {
|
|
546
|
+
expectedType: 'any',
|
|
547
|
+
allowMissing: true,
|
|
548
|
+
})
|
|
549
|
+
: undefined;
|
|
550
|
+
const targetLanguage = String(payload.targetLanguage || settings.translateLanguage || settings.language).trim();
|
|
551
|
+
if (!targetLanguage) {
|
|
552
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
553
|
+
res.end(JSON.stringify({ success: false, error: 'Missing targetLanguage' }));
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const result = await notemdService.translateFile({
|
|
557
|
+
filePath: resolvedFilePath,
|
|
558
|
+
outputPath: resolvedOutputPath,
|
|
559
|
+
targetLanguage,
|
|
560
|
+
}, settings);
|
|
561
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
562
|
+
res.end(JSON.stringify({ success: true, result }));
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (isAccessDeniedError(error)) {
|
|
569
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
570
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
console.error(error);
|
|
574
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/translate-file');
|
|
575
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
576
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
579
|
+
} },
|
|
580
|
+
{ method: 'POST', path: '/api/notemd/fix-mermaid', handler: async (req, res) => {
|
|
581
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
173
582
|
try {
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
583
|
+
const payload = await readJsonBody(req);
|
|
584
|
+
const resolvedFilePath = await resolvePathWithinKnowledgeBase(payload.filePath, {
|
|
585
|
+
expectedType: 'file',
|
|
586
|
+
});
|
|
587
|
+
const inPlace = payload.inPlace !== false;
|
|
588
|
+
const result = await notemdService.fixMermaid(resolvedFilePath, inPlace);
|
|
589
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
590
|
+
res.end(JSON.stringify({ success: true, result }));
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (isAccessDeniedError(error)) {
|
|
597
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
598
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
console.error(error);
|
|
602
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/fix-mermaid');
|
|
603
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
604
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
} },
|
|
608
|
+
{ method: 'POST', path: '/api/notemd/fix-formulas', handler: async (req, res) => {
|
|
609
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
188
610
|
try {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
611
|
+
const payload = await readJsonBody(req);
|
|
612
|
+
const resolvedFilePath = await resolvePathWithinKnowledgeBase(payload.filePath, {
|
|
613
|
+
expectedType: 'file',
|
|
614
|
+
});
|
|
615
|
+
const inPlace = payload.inPlace !== false;
|
|
616
|
+
const result = await notemdService.fixFormulas(resolvedFilePath, inPlace);
|
|
617
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
618
|
+
res.end(JSON.stringify({ success: true, result }));
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (isAccessDeniedError(error)) {
|
|
625
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
626
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
console.error(error);
|
|
630
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/fix-formulas');
|
|
631
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
632
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
633
|
+
}
|
|
634
|
+
return;
|
|
635
|
+
} },
|
|
636
|
+
{ method: 'POST', path: '/api/notemd/check-duplicates', handler: async (req, res) => {
|
|
637
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
203
638
|
try {
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
639
|
+
const payload = await readJsonBody(req);
|
|
640
|
+
const resolvedFilePath = await resolvePathWithinKnowledgeBase(payload.filePath, {
|
|
641
|
+
expectedType: 'file',
|
|
642
|
+
});
|
|
643
|
+
const result = await notemdService.checkDuplicates(resolvedFilePath);
|
|
644
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
645
|
+
res.end(JSON.stringify({ success: true, result }));
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (isAccessDeniedError(error)) {
|
|
652
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
653
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
console.error(error);
|
|
657
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/check-duplicates');
|
|
658
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
659
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
660
|
+
}
|
|
661
|
+
return;
|
|
662
|
+
} },
|
|
663
|
+
{ method: 'POST', path: '/api/notemd/extract-concepts', handler: async (req, res) => {
|
|
664
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
665
|
+
const streamEnabled = shouldStreamNotemdResponse(req);
|
|
666
|
+
let operation = null;
|
|
218
667
|
try {
|
|
219
|
-
const
|
|
668
|
+
const payload = await readJsonBody(req);
|
|
220
669
|
const settings = await loadNotemdSettings();
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
670
|
+
operation = createNotemdOperation(payload.operationId, res);
|
|
671
|
+
const reporter = createNotemdReporter(operation, streamEnabled ? res : undefined);
|
|
672
|
+
if (streamEnabled) {
|
|
673
|
+
res.writeHead(200, {
|
|
674
|
+
'Content-Type': 'text/event-stream',
|
|
675
|
+
'Cache-Control': 'no-cache',
|
|
676
|
+
Connection: 'keep-alive',
|
|
677
|
+
});
|
|
678
|
+
writeSseEvent(res, 'operation', {
|
|
679
|
+
operationId: operation.id,
|
|
680
|
+
status: operation.status,
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
const resolvedFilePath = await resolvePathWithinKnowledgeBase(payload.filePath, {
|
|
684
|
+
expectedType: 'file',
|
|
685
|
+
});
|
|
686
|
+
const result = await notemdService.extractConcepts(resolvedFilePath, settings, reporter, operation.controller.signal);
|
|
687
|
+
operation.controller.signal.throwIfAborted();
|
|
688
|
+
await persistNotemdWorkspacePatch({
|
|
689
|
+
filePath: resolvedFilePath,
|
|
690
|
+
folderPath: path.dirname(resolvedFilePath),
|
|
691
|
+
}).catch((workspaceError) => {
|
|
692
|
+
console.warn('[NoteMD] Failed to persist workspace state after extract-concepts.', workspaceError);
|
|
693
|
+
});
|
|
694
|
+
finalizeNotemdOperation(operation, 'done');
|
|
695
|
+
if (streamEnabled) {
|
|
696
|
+
writeSseEvent(res, 'done', {
|
|
697
|
+
success: true,
|
|
698
|
+
operationId: operation.id,
|
|
699
|
+
result,
|
|
700
|
+
});
|
|
701
|
+
await finishSseResponse(res);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
705
|
+
res.end(JSON.stringify({ success: true, operationId: operation.id, result, logs: operation.logs }));
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
if (operation) {
|
|
710
|
+
finalizeNotemdOperation(operation, operation.controller.signal.aborted ? 'cancelled' : 'error');
|
|
711
|
+
}
|
|
712
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (isAccessDeniedError(error)) {
|
|
716
|
+
const statusCode = operation?.controller.signal.aborted ? 499 : 403;
|
|
717
|
+
const payload = { success: false, error: String(error.message || 'Access denied') };
|
|
718
|
+
if (streamEnabled) {
|
|
719
|
+
writeSseEvent(res, 'error', payload);
|
|
720
|
+
await finishSseResponse(res);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
724
|
+
res.end(JSON.stringify(payload));
|
|
725
|
+
}
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
console.error(error);
|
|
729
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/extract-concepts');
|
|
730
|
+
const payload = { success: false, error: String(error) };
|
|
731
|
+
if (streamEnabled) {
|
|
732
|
+
writeSseEvent(res, 'error', payload);
|
|
733
|
+
await finishSseResponse(res);
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
737
|
+
res.end(JSON.stringify(payload));
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return;
|
|
741
|
+
} },
|
|
742
|
+
{ method: 'POST', path: '/api/notemd/cancel', handler: async (req, res) => {
|
|
743
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
234
744
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
745
|
+
const payload = await readJsonBody(req);
|
|
746
|
+
const operationId = String(payload.operationId || '').trim();
|
|
747
|
+
if (!operationId) {
|
|
748
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
749
|
+
res.end(JSON.stringify({ success: false, error: 'Missing operationId' }));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const operation = NOTEMD_ACTIVE_OPERATIONS.get(operationId);
|
|
753
|
+
if (!operation) {
|
|
754
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
755
|
+
res.end(JSON.stringify({ success: false, error: 'Operation not found' }));
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (operation.status !== 'running') {
|
|
759
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
760
|
+
res.end(JSON.stringify({
|
|
761
|
+
success: false,
|
|
762
|
+
operationId,
|
|
763
|
+
status: operation.status,
|
|
764
|
+
message: 'Operation is not running.',
|
|
765
|
+
}));
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
operation.controller.abort();
|
|
769
|
+
finalizeNotemdOperation(operation, 'cancelled');
|
|
770
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
771
|
+
res.end(JSON.stringify({ success: true, operationId, status: 'cancelled' }));
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
console.error(error);
|
|
778
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/cancel');
|
|
779
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
780
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
781
|
+
}
|
|
782
|
+
return;
|
|
783
|
+
} },
|
|
244
784
|
{
|
|
245
785
|
method: 'POST',
|
|
246
786
|
path: api('/generate-diagram'),
|
|
247
787
|
handler: async (req, res) => {
|
|
248
788
|
try {
|
|
249
|
-
const body = await
|
|
789
|
+
const body = await readJsonBody(req);
|
|
250
790
|
const settings = await loadNotemdSettings();
|
|
251
|
-
const result = await notemdService.generateDiagram(
|
|
791
|
+
const result = await notemdService.generateDiagram(body, settings);
|
|
252
792
|
ok(res, { result });
|
|
253
793
|
}
|
|
254
794
|
catch (e) {
|
|
@@ -261,9 +801,9 @@ function registerNotemdRoutes(ctx) {
|
|
|
261
801
|
path: api('/preview-diagram'),
|
|
262
802
|
handler: async (req, res) => {
|
|
263
803
|
try {
|
|
264
|
-
const body = await
|
|
804
|
+
const body = await readJsonBody(req);
|
|
265
805
|
const settings = await loadNotemdSettings();
|
|
266
|
-
const result = await notemdService.previewDiagram(
|
|
806
|
+
const result = await notemdService.previewDiagram(body, settings);
|
|
267
807
|
ok(res, { result });
|
|
268
808
|
}
|
|
269
809
|
catch (e) {
|
|
@@ -276,9 +816,10 @@ function registerNotemdRoutes(ctx) {
|
|
|
276
816
|
path: api('/export-diagram'),
|
|
277
817
|
handler: async (req, res) => {
|
|
278
818
|
try {
|
|
279
|
-
const body = await
|
|
819
|
+
const body = await readJsonBody(req);
|
|
280
820
|
const settings = await loadNotemdSettings();
|
|
281
|
-
const
|
|
821
|
+
const request = { ...body, ...(body.outputPath ? { outputPath: await resolvePathWithinKnowledgeBase(body.outputPath, { expectedType: 'any', allowMissing: true }) } : {}) };
|
|
822
|
+
const result = await notemdService.exportDiagram(request, settings);
|
|
282
823
|
ok(res, { result });
|
|
283
824
|
}
|
|
284
825
|
catch (e) {
|
|
@@ -286,15 +827,14 @@ function registerNotemdRoutes(ctx) {
|
|
|
286
827
|
}
|
|
287
828
|
},
|
|
288
829
|
},
|
|
289
|
-
// ── Web search (obsidian-notemd v1.8.4) ──
|
|
290
830
|
{
|
|
291
831
|
method: 'POST',
|
|
292
832
|
path: api('/search'),
|
|
293
833
|
handler: async (req, res) => {
|
|
294
834
|
try {
|
|
295
|
-
const body = await
|
|
835
|
+
const body = await readJsonBody(req);
|
|
296
836
|
const settings = await loadNotemdSettings();
|
|
297
|
-
const result = await notemdService.search(
|
|
837
|
+
const result = await notemdService.search(body, settings);
|
|
298
838
|
ok(res, { result });
|
|
299
839
|
}
|
|
300
840
|
catch (e) {
|
|
@@ -302,7 +842,6 @@ function registerNotemdRoutes(ctx) {
|
|
|
302
842
|
}
|
|
303
843
|
},
|
|
304
844
|
},
|
|
305
|
-
// ── Batch progress query (obsidian-notemd v1.8.4) ──
|
|
306
845
|
{
|
|
307
846
|
method: 'GET',
|
|
308
847
|
path: api('/progress'),
|
|
@@ -316,14 +855,14 @@ function registerNotemdRoutes(ctx) {
|
|
|
316
855
|
}
|
|
317
856
|
},
|
|
318
857
|
},
|
|
319
|
-
// ── LLM provider diagnostics (obsidian-notemd v1.8.4) ──
|
|
320
858
|
{
|
|
321
859
|
method: 'POST',
|
|
322
860
|
path: api('/diagnose-llm'),
|
|
323
861
|
handler: async (req, res) => {
|
|
324
862
|
try {
|
|
325
|
-
const body = await
|
|
326
|
-
const
|
|
863
|
+
const body = await readJsonBody(req);
|
|
864
|
+
const settings = await loadNotemdSettings();
|
|
865
|
+
const result = await notemdService.diagnoseLlmProvider(body, settings);
|
|
327
866
|
ok(res, { result });
|
|
328
867
|
}
|
|
329
868
|
catch (e) {
|
|
@@ -331,15 +870,19 @@ function registerNotemdRoutes(ctx) {
|
|
|
331
870
|
}
|
|
332
871
|
},
|
|
333
872
|
},
|
|
334
|
-
// ── Extract original text (obsidian-notemd v1.8.4) ──
|
|
335
873
|
{
|
|
336
874
|
method: 'POST',
|
|
337
875
|
path: api('/extract-original-text'),
|
|
338
876
|
handler: async (req, res) => {
|
|
339
877
|
try {
|
|
340
|
-
const body = await
|
|
878
|
+
const body = await readJsonBody(req);
|
|
341
879
|
const settings = await loadNotemdSettings();
|
|
342
|
-
const
|
|
880
|
+
const request = {
|
|
881
|
+
...body,
|
|
882
|
+
filePath: await resolvePathWithinKnowledgeBase(body.filePath, { expectedType: 'file' }),
|
|
883
|
+
...(body.outputPath ? { outputPath: await resolvePathWithinKnowledgeBase(body.outputPath, { expectedType: 'any', allowMissing: true }) } : {}),
|
|
884
|
+
};
|
|
885
|
+
const result = await notemdService.extractOriginalText(request, settings);
|
|
343
886
|
ok(res, { result });
|
|
344
887
|
}
|
|
345
888
|
catch (e) {
|
|
@@ -347,7 +890,6 @@ function registerNotemdRoutes(ctx) {
|
|
|
347
890
|
}
|
|
348
891
|
},
|
|
349
892
|
},
|
|
350
|
-
// ── CLI operations registry ──
|
|
351
893
|
{
|
|
352
894
|
method: 'GET',
|
|
353
895
|
path: api('/capability-manifest'),
|
|
@@ -379,9 +921,9 @@ function registerNotemdRoutes(ctx) {
|
|
|
379
921
|
path: api('/provider-diagnostic'),
|
|
380
922
|
handler: async (req, res) => {
|
|
381
923
|
try {
|
|
382
|
-
const body = await
|
|
924
|
+
const body = await readJsonBody(req);
|
|
383
925
|
const settings = await loadNotemdSettings();
|
|
384
|
-
const result = await notemdService.diagnoseLlmProvider(
|
|
926
|
+
const result = await notemdService.diagnoseLlmProvider(body, settings);
|
|
385
927
|
ok(res, { result });
|
|
386
928
|
}
|
|
387
929
|
catch (e) {
|
|
@@ -389,44 +931,85 @@ function registerNotemdRoutes(ctx) {
|
|
|
389
931
|
}
|
|
390
932
|
},
|
|
391
933
|
},
|
|
392
|
-
{
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
handler: async (req, res) => {
|
|
934
|
+
{ method: 'POST', path: '/api/notemd/one-click-extract', handler: async (req, res) => {
|
|
935
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
936
|
+
let operation = null;
|
|
396
937
|
try {
|
|
397
|
-
const
|
|
938
|
+
const payload = await readJsonBody(req);
|
|
398
939
|
const settings = await loadNotemdSettings();
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
940
|
+
operation = createNotemdOperation(payload.operationId, res);
|
|
941
|
+
const reporter = createNotemdReporter(operation);
|
|
942
|
+
const resolvedFilePath = await resolvePathWithinKnowledgeBase(payload.filePath, {
|
|
943
|
+
expectedType: 'file',
|
|
944
|
+
});
|
|
945
|
+
const result = await notemdService.oneClickExtract(resolvedFilePath, settings, reporter, operation.controller.signal);
|
|
946
|
+
operation.controller.signal.throwIfAborted();
|
|
947
|
+
await persistNotemdWorkspacePatch({
|
|
948
|
+
filePath: resolvedFilePath,
|
|
949
|
+
folderPath: result.outputFolderPath,
|
|
950
|
+
outputFolderPath: result.outputFolderPath,
|
|
951
|
+
}).catch((workspaceError) => {
|
|
952
|
+
console.warn('[NoteMD] Failed to persist workspace state after one-click-extract.', workspaceError);
|
|
953
|
+
});
|
|
954
|
+
finalizeNotemdOperation(operation, 'done');
|
|
955
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
956
|
+
res.end(JSON.stringify({ success: true, operationId: operation.id, result, logs: operation.logs }));
|
|
957
|
+
}
|
|
958
|
+
catch (error) {
|
|
959
|
+
if (operation) {
|
|
960
|
+
finalizeNotemdOperation(operation, operation.controller.signal.aborted ? 'cancelled' : 'error');
|
|
961
|
+
}
|
|
962
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (isAccessDeniedError(error)) {
|
|
966
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
967
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
console.error(error);
|
|
971
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/one-click-extract');
|
|
972
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
973
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
974
|
+
}
|
|
975
|
+
return;
|
|
976
|
+
} },
|
|
977
|
+
{ method: 'POST', path: '/api/notemd/batch-fix-mermaid', handler: async (req, res) => {
|
|
978
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
411
979
|
try {
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
980
|
+
const payload = await readJsonBody(req);
|
|
981
|
+
const resolvedFolderPath = await resolvePathWithinKnowledgeBase(payload.folderPath, {
|
|
982
|
+
expectedType: 'directory',
|
|
983
|
+
});
|
|
984
|
+
const result = await notemdService.batchFixMermaid(resolvedFolderPath, payload.inPlace !== false);
|
|
985
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
986
|
+
res.end(JSON.stringify({ success: true, result }));
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (isAccessDeniedError(error)) {
|
|
993
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
994
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
console.error(error);
|
|
998
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/batch-fix-mermaid');
|
|
999
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1000
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
1001
|
+
}
|
|
1002
|
+
return;
|
|
1003
|
+
} },
|
|
422
1004
|
{
|
|
423
1005
|
method: 'POST',
|
|
424
1006
|
path: api('/batch-fix-formulas'),
|
|
425
1007
|
handler: async (req, res) => {
|
|
426
1008
|
try {
|
|
427
|
-
const raw = await
|
|
428
|
-
const { folderPath, inPlace } =
|
|
429
|
-
const
|
|
1009
|
+
const raw = await readJsonBody(req);
|
|
1010
|
+
const { folderPath, inPlace } = raw;
|
|
1011
|
+
const resolvedFolderPath = await resolvePathWithinKnowledgeBase(folderPath, { expectedType: 'directory' });
|
|
1012
|
+
const result = await notemdService.batchFixFormulas(resolvedFolderPath, inPlace ?? true);
|
|
430
1013
|
ok(res, { result });
|
|
431
1014
|
}
|
|
432
1015
|
catch (e) {
|
|
@@ -439,10 +1022,11 @@ function registerNotemdRoutes(ctx) {
|
|
|
439
1022
|
path: api('/batch-generate-content'),
|
|
440
1023
|
handler: async (req, res) => {
|
|
441
1024
|
try {
|
|
442
|
-
const raw = await
|
|
443
|
-
const { folderPath } =
|
|
1025
|
+
const raw = await readJsonBody(req);
|
|
1026
|
+
const { folderPath } = raw;
|
|
444
1027
|
const settings = await loadNotemdSettings();
|
|
445
|
-
const
|
|
1028
|
+
const resolvedFolderPath = await resolvePathWithinKnowledgeBase(folderPath, { expectedType: 'directory' });
|
|
1029
|
+
const result = await notemdService.generateFolderContent(resolvedFolderPath, settings);
|
|
446
1030
|
ok(res, { result });
|
|
447
1031
|
}
|
|
448
1032
|
catch (e) {
|
|
@@ -455,8 +1039,8 @@ function registerNotemdRoutes(ctx) {
|
|
|
455
1039
|
path: api('/batch-progress'),
|
|
456
1040
|
handler: async (req, res) => {
|
|
457
1041
|
try {
|
|
458
|
-
const raw = await
|
|
459
|
-
const { operationId } =
|
|
1042
|
+
const raw = await readJsonBody(req);
|
|
1043
|
+
const { operationId } = raw;
|
|
460
1044
|
const result = await notemdService.getBatchProgress(operationId);
|
|
461
1045
|
ok(res, { progress: result });
|
|
462
1046
|
}
|
|
@@ -465,15 +1049,18 @@ function registerNotemdRoutes(ctx) {
|
|
|
465
1049
|
}
|
|
466
1050
|
},
|
|
467
1051
|
},
|
|
468
|
-
// ── Workflow pipeline (NoteConnection native) ──
|
|
469
1052
|
{
|
|
470
1053
|
method: 'POST',
|
|
471
1054
|
path: api('/workflow'),
|
|
472
1055
|
handler: async (req, res) => {
|
|
473
1056
|
try {
|
|
474
|
-
const raw = await
|
|
1057
|
+
const raw = await readJsonBody(req);
|
|
475
1058
|
const settings = await loadNotemdSettings();
|
|
476
|
-
const request =
|
|
1059
|
+
const request = {
|
|
1060
|
+
...raw,
|
|
1061
|
+
filePath: await resolvePathWithinKnowledgeBase(raw.filePath, { expectedType: 'file' }),
|
|
1062
|
+
...(raw.outputFolderPath ? { outputFolderPath: await resolvePathWithinKnowledgeBase(raw.outputFolderPath, { expectedType: 'directory', allowMissing: true }) } : {}),
|
|
1063
|
+
};
|
|
477
1064
|
const result = await notemdService.runWorkflow(request, settings);
|
|
478
1065
|
ok(res, { result });
|
|
479
1066
|
}
|
|
@@ -487,9 +1074,13 @@ function registerNotemdRoutes(ctx) {
|
|
|
487
1074
|
path: api('/batch-workflow'),
|
|
488
1075
|
handler: async (req, res) => {
|
|
489
1076
|
try {
|
|
490
|
-
const raw = await
|
|
1077
|
+
const raw = await readJsonBody(req);
|
|
491
1078
|
const settings = await loadNotemdSettings();
|
|
492
|
-
const request =
|
|
1079
|
+
const request = {
|
|
1080
|
+
...raw,
|
|
1081
|
+
folderPath: await resolvePathWithinKnowledgeBase(raw.folderPath, { expectedType: 'directory' }),
|
|
1082
|
+
...(raw.outputBasePath ? { outputBasePath: await resolvePathWithinKnowledgeBase(raw.outputBasePath, { expectedType: 'directory', allowMissing: true }) } : {}),
|
|
1083
|
+
};
|
|
493
1084
|
const result = await notemdService.runBatchWorkflow(request, settings);
|
|
494
1085
|
ok(res, { result });
|
|
495
1086
|
}
|
|
@@ -513,7 +1104,6 @@ function registerNotemdRoutes(ctx) {
|
|
|
513
1104
|
}
|
|
514
1105
|
},
|
|
515
1106
|
},
|
|
516
|
-
// ── Agent Workspace Bridge (Phase 4 P3) ──
|
|
517
1107
|
{
|
|
518
1108
|
method: 'GET',
|
|
519
1109
|
path: api('/agent-manifest'),
|
|
@@ -550,10 +1140,10 @@ function registerNotemdRoutes(ctx) {
|
|
|
550
1140
|
path: api('/provider-profiles/import'),
|
|
551
1141
|
handler: async (req, res) => {
|
|
552
1142
|
try {
|
|
553
|
-
const body = await
|
|
1143
|
+
const body = await readJsonBody(req);
|
|
554
1144
|
const settings = await loadNotemdSettings();
|
|
555
1145
|
const { parseProviderProfileImport } = await Promise.resolve().then(() => __importStar(require('../notemd/providerProfiles')));
|
|
556
|
-
const result = parseProviderProfileImport(body, settings.providers);
|
|
1146
|
+
const result = parseProviderProfileImport(JSON.stringify(body), settings.providers);
|
|
557
1147
|
ok(res, { result });
|
|
558
1148
|
}
|
|
559
1149
|
catch (e) {
|
|
@@ -561,5 +1151,171 @@ function registerNotemdRoutes(ctx) {
|
|
|
561
1151
|
}
|
|
562
1152
|
},
|
|
563
1153
|
},
|
|
1154
|
+
{ method: 'GET', path: '/api/notemd/provider-templates', handler: async (req, res) => {
|
|
1155
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
1156
|
+
try {
|
|
1157
|
+
const urlObj = new URL(req.url || '/', `http://${LOOPBACK_HOST}:${finalPort}`);
|
|
1158
|
+
const persistTemplates = ['1', 'true', 'yes'].includes(String(urlObj.searchParams.get('persist') || '').trim().toLowerCase());
|
|
1159
|
+
let persistence = { configPath: (0, AppConfigToml_1.resolveAppConfigPath)(), persisted: false };
|
|
1160
|
+
if (persistTemplates) {
|
|
1161
|
+
persistence = await ensureNotemdProviderTemplatesPersisted();
|
|
1162
|
+
}
|
|
1163
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1164
|
+
res.end(JSON.stringify({
|
|
1165
|
+
success: true,
|
|
1166
|
+
templates: providerTemplates_1.NOTEMD_PROVIDER_TEMPLATES,
|
|
1167
|
+
configPath: persistence.configPath,
|
|
1168
|
+
persisted: persistence.persisted,
|
|
1169
|
+
}));
|
|
1170
|
+
}
|
|
1171
|
+
catch (error) {
|
|
1172
|
+
writeApiErrorResponse(res, error, {
|
|
1173
|
+
context: 'API:GET /api/notemd/provider-templates',
|
|
1174
|
+
requestId,
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
return;
|
|
1178
|
+
} },
|
|
1179
|
+
{ method: 'GET', path: '/api/notemd/workspace', handler: async (req, res) => {
|
|
1180
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
1181
|
+
try {
|
|
1182
|
+
const settings = await loadNotemdSettings();
|
|
1183
|
+
const workspace = extractNotemdWorkspaceState(settings);
|
|
1184
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1185
|
+
res.end(JSON.stringify({
|
|
1186
|
+
success: true,
|
|
1187
|
+
workspace,
|
|
1188
|
+
}));
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
writeApiErrorResponse(res, error, {
|
|
1192
|
+
context: 'API:GET /api/notemd/workspace',
|
|
1193
|
+
requestId,
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
return;
|
|
1197
|
+
} },
|
|
1198
|
+
{ method: 'POST', path: '/api/notemd/provider-templates/apply', handler: async (req, res) => {
|
|
1199
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
1200
|
+
try {
|
|
1201
|
+
const payload = await readJsonBody(req);
|
|
1202
|
+
const rawTemplateId = isObjectRecord(payload)
|
|
1203
|
+
? String(payload.templateId || payload.template_id || '').trim()
|
|
1204
|
+
: '';
|
|
1205
|
+
if (!rawTemplateId) {
|
|
1206
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1207
|
+
res.end(JSON.stringify({ success: false, error: 'Missing templateId.' }));
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
const template = (0, providerTemplates_1.getNotemdProviderTemplate)(rawTemplateId);
|
|
1211
|
+
if (!template) {
|
|
1212
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1213
|
+
res.end(JSON.stringify({ success: false, error: `Unknown provider template: ${rawTemplateId}` }));
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const settings = await loadNotemdSettings();
|
|
1217
|
+
const updatedSettings = (0, providerTemplates_1.applyProviderTemplateToSettings)(settings, rawTemplateId);
|
|
1218
|
+
const persistedSettings = await persistNotemdSettings(updatedSettings);
|
|
1219
|
+
const persistence = await ensureNotemdProviderTemplatesPersisted();
|
|
1220
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1221
|
+
res.end(JSON.stringify({
|
|
1222
|
+
success: true,
|
|
1223
|
+
template,
|
|
1224
|
+
settings: persistedSettings,
|
|
1225
|
+
configPath: persistence.configPath,
|
|
1226
|
+
persistedTemplates: persistence.persisted,
|
|
1227
|
+
}));
|
|
1228
|
+
}
|
|
1229
|
+
catch (error) {
|
|
1230
|
+
writeApiErrorResponse(res, error, {
|
|
1231
|
+
context: 'API:POST /api/notemd/provider-templates/apply',
|
|
1232
|
+
requestId,
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
return;
|
|
1236
|
+
} },
|
|
1237
|
+
{ method: 'POST', path: '/api/notemd/workspace', handler: async (req, res) => {
|
|
1238
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
1239
|
+
try {
|
|
1240
|
+
const payload = await readJsonBody(req);
|
|
1241
|
+
const workspaceCandidate = isObjectRecord(payload) && payload.workspace !== undefined
|
|
1242
|
+
? payload.workspace
|
|
1243
|
+
: payload;
|
|
1244
|
+
const workspace = await persistNotemdWorkspacePatch(workspaceCandidate);
|
|
1245
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1246
|
+
res.end(JSON.stringify({ success: true, workspace }));
|
|
1247
|
+
}
|
|
1248
|
+
catch (error) {
|
|
1249
|
+
writeApiErrorResponse(res, error, {
|
|
1250
|
+
context: 'API:POST /api/notemd/workspace',
|
|
1251
|
+
requestId,
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
return;
|
|
1255
|
+
} },
|
|
1256
|
+
{ method: 'POST', path: '/api/notemd/translate-folder', handler: async (req, res) => {
|
|
1257
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
1258
|
+
try {
|
|
1259
|
+
const payload = await readJsonBody(req);
|
|
1260
|
+
const settings = await loadNotemdSettings();
|
|
1261
|
+
const resolvedFolderPath = await resolvePathWithinKnowledgeBase(payload.folderPath, {
|
|
1262
|
+
expectedType: 'directory',
|
|
1263
|
+
});
|
|
1264
|
+
const targetLanguage = String(payload.targetLanguage || settings.translateLanguage || settings.language).trim();
|
|
1265
|
+
if (!targetLanguage) {
|
|
1266
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1267
|
+
res.end(JSON.stringify({ success: false, error: 'Missing targetLanguage' }));
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
const result = await notemdService.translateFolder(resolvedFolderPath, targetLanguage, settings);
|
|
1271
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1272
|
+
res.end(JSON.stringify({ success: true, result }));
|
|
1273
|
+
}
|
|
1274
|
+
catch (error) {
|
|
1275
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (isAccessDeniedError(error)) {
|
|
1279
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
1280
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
console.error(error);
|
|
1284
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/translate-folder');
|
|
1285
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1286
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
1287
|
+
}
|
|
1288
|
+
return;
|
|
1289
|
+
} },
|
|
1290
|
+
{ method: 'POST', path: '/api/notemd/generate-folder-content', handler: async (req, res) => {
|
|
1291
|
+
const requestId = String(res.getHeader('X-Request-Id') || '');
|
|
1292
|
+
try {
|
|
1293
|
+
const payload = await readJsonBody(req);
|
|
1294
|
+
const settings = await loadNotemdSettings();
|
|
1295
|
+
const resolvedFolderPath = await resolvePathWithinKnowledgeBase(payload.folderPath, {
|
|
1296
|
+
expectedType: 'directory',
|
|
1297
|
+
});
|
|
1298
|
+
const result = await notemdService.generateFolderContent(resolvedFolderPath, settings);
|
|
1299
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1300
|
+
res.end(JSON.stringify({ success: true, result }));
|
|
1301
|
+
}
|
|
1302
|
+
catch (error) {
|
|
1303
|
+
if (writeBodyParseErrorResponse(res, error)) {
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (isAccessDeniedError(error)) {
|
|
1307
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
1308
|
+
res.end(JSON.stringify({ success: false, error: String(error.message || 'Access denied') }));
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
console.error(error);
|
|
1312
|
+
CrashLogger_1.CrashLogger.log(error, 'API:POST /api/notemd/generate-folder-content');
|
|
1313
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1314
|
+
res.end(JSON.stringify({ success: false, error: String(error) }));
|
|
1315
|
+
}
|
|
1316
|
+
return;
|
|
1317
|
+
} }
|
|
564
1318
|
];
|
|
1319
|
+
const legacyPutAliases = new Set(["/api/notemd/settings", "/api/notemd/provider-templates/apply", "/api/notemd/workspace", "/api/notemd/cancel", "/api/notemd/test-llm", "/api/notemd/process-file", "/api/notemd/process-folder", "/api/notemd/generate-content", "/api/notemd/translate-file", "/api/notemd/translate-folder", "/api/notemd/fix-mermaid", "/api/notemd/fix-formulas", "/api/notemd/check-duplicates", "/api/notemd/extract-concepts", "/api/notemd/one-click-extract", "/api/notemd/batch-fix-mermaid", "/api/notemd/generate-folder-content"]);
|
|
1320
|
+
return [...routes, ...routes.filter(route => route.method === 'POST' && legacyPutAliases.has(route.path)).map(route => ({ ...route, method: 'PUT' }))];
|
|
565
1321
|
}
|