noteconnection 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +257 -1803
  2. package/dist/src/agent_workspace.contract.parity.test.js +105 -421
  3. package/dist/src/agent_workspace.frontend.test.js +410 -11
  4. package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
  5. package/dist/src/appimage.patchelf.test.js +113 -0
  6. package/dist/src/appimage.portability.test.js +156 -0
  7. package/dist/src/backend/FileLoader.js +18 -4
  8. package/dist/src/backend/FileLoader.test.js +125 -0
  9. package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
  10. package/dist/src/backend/GraphBuilder.js +66 -18
  11. package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
  12. package/dist/src/backend/GraphMetrics.js +0 -3
  13. package/dist/src/backend/ResourceIdentity.js +155 -0
  14. package/dist/src/backend/ResourceIdentity.test.js +113 -0
  15. package/dist/src/backend/algorithms/CycleDetection.js +0 -1
  16. package/dist/src/backend/algorithms/HybridEngine.js +0 -3
  17. package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
  18. package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
  19. package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
  20. package/dist/src/backend/build_dag.js +0 -1
  21. package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
  22. package/dist/src/capacitor.runtime.contract.test.js +11 -2
  23. package/dist/src/core/Graph.js +138 -42
  24. package/dist/src/core/Graph.test.js +74 -0
  25. package/dist/src/core/NoteConnection.js +2 -2
  26. package/dist/src/core/PathBridge.js +265 -2
  27. package/dist/src/core/ResourceReference.js +17 -0
  28. package/dist/src/desktop.godot.pack.test.js +142 -0
  29. package/dist/src/desktop.runtime.graph.test.js +66 -0
  30. package/dist/src/export/WorkspaceExportBundle.js +44 -0
  31. package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
  32. package/dist/src/foundation.release.evidence.contract.test.js +127 -9
  33. package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
  34. package/dist/src/frontend/README.md +257 -1803
  35. package/dist/src/frontend/agent_workspace.js +665 -7
  36. package/dist/src/frontend/index.html +31 -2
  37. package/dist/src/frontend/knowledge_projection_contract.js +230 -0
  38. package/dist/src/frontend/knowledge_projection_store.js +214 -0
  39. package/dist/src/frontend/libs/path_core.js +158 -42
  40. package/dist/src/frontend/locales/en.json +32 -0
  41. package/dist/src/frontend/locales/zh.json +32 -0
  42. package/dist/src/frontend/mobile_budget_runtime.js +33 -0
  43. package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
  44. package/dist/src/frontend/mobile_identity_contract.js +221 -0
  45. package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
  46. package/dist/src/frontend/path.html +6 -2
  47. package/dist/src/frontend/simulationWorker.js +1 -1
  48. package/dist/src/frontend/source_manager.js +110 -4
  49. package/dist/src/frontend/storage_provider.js +661 -71
  50. package/dist/src/frontend/styles.css +107 -13
  51. package/dist/src/frontend/workspace_panes.js +111 -7
  52. package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
  53. package/dist/src/graph.answer.operator.contract.test.js +72 -0
  54. package/dist/src/index.js +6 -2
  55. package/dist/src/index.knowledge-root.test.js +118 -0
  56. package/dist/src/indexing/IndexLifecycle.js +22 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +24 -0
  58. package/dist/src/knowledge.api.contract.test.js +4 -2
  59. package/dist/src/knowledge_projection.contract.test.js +134 -0
  60. package/dist/src/knowledge_projection_store.test.js +200 -0
  61. package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
  62. package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
  64. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
  68. package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
  69. package/dist/src/learning/PublicAnswerContract.test.js +85 -0
  70. package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
  71. package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
  72. package/dist/src/learning/agentConversationExecution.js +211 -0
  73. package/dist/src/learning/agentConversationExecution.test.js +209 -0
  74. package/dist/src/learning/agentConversationSerialization.js +266 -0
  75. package/dist/src/learning/agentConversationSerialization.test.js +165 -0
  76. package/dist/src/learning/agentResponseBudget.js +179 -0
  77. package/dist/src/learning/agentResponseBudget.test.js +97 -0
  78. package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
  79. package/dist/src/learning/answerReleaseReview.js +1191 -250
  80. package/dist/src/learning/answerReleaseReview.test.js +1293 -7
  81. package/dist/src/learning/answerTaskPlan.js +473 -0
  82. package/dist/src/learning/conversationComposer.js +627 -386
  83. package/dist/src/learning/conversationComposer.test.js +1403 -0
  84. package/dist/src/learning/evidenceContextAssembler.js +312 -120
  85. package/dist/src/learning/graphAnswerCoverage.js +37 -0
  86. package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
  87. package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
  88. package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
  89. package/dist/src/learning/graphAnswerFacts.js +94 -0
  90. package/dist/src/learning/graphAnswerFacts.test.js +39 -0
  91. package/dist/src/learning/graphAnswerPlan.js +908 -0
  92. package/dist/src/learning/graphAnswerPlan.test.js +907 -0
  93. package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
  94. package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
  95. package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
  96. package/dist/src/learning/graphClaimMatcher.js +104 -0
  97. package/dist/src/learning/graphConditionedContext.js +109 -0
  98. package/dist/src/learning/graphConditionedContext.test.js +99 -0
  99. package/dist/src/learning/graphContextAssembler.js +3 -13
  100. package/dist/src/learning/graphContextAssembler.test.js +2 -2
  101. package/dist/src/learning/graphExpansionPolicy.js +22 -0
  102. package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
  103. package/dist/src/learning/identityMetadata.test.js +26 -0
  104. package/dist/src/learning/index.js +1 -0
  105. package/dist/src/learning/mobileAnswerProjection.js +104 -0
  106. package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
  107. package/dist/src/learning/queryBackend.js +34 -10
  108. package/dist/src/learning/queryBackend.test.js +21 -0
  109. package/dist/src/learning/ragContextPack.js +115 -16
  110. package/dist/src/learning/ragContextPack.test.js +151 -0
  111. package/dist/src/learning/ragEvidenceQuality.js +96 -0
  112. package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
  113. package/dist/src/learning/ragPublicText.js +67 -4
  114. package/dist/src/learning/ragPublicText.test.js +57 -0
  115. package/dist/src/learning/ragSufficiencyJudge.js +11 -1
  116. package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
  117. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
  118. package/dist/src/learning/requestNormalization.js +106 -0
  119. package/dist/src/learning/requestNormalization.test.js +101 -0
  120. package/dist/src/learning/store.js +116 -12
  121. package/dist/src/learning/store.test.js +299 -0
  122. package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
  123. package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
  124. package/dist/src/learning/workspaceHydration.js +168 -0
  125. package/dist/src/learning/workspaceHydration.test.js +30 -0
  126. package/dist/src/license.policy.contract.test.js +9 -4
  127. package/dist/src/markdown/MarkdownGateway.js +4 -0
  128. package/dist/src/markdown/MarkdownGateway.test.js +59 -0
  129. package/dist/src/middleware/SseResponseWriter.js +122 -0
  130. package/dist/src/middleware/SseResponseWriter.test.js +54 -0
  131. package/dist/src/middleware/auth.js +28 -7
  132. package/dist/src/middleware/auth.test.js +33 -0
  133. package/dist/src/middleware/index.js +2 -1
  134. package/dist/src/mobile.android.evidence.contract.test.js +146 -0
  135. package/dist/src/mobile.android.signing.contract.test.js +66 -0
  136. package/dist/src/mobile.artifact.contract.test.js +144 -0
  137. package/dist/src/mobile.budget.contract.test.js +90 -0
  138. package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
  139. package/dist/src/mobile.graph.parity.contract.test.js +207 -0
  140. package/dist/src/mobile.identity.contract.test.js +66 -0
  141. package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
  142. package/dist/src/mobile.identity.corpus.test.js +40 -0
  143. package/dist/src/mobile.native.recovery.contract.test.js +64 -0
  144. package/dist/src/mobile.pipeline.test.js +11 -1
  145. package/dist/src/mobile.profile.contract.test.js +225 -0
  146. package/dist/src/mobile.semantic.parity.test.js +92 -0
  147. package/dist/src/mobile_exact_analyzer.test.js +113 -0
  148. package/dist/src/notemd/ContentGenerator.js +4 -9
  149. package/dist/src/notemd/FileProcessor.js +4 -9
  150. package/dist/src/notemd/NotemdService.js +12 -17
  151. package/dist/src/notemd/Translator.js +4 -9
  152. package/dist/src/notemd/progressReporter.js +9 -0
  153. package/dist/src/notemd.server.integration.test.js +119 -4
  154. package/dist/src/notemd.workflow.pipeline.test.js +1 -1
  155. package/dist/src/pathbridge.handshake.contract.test.js +29 -0
  156. package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
  157. package/dist/src/pathcore.bundle.contract.test.js +8 -0
  158. package/dist/src/platform/ExportProfile.js +28 -0
  159. package/dist/src/platform/MobileBudget.js +11 -0
  160. package/dist/src/platform/PlatformCapabilities.js +14 -0
  161. package/dist/src/platform/PlatformCapabilities.test.js +6 -0
  162. package/dist/src/query_backend.external_http.integration.test.js +2 -2
  163. package/dist/src/release.godot.mirror.contract.test.js +28 -4
  164. package/dist/src/resources/ResourceRegistry.js +40 -0
  165. package/dist/src/resources/ResourceRegistry.test.js +36 -0
  166. package/dist/src/routes/index.js +0 -4
  167. package/dist/src/routes/knowledge.js +46 -33
  168. package/dist/src/routes/knowledgePayload.js +185 -0
  169. package/dist/src/routes/knowledgePayload.test.js +53 -0
  170. package/dist/src/routes/markdown.js +8 -0
  171. package/dist/src/routes/notemd.js +990 -234
  172. package/dist/src/routes/registry.contract.test.js +5 -2
  173. package/dist/src/routes/registry.shadow.contract.test.js +22 -0
  174. package/dist/src/routes/render.js +14 -51
  175. package/dist/src/runtime.capabilities.test.js +18 -2
  176. package/dist/src/server.js +566 -1090
  177. package/dist/src/server.migration.test.js +417 -15
  178. package/dist/src/sidecar.build.lock.contract.test.js +201 -0
  179. package/dist/src/sidecar.freshness.contract.test.js +184 -0
  180. package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
  181. package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
  182. package/dist/src/simulation.worker.offline.test.js +104 -0
  183. package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
  184. package/dist/src/storage.provider.contract.test.js +152 -1
  185. package/dist/src/tauri.frontend.build.contract.test.js +8 -0
  186. package/dist/src/tauri.window.evidence.execution.test.js +68 -0
  187. package/dist/src/utils/RuntimePaths.js +6 -0
  188. package/dist/src/utils/RuntimePaths.test.js +27 -0
  189. package/dist/src/windows.installer.build.test.js +109 -0
  190. package/dist/src/windows.installer.payload.test.js +76 -0
  191. package/dist/src/windows.installer.runtime.test.js +69 -0
  192. package/dist/src/workspace/WorkspaceRegistry.js +13 -0
  193. package/package.json +32 -10
  194. package/dist/src/routes/data.js +0 -267
  195. package/dist/src/routes/diagnostics.js +0 -51
@@ -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 api = (path) => `/api/notemd${path}`;
44
- const json = (res, code, data) => {
45
- res.writeHead(code, { 'Content-Type': 'application/json' });
46
- res.end(JSON.stringify(data));
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
- const readBody = (req) => new Promise((resolve, reject) => {
55
- const chunks = [];
56
- req.on('data', (c) => chunks.push(c));
57
- req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
58
- req.on('error', reject);
59
- });
60
- return [
61
- {
62
- method: 'GET',
63
- path: api('/settings'),
64
- handler: async (_req, res) => {
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
- ok(res, { settings });
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 (e) {
70
- fail(res, e, 'API:GET /api/notemd/settings');
232
+ catch (error) {
233
+ writeApiErrorResponse(res, error, {
234
+ context: 'API:GET /api/notemd/settings',
235
+ requestId,
236
+ });
71
237
  }
72
- },
73
- },
74
- {
75
- method: 'POST',
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 body = await readBody(req);
80
- const payload = JSON.parse(body);
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
- ? await persistNotemdSettings(nextSettings)
86
- : await notemdService.updateSettings(payload);
87
- ok(res, { settings });
88
- }
89
- catch (e) {
90
- fail(res, e, 'API:POST /api/notemd/settings');
91
- }
92
- },
93
- },
94
- {
95
- method: 'POST',
96
- path: api('/test-llm'),
97
- handler: async (req, res) => {
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 body = await readBody(req);
262
+ const payload = await readJsonBody(req);
100
263
  const settings = await loadNotemdSettings();
101
- const result = await notemdService.testLlmConnection(JSON.parse(body), settings);
102
- ok(res, { result });
103
- }
104
- catch (e) {
105
- fail(res, e, 'API:POST /api/notemd/test-llm');
106
- }
107
- },
108
- },
109
- {
110
- method: 'POST',
111
- path: api('/process-file'),
112
- handler: async (req, res) => {
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 body = await readBody(req);
292
+ const payload = await readJsonBody(req);
115
293
  const settings = await loadNotemdSettings();
116
- const result = await notemdService.processFile(JSON.parse(body), settings);
117
- ok(res, { result });
118
- }
119
- catch (e) {
120
- fail(res, e, 'API:POST /api/notemd/process-file');
121
- }
122
- },
123
- },
124
- {
125
- method: 'POST',
126
- path: api('/process-folder'),
127
- handler: async (req, res) => {
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 body = await readBody(req);
389
+ const payload = await readJsonBody(req);
130
390
  const settings = await loadNotemdSettings();
131
- const result = await notemdService.processFolder(JSON.parse(body), settings);
132
- ok(res, { result });
133
- }
134
- catch (e) {
135
- fail(res, e, 'API:POST /api/notemd/process-folder');
136
- }
137
- },
138
- },
139
- {
140
- method: 'POST',
141
- path: api('/generate-content'),
142
- handler: async (req, res) => {
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 body = await readBody(req);
482
+ const payload = await readJsonBody(req);
145
483
  const settings = await loadNotemdSettings();
146
- const result = await notemdService.generateContent(JSON.parse(body), settings);
147
- ok(res, { result });
148
- }
149
- catch (e) {
150
- fail(res, e, 'API:POST /api/notemd/generate-content');
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
- method: 'POST',
156
- path: api('/translate-file'),
157
- handler: async (req, res) => {
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 body = await readBody(req);
539
+ const payload = await readJsonBody(req);
160
540
  const settings = await loadNotemdSettings();
161
- const result = await notemdService.translateFile(JSON.parse(body), settings);
162
- ok(res, { result });
163
- }
164
- catch (e) {
165
- fail(res, e, 'API:POST /api/notemd/translate-file');
166
- }
167
- },
168
- },
169
- {
170
- method: 'POST',
171
- path: api('/fix-mermaid'),
172
- handler: async (req, res) => {
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 raw = await readBody(req);
175
- const { filePath, inPlace } = JSON.parse(raw);
176
- const result = await notemdService.fixMermaid(filePath, inPlace);
177
- ok(res, { result });
178
- }
179
- catch (e) {
180
- fail(res, e, 'API:POST /api/notemd/fix-mermaid');
181
- }
182
- },
183
- },
184
- {
185
- method: 'POST',
186
- path: api('/fix-formulas'),
187
- handler: async (req, res) => {
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 raw = await readBody(req);
190
- const { filePath, inPlace } = JSON.parse(raw);
191
- const result = await notemdService.fixFormulas(filePath, inPlace);
192
- ok(res, { result });
193
- }
194
- catch (e) {
195
- fail(res, e, 'API:POST /api/notemd/fix-formulas');
196
- }
197
- },
198
- },
199
- {
200
- method: 'POST',
201
- path: api('/check-duplicates'),
202
- handler: async (req, res) => {
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 raw = await readBody(req);
205
- const { filePath } = JSON.parse(raw);
206
- const result = await notemdService.checkDuplicates(filePath);
207
- ok(res, { result });
208
- }
209
- catch (e) {
210
- fail(res, e, 'API:POST /api/notemd/check-duplicates');
211
- }
212
- },
213
- },
214
- {
215
- method: 'POST',
216
- path: api('/extract-concepts'),
217
- handler: async (req, res) => {
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 raw = await readBody(req);
668
+ const payload = await readJsonBody(req);
220
669
  const settings = await loadNotemdSettings();
221
- const { filePath } = JSON.parse(raw);
222
- const result = await notemdService.extractConcepts(filePath, settings);
223
- ok(res, { result });
224
- }
225
- catch (e) {
226
- fail(res, e, 'API:POST /api/notemd/extract-concepts');
227
- }
228
- },
229
- },
230
- {
231
- method: 'POST',
232
- path: api('/cancel'),
233
- handler: async (_req, res) => {
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
- notemdService.cancelCurrentOperation();
236
- ok(res, { cancelled: true });
237
- }
238
- catch (e) {
239
- fail(res, e, 'API:POST /api/notemd/cancel');
240
- }
241
- },
242
- },
243
- // ── Diagram generation (obsidian-notemd v1.8.4) ──
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 readBody(req);
789
+ const body = await readJsonBody(req);
250
790
  const settings = await loadNotemdSettings();
251
- const result = await notemdService.generateDiagram(JSON.parse(body), settings);
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 readBody(req);
804
+ const body = await readJsonBody(req);
265
805
  const settings = await loadNotemdSettings();
266
- const result = await notemdService.previewDiagram(JSON.parse(body), settings);
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 readBody(req);
819
+ const body = await readJsonBody(req);
280
820
  const settings = await loadNotemdSettings();
281
- const result = await notemdService.exportDiagram(JSON.parse(body), settings);
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 readBody(req);
835
+ const body = await readJsonBody(req);
296
836
  const settings = await loadNotemdSettings();
297
- const result = await notemdService.search(JSON.parse(body), settings);
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 readBody(req);
326
- const result = await notemdService.diagnoseLlmProvider(JSON.parse(body));
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 readBody(req);
878
+ const body = await readJsonBody(req);
341
879
  const settings = await loadNotemdSettings();
342
- const result = await notemdService.extractOriginalText(JSON.parse(body), settings);
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 readBody(req);
924
+ const body = await readJsonBody(req);
383
925
  const settings = await loadNotemdSettings();
384
- const result = await notemdService.diagnoseLlmProvider(JSON.parse(body), settings);
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
- method: 'POST',
394
- path: api('/one-click-extract'),
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 body = await readBody(req);
938
+ const payload = await readJsonBody(req);
398
939
  const settings = await loadNotemdSettings();
399
- const result = await notemdService.oneClickExtract(JSON.parse(body).filePath, settings);
400
- ok(res, { result });
401
- }
402
- catch (e) {
403
- fail(res, e, 'API:POST /api/notemd/one-click-extract');
404
- }
405
- },
406
- },
407
- {
408
- method: 'POST',
409
- path: api('/batch-fix-mermaid'),
410
- handler: async (req, res) => {
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 raw = await readBody(req);
413
- const { folderPath, inPlace } = JSON.parse(raw);
414
- const result = await notemdService.batchFixMermaid(folderPath, inPlace ?? true);
415
- ok(res, { result });
416
- }
417
- catch (e) {
418
- fail(res, e, 'API:POST /api/notemd/batch-fix-mermaid');
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 readBody(req);
428
- const { folderPath, inPlace } = JSON.parse(raw);
429
- const result = await notemdService.batchFixFormulas(folderPath, inPlace ?? true);
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 readBody(req);
443
- const { folderPath } = JSON.parse(raw);
1025
+ const raw = await readJsonBody(req);
1026
+ const { folderPath } = raw;
444
1027
  const settings = await loadNotemdSettings();
445
- const result = await notemdService.generateFolderContent(folderPath, settings);
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 readBody(req);
459
- const { operationId } = JSON.parse(raw);
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 readBody(req);
1057
+ const raw = await readJsonBody(req);
475
1058
  const settings = await loadNotemdSettings();
476
- const request = JSON.parse(raw);
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 readBody(req);
1077
+ const raw = await readJsonBody(req);
491
1078
  const settings = await loadNotemdSettings();
492
- const request = JSON.parse(raw);
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 readBody(req);
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
  }