@jagilber-org/index-server 1.22.1 → 1.26.4

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 (190) hide show
  1. package/CHANGELOG.md +91 -2
  2. package/CODE_OF_CONDUCT.md +2 -0
  3. package/CONTRIBUTING.md +32 -2
  4. package/README.md +82 -19
  5. package/SECURITY.md +17 -5
  6. package/dist/config/dashboardConfig.d.ts +3 -0
  7. package/dist/config/dashboardConfig.js +3 -0
  8. package/dist/config/defaultValues.d.ts +1 -1
  9. package/dist/config/defaultValues.js +1 -1
  10. package/dist/config/featureConfig.d.ts +2 -0
  11. package/dist/config/featureConfig.js +6 -1
  12. package/dist/config/runtimeConfig.d.ts +1 -1
  13. package/dist/config/runtimeConfig.js +8 -9
  14. package/dist/dashboard/client/admin.html +170 -53
  15. package/dist/dashboard/client/css/admin.css +132 -0
  16. package/dist/dashboard/client/js/admin.auth.js +25 -11
  17. package/dist/dashboard/client/js/admin.config.js +1 -1
  18. package/dist/dashboard/client/js/admin.feedback.js +328 -0
  19. package/dist/dashboard/client/js/admin.graph.js +120 -18
  20. package/dist/dashboard/client/js/admin.instructions.js +27 -13
  21. package/dist/dashboard/client/js/admin.logs.js +1 -5
  22. package/dist/dashboard/client/js/admin.maintenance.js +53 -8
  23. package/dist/dashboard/client/js/admin.messaging.js +1 -4
  24. package/dist/dashboard/client/js/admin.overview.js +5 -1
  25. package/dist/dashboard/client/js/admin.sessions.js +1 -1
  26. package/dist/dashboard/client/js/admin.utils.js +43 -1
  27. package/dist/dashboard/client/js/mermaid.min.js +813 -537
  28. package/dist/dashboard/export/DataExporter.js +2 -1
  29. package/dist/dashboard/server/AdminPanel.d.ts +3 -0
  30. package/dist/dashboard/server/AdminPanel.js +132 -35
  31. package/dist/dashboard/server/ApiRoutes.js +40 -9
  32. package/dist/dashboard/server/DashboardServer.js +1 -1
  33. package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
  34. package/dist/dashboard/server/FileMetricsStorage.js +52 -5
  35. package/dist/dashboard/server/HttpTransport.js +6 -0
  36. package/dist/dashboard/server/InstanceManager.js +7 -2
  37. package/dist/dashboard/server/KnowledgeStore.js +7 -2
  38. package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
  39. package/dist/dashboard/server/MetricsCollector.js +113 -17
  40. package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
  41. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
  42. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
  43. package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
  44. package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
  45. package/dist/dashboard/server/routes/admin.routes.js +35 -27
  46. package/dist/dashboard/server/routes/alerts.routes.js +4 -3
  47. package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
  48. package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
  49. package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
  50. package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
  51. package/dist/dashboard/server/routes/graph.routes.js +10 -13
  52. package/dist/dashboard/server/routes/index.d.ts +1 -0
  53. package/dist/dashboard/server/routes/index.js +74 -39
  54. package/dist/dashboard/server/routes/instances.routes.js +2 -1
  55. package/dist/dashboard/server/routes/instructions.routes.js +46 -27
  56. package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
  57. package/dist/dashboard/server/routes/logs.routes.js +5 -4
  58. package/dist/dashboard/server/routes/messaging.routes.js +15 -14
  59. package/dist/dashboard/server/routes/metrics.routes.js +14 -13
  60. package/dist/dashboard/server/routes/scripts.routes.js +6 -3
  61. package/dist/dashboard/server/routes/status.routes.js +5 -4
  62. package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
  63. package/dist/dashboard/server/routes/usage.routes.js +2 -1
  64. package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
  65. package/dist/dashboard/server/utils/escapeHtml.js +11 -0
  66. package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
  67. package/dist/dashboard/server/utils/pathContainment.js +15 -0
  68. package/dist/dashboard/server/wsInit.js +2 -2
  69. package/dist/lib/mcpStdioLogging.d.ts +165 -0
  70. package/dist/lib/mcpStdioLogging.js +287 -0
  71. package/dist/schemas/index.d.ts +37 -2
  72. package/dist/schemas/index.js +27 -3
  73. package/dist/server/backgroundServicesStartup.d.ts +7 -1
  74. package/dist/server/backgroundServicesStartup.js +25 -8
  75. package/dist/server/certInit.d.ts +97 -0
  76. package/dist/server/certInit.js +359 -0
  77. package/dist/server/certInit.types.d.ts +92 -0
  78. package/dist/server/certInit.types.js +34 -0
  79. package/dist/server/handshake/fallbackFrames.d.ts +31 -0
  80. package/dist/server/handshake/fallbackFrames.js +38 -0
  81. package/dist/server/handshake/initializeDetector.d.ts +31 -0
  82. package/dist/server/handshake/initializeDetector.js +88 -0
  83. package/dist/server/handshake/protocol.d.ts +15 -0
  84. package/dist/server/handshake/protocol.js +37 -0
  85. package/dist/server/handshake/readyEmitter.d.ts +6 -0
  86. package/dist/server/handshake/readyEmitter.js +88 -0
  87. package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
  88. package/dist/server/handshake/safetyFallbacks.js +134 -0
  89. package/dist/server/handshake/stdinSniffer.d.ts +1 -0
  90. package/dist/server/handshake/stdinSniffer.js +260 -0
  91. package/dist/server/handshake/tracing.d.ts +16 -0
  92. package/dist/server/handshake/tracing.js +95 -0
  93. package/dist/server/handshakeManager.d.ts +23 -23
  94. package/dist/server/handshakeManager.js +36 -466
  95. package/dist/server/index-server.d.ts +23 -0
  96. package/dist/server/index-server.js +194 -9
  97. package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
  98. package/dist/server/mcpReadOnlySurfaces.js +297 -0
  99. package/dist/server/sdkServer.js +69 -7
  100. package/dist/server/transport.d.ts +5 -6
  101. package/dist/server/transport.js +46 -64
  102. package/dist/server/transportFactory.d.ts +3 -9
  103. package/dist/server/transportFactory.js +18 -380
  104. package/dist/services/atomicFs.d.ts +3 -0
  105. package/dist/services/atomicFs.js +171 -13
  106. package/dist/services/auditLog.d.ts +17 -2
  107. package/dist/services/auditLog.js +75 -14
  108. package/dist/services/bootstrapGating.js +1 -1
  109. package/dist/services/categoryRules.d.ts +10 -0
  110. package/dist/services/categoryRules.js +17 -0
  111. package/dist/services/classificationService.js +7 -5
  112. package/dist/services/embeddingService.d.ts +27 -11
  113. package/dist/services/embeddingService.js +51 -14
  114. package/dist/services/feedbackStorage.d.ts +39 -0
  115. package/dist/services/feedbackStorage.js +88 -0
  116. package/dist/services/handlers/instructions.add.js +429 -317
  117. package/dist/services/handlers/instructions.groom.js +128 -31
  118. package/dist/services/handlers/instructions.import.js +56 -23
  119. package/dist/services/handlers/instructions.patch.js +43 -32
  120. package/dist/services/handlers/instructions.query.js +20 -29
  121. package/dist/services/handlers/instructions.shared.d.ts +54 -0
  122. package/dist/services/handlers/instructions.shared.js +126 -1
  123. package/dist/services/handlers.activation.js +83 -81
  124. package/dist/services/handlers.dashboardConfig.d.ts +2 -2
  125. package/dist/services/handlers.dashboardConfig.js +1 -2
  126. package/dist/services/handlers.diagnostics.js +75 -54
  127. package/dist/services/handlers.feedback.d.ts +4 -11
  128. package/dist/services/handlers.feedback.js +11 -333
  129. package/dist/services/handlers.gates.js +69 -37
  130. package/dist/services/handlers.graph.js +2 -2
  131. package/dist/services/handlers.help.js +2 -2
  132. package/dist/services/handlers.instructionSchema.js +4 -2
  133. package/dist/services/handlers.integrity.js +42 -22
  134. package/dist/services/handlers.messaging.js +1 -1
  135. package/dist/services/handlers.metrics.js +51 -6
  136. package/dist/services/handlers.prompt.js +10 -2
  137. package/dist/services/handlers.search.js +94 -44
  138. package/dist/services/handlers.trace.js +1 -1
  139. package/dist/services/handlers.usage.js +38 -7
  140. package/dist/services/indexContext.d.ts +21 -1
  141. package/dist/services/indexContext.js +263 -78
  142. package/dist/services/indexLoader.d.ts +1 -0
  143. package/dist/services/indexLoader.js +28 -8
  144. package/dist/services/instructionRecordValidation.d.ts +39 -0
  145. package/dist/services/instructionRecordValidation.js +388 -0
  146. package/dist/services/instructions.dispatcher.js +4 -4
  147. package/dist/services/loaderSchemaValidator.d.ts +15 -0
  148. package/dist/services/loaderSchemaValidator.js +69 -0
  149. package/dist/services/logger.js +11 -2
  150. package/dist/services/mcpLogBridge.d.ts +49 -0
  151. package/dist/services/mcpLogBridge.js +83 -0
  152. package/dist/services/ownershipService.js +18 -8
  153. package/dist/services/performanceBaseline.js +23 -22
  154. package/dist/services/promptReviewService.d.ts +3 -1
  155. package/dist/services/promptReviewService.js +41 -13
  156. package/dist/services/regexSafety.d.ts +6 -0
  157. package/dist/services/regexSafety.js +46 -0
  158. package/dist/services/seedBootstrap.js +1 -1
  159. package/dist/services/storage/factory.d.ts +14 -1
  160. package/dist/services/storage/factory.js +61 -1
  161. package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
  162. package/dist/services/storage/jsonEmbeddingStore.js +83 -0
  163. package/dist/services/storage/jsonFileStore.d.ts +3 -1
  164. package/dist/services/storage/jsonFileStore.js +8 -6
  165. package/dist/services/storage/migrationEngine.d.ts +13 -0
  166. package/dist/services/storage/migrationEngine.js +31 -0
  167. package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
  168. package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
  169. package/dist/services/storage/sqliteStore.d.ts +3 -1
  170. package/dist/services/storage/sqliteStore.js +2 -2
  171. package/dist/services/storage/types.d.ts +48 -1
  172. package/dist/services/toolRegistry.js +77 -67
  173. package/dist/services/toolRegistry.zod.js +89 -86
  174. package/dist/services/tracing.js +5 -4
  175. package/dist/utils/envUtils.d.ts +4 -0
  176. package/dist/utils/envUtils.js +7 -0
  177. package/dist/utils/memoryMonitor.js +11 -10
  178. package/package.json +12 -4
  179. package/schemas/instruction.schema.json +38 -1
  180. package/scripts/copy-dashboard-assets.mjs +1 -1
  181. package/scripts/dist/README.md +1 -1
  182. package/scripts/generate-certs.mjs +201 -0
  183. package/scripts/setup-wizard.mjs +781 -0
  184. package/server.json +20 -0
  185. package/dist/externalClientLib.d.ts +0 -1
  186. package/dist/externalClientLib.js +0 -2
  187. package/dist/portableClientWrapper.d.ts +0 -1
  188. package/dist/portableClientWrapper.js +0 -2
  189. package/dist/services/indexingService.d.ts +0 -1
  190. package/dist/services/indexingService.js +0 -2
@@ -3,407 +3,45 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dynamicImport = void 0;
4
4
  exports.setupStdoutDiagnostics = setupStdoutDiagnostics;
5
5
  exports.setupDispatcherOverride = setupDispatcherOverride;
6
- exports.wrapTransportSend = wrapTransportSend;
7
6
  exports.setupKeepalive = setupKeepalive;
8
7
  /* eslint-disable @typescript-eslint/no-explicit-any */
9
8
  /**
10
- * Transport initialization, request dispatcher override, and diagnostics
11
- * wrappers for the MCP server.
9
+ * Transport bootstrap helpers for the MCP server.
12
10
  */
13
11
  const runtimeConfig_1 = require("../config/runtimeConfig");
14
- const handshakeManager_1 = require("./handshakeManager");
15
12
  // Helper to perform a true dynamic ESM import that TypeScript won't down-level to require()
16
13
  const dynamicImport = (specifier) => (Function('m', 'return import(m);'))(specifier);
17
14
  exports.dynamicImport = dynamicImport;
18
15
  /**
19
- * Set up stdout diagnostics wrapper for backpressure monitoring.
16
+ * Emit a lightweight diagnostic marker without intercepting stdout writes.
20
17
  * Enabled via INDEX_SERVER_TRACE=healthMixed.
21
18
  */
22
19
  async function setupStdoutDiagnostics() {
23
- const __diagEnabled = (0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed');
24
- const __diag = (msg) => { if (__diagEnabled) {
25
- try {
26
- process.stderr.write(`[diag] ${Date.now()} ${msg}\n`);
27
- }
28
- catch { /* ignore */ }
29
- } };
30
- // Emit a one-time version marker so tests can assert the newer diagnostic wrapper code is actually loaded.
31
- if (__diagEnabled) {
32
- try {
33
- const buildMarker = 'sdkServerDiagV1';
34
- // Include a coarse content hash surrogate: file size + mtime if available
35
- let fsMeta = '';
36
- try {
37
- const fsMod = await import('fs');
38
- const stat = fsMod.statSync(__filename);
39
- fsMeta = ` size=${stat.size} mtimeMs=${Math.trunc(stat.mtimeMs)}`;
40
- }
41
- catch { /* ignore meta */ }
42
- process.stderr.write(`[diag] ${Date.now()} diag_start marker=${buildMarker}${fsMeta}\n`);
43
- }
44
- catch { /* ignore */ }
45
- }
46
- if (__diagEnabled) {
47
- try {
48
- const origWrite = process.stdout.write.bind(process.stdout);
49
- let backpressureEvents = 0;
50
- let bytesTotal = 0;
51
- let lastReportAt = Date.now();
52
- process.stdout.on?.('drain', () => { __diag('stdout_drain'); });
53
- process.stdout.write = function (chunk, encoding, cb) {
54
- try {
55
- const size = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(String(chunk));
56
- bytesTotal += size;
57
- const ret = origWrite(chunk, encoding, cb);
58
- if (!ret) {
59
- backpressureEvents++;
60
- __diag(`stdout_backpressure size=${size} backpressureEvents=${backpressureEvents}`);
61
- }
62
- const now = Date.now();
63
- if (now - lastReportAt > 2000) {
64
- __diag(`stdout_summary bytesTotal=${bytesTotal} backpressureEvents=${backpressureEvents}`);
65
- lastReportAt = now;
66
- }
67
- return ret;
68
- }
69
- catch (e) {
70
- try {
71
- __diag(`stdout_write_wrapper_error ${e?.message || String(e)}`);
72
- }
73
- catch { /* ignore */ }
74
- return origWrite(chunk, encoding, cb);
75
- }
76
- };
77
- }
78
- catch { /* ignore */ }
79
- }
80
- }
81
- // Robust semantic error preservation: deep scan for JSON-RPC code/message
82
- function deepScan(obj, depth = 0, seen = new Set()) {
83
- if (!obj || typeof obj !== 'object' || depth > 4 || seen.has(obj))
84
- return undefined;
85
- seen.add(obj);
86
- if (Number.isSafeInteger(obj.code)) {
87
- const c = obj.code;
88
- if (c === -32601 || c === -32602)
89
- return c; // prioritize semantic validation codes
90
- }
91
- // Prefer specific well-known nesting keys first
92
- const keys = ['error', 'original', 'cause', 'data'];
93
- for (const k of keys) {
94
- try {
95
- const child = obj[k];
96
- const found = deepScan(child, depth + 1, seen);
97
- if (found !== undefined)
98
- return found;
99
- }
100
- catch { /* ignore */ }
101
- }
102
- // Fallback: generic property iteration (shallow) to catch unexpected wrappers
103
- if (depth < 2) {
104
- try {
105
- for (const v of Object.values(obj)) {
106
- const found = deepScan(v, depth + 1, seen);
107
- if (found !== undefined)
108
- return found;
109
- }
110
- }
111
- catch { /* ignore */ }
112
- }
113
- return undefined;
114
- }
115
- // Categorize request for diagnostics
116
- function categorizeRequest(request) {
117
- const metaName = request.method === 'tools/call' ? request?.params?.name : '';
118
- if (request.method === 'initialize')
119
- return 'init';
120
- if (request.method === 'health_check' || metaName === 'health_check')
121
- return 'health';
122
- if (request.method === 'metrics_snapshot' || metaName === 'metrics_snapshot')
123
- return 'metrics';
124
- if (metaName === 'meta_tools')
125
- return 'meta';
126
- return 'other';
127
- }
128
- /**
129
- * Override the internal request dispatcher to retain error.data & emit diagnostics.
130
- * The upstream SDK has used both `_onRequest` (camel) and `_onrequest` (lower) across versions;
131
- * we defensively hook whichever exists and assign our wrapper to BOTH names.
132
- */
133
- function setupDispatcherOverride(server) {
134
- const existingLower = server._onrequest;
135
- const existingCamel = server._onRequest;
136
- const originalOnRequest = (existingCamel || existingLower) ? (existingCamel || existingLower).bind(server) : undefined;
137
- let __diagQueueDepth = 0;
138
- if (originalOnRequest) {
139
- const wrapped = function (request) {
140
- const diagEnabled = (0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed');
141
- let startedAt;
142
- if (diagEnabled) {
143
- const category = categorizeRequest(request);
144
- if (category === 'health' || category === 'meta' || category === 'metrics' || category === 'init') {
145
- startedAt = Date.now();
146
- try {
147
- __diagQueueDepth++;
148
- process.stderr.write(`[diag] ${startedAt} rq_enqueue method=${request.method} cat=${category} id=${request.id} qdepth=${__diagQueueDepth}\n`);
149
- if (category === 'health') {
150
- if (!server.__firstHealthEnqueueAt) {
151
- server.__firstHealthEnqueueAt = startedAt;
152
- }
153
- if (request.id === 1 && !server.__healthId1EnqueueAt) {
154
- server.__healthId1EnqueueAt = startedAt;
155
- }
156
- }
157
- if (!server.__activeDiagRequests) {
158
- server.__activeDiagRequests = new Map();
159
- }
160
- server.__activeDiagRequests.set(request.id, { id: request.id, method: request.method, cat: category, start: startedAt });
161
- // Mis-order detection: health/metrics/meta before initialize observed
162
- if ((category === 'health' || category === 'metrics' || category === 'meta') && !server.__sawInitializeRequest) {
163
- try {
164
- process.stderr.write(`[diag] ${Date.now()} rq_misorder_before_init method=${request.method} id=${request.id} cat=${category} qdepth=${__diagQueueDepth}\n`);
165
- }
166
- catch { /* ignore */ }
167
- }
168
- }
169
- catch { /* ignore */ }
170
- }
171
- }
172
- const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
173
- if (handler === undefined) {
174
- return this._transport?.send({ jsonrpc: '2.0', id: request.id, error: { code: -32601, message: 'Method not found', data: { method: request.method } } }).catch(() => { });
175
- }
176
- const abortController = new AbortController();
177
- this._requestHandlerAbortControllers.set(request.id, abortController);
178
- // IMPORTANT: We intentionally never early-return without sending a response
179
- Promise.resolve()
180
- .then(() => handler(request, { signal: abortController.signal }))
181
- .then((result) => {
182
- if (startedAt !== undefined) {
183
- try {
184
- const dur = Date.now() - startedAt;
185
- const category = categorizeRequest(request);
186
- if (category === 'health' || category === 'meta' || category === 'metrics' || category === 'init') {
187
- __diagQueueDepth = Math.max(0, __diagQueueDepth - 1);
188
- process.stderr.write(`[diag] ${Date.now()} rq_complete method=${request.method} cat=${category} id=${request.id} dur_ms=${dur} qdepth=${__diagQueueDepth}\n`);
189
- try {
190
- server.__activeDiagRequests?.delete(request.id);
191
- }
192
- catch { /* ignore */ }
193
- }
194
- }
195
- catch { /* ignore */ }
196
- }
197
- if (abortController.signal.aborted) {
198
- try {
199
- if ((0, runtimeConfig_1.getRuntimeConfig)().logging.verbose)
200
- process.stderr.write(`[rpc] aborted-but-sending method=${request.method} id=${request.id}\n`);
201
- }
202
- catch { /* ignore */ }
203
- }
204
- else {
205
- try {
206
- if ((0, runtimeConfig_1.getRuntimeConfig)().logging.verbose)
207
- process.stderr.write(`[rpc] response method=${request.method} id=${request.id} ok\n`);
208
- }
209
- catch { /* ignore */ }
210
- }
211
- const sendPromise = this._transport?.send({ jsonrpc: '2.0', id: request.id, result });
212
- if (request.method === 'initialize') {
213
- (0, handshakeManager_1.initFrameLog)('dispatcher_before_send', { id: request.id });
214
- (sendPromise?.then?.(() => {
215
- (0, handshakeManager_1.initFrameLog)('dispatcher_send_resolved', { id: request.id });
216
- server.__initResponseSent = true;
217
- setTimeout(() => (0, handshakeManager_1.emitReadyGlobal)(server, 'transport-send-hook'), 0);
218
- }))?.catch(() => { });
219
- }
220
- return sendPromise;
221
- }, (error) => {
222
- if (startedAt !== undefined) {
223
- try {
224
- const dur = Date.now() - startedAt;
225
- const category = categorizeRequest(request);
226
- if (category === 'health' || category === 'meta' || category === 'metrics' || category === 'init') {
227
- __diagQueueDepth = Math.max(0, __diagQueueDepth - 1);
228
- process.stderr.write(`[diag] ${Date.now()} rq_error method=${request.method} cat=${category} id=${request.id} dur_ms=${dur} qdepth=${__diagQueueDepth}\n`);
229
- try {
230
- server.__activeDiagRequests?.delete(request.id);
231
- }
232
- catch { /* ignore */ }
233
- }
234
- }
235
- catch { /* ignore */ }
236
- }
237
- if (abortController.signal.aborted) {
238
- try {
239
- if ((0, runtimeConfig_1.getRuntimeConfig)().logging.verbose)
240
- process.stderr.write(`[rpc] aborted-error-path method=${request.method} id=${request.id}\n`);
241
- }
242
- catch { /* ignore */ }
243
- }
244
- // Robust semantic error preservation: search multiple nests for a JSON-RPC code/message
245
- let errCode = error?.code;
246
- if (!Number.isSafeInteger(errCode))
247
- errCode = error?.data?.code;
248
- if (!Number.isSafeInteger(errCode))
249
- errCode = error?.original?.code;
250
- if (!Number.isSafeInteger(errCode))
251
- errCode = error?.cause?.code;
252
- if (!Number.isSafeInteger(errCode))
253
- errCode = error?.error?.code;
254
- const rawBeforeDeep = errCode;
255
- if (!Number.isSafeInteger(errCode) || errCode === -32603) {
256
- const deep = deepScan(error);
257
- if (Number.isSafeInteger(deep))
258
- errCode = deep;
259
- }
260
- const safeCode = Number.isSafeInteger(errCode) ? errCode : undefined;
261
- let errMessage = error?.message;
262
- if (!errMessage)
263
- errMessage = error?.data?.message;
264
- if (!errMessage)
265
- errMessage = error?.original?.message;
266
- if (!errMessage)
267
- errMessage = error?.cause?.message;
268
- if (!errMessage)
269
- errMessage = error?.error?.message;
270
- if (typeof errMessage !== 'string' || !errMessage.trim())
271
- errMessage = 'Internal error';
272
- let data = error?.data;
273
- if (data && typeof data === 'object') {
274
- if (typeof data.message !== 'string')
275
- data = { ...data, message: errMessage };
276
- }
277
- else if (error && typeof error === 'object') {
278
- data = { message: errMessage, ...(error.method ? { method: error.method } : {}) };
279
- }
280
- let finalCode = (safeCode !== undefined) ? safeCode : -32603;
281
- if (finalCode === -32603 && data && typeof data === 'object') {
282
- try {
283
- const reason = data.reason || data.data?.reason;
284
- if (reason === 'missing_action')
285
- finalCode = -32602;
286
- else if (reason === 'unknown_action' || reason === 'mutation_disabled' || reason === 'unknown_handler')
287
- finalCode = -32601;
288
- }
289
- catch { /* ignore */ }
290
- }
291
- try {
292
- if ((0, runtimeConfig_1.getRuntimeConfig)().logging.verbose) {
293
- const before = Number.isSafeInteger(rawBeforeDeep) ? rawBeforeDeep : 'n/a';
294
- if ((before === 'n/a' || before === -32603) && (finalCode === -32601 || finalCode === -32602)) {
295
- const reasonHint = data?.reason || data?.data?.reason;
296
- process.stderr.write(`[rpc] deep_recover_semantic code=${finalCode} from=${before} reasonHint=${reasonHint || ''}\n`);
297
- }
298
- }
299
- }
300
- catch { /* ignore */ }
301
- try {
302
- if ((0, runtimeConfig_1.getRuntimeConfig)().logging.verbose)
303
- process.stderr.write(`[rpc] response method=${request.method} id=${request.id} error=${errMessage} code=${finalCode}\n`);
304
- }
305
- catch { /* ignore */ }
306
- return this._transport?.send({ jsonrpc: '2.0', id: request.id, error: { code: finalCode, message: errMessage, data } });
307
- })
308
- .catch(() => { })
309
- .finally(() => { this._requestHandlerAbortControllers.delete(request.id); });
310
- };
311
- // Attach wrapper to BOTH potential internal symbols to guarantee interception.
312
- server._onRequest = wrapped;
313
- server._onrequest = wrapped;
314
- server.__dispatcherOverrideActive = true;
315
- try {
316
- if ((0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed'))
317
- process.stderr.write(`[diag] ${Date.now()} dispatcher_override applied props=${[existingCamel ? '_onRequest(original)' : '', existingLower ? '_onrequest(original)' : ''].filter(Boolean).join(',') || 'none'}\n`);
318
- }
319
- catch { /* ignore */ }
320
- // Starvation watchdog
321
- if ((0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed') && !server.__starvationWatchdogStarted) {
322
- server.__starvationWatchdogStarted = true;
323
- let ticks = 0;
324
- const iv = setInterval(() => {
325
- try {
326
- ticks++;
327
- const active = server.__activeDiagRequests;
328
- const firstH = server.__healthId1EnqueueAt;
329
- if (active && active.size) {
330
- const pending = Array.from(active.values()).map((r) => ({ id: r.id, cat: r.cat, age: Date.now() - r.start })).sort((a, b) => a.id - b.id).slice(0, 12);
331
- const hasHealth1 = !!active.get?.(1);
332
- if (hasHealth1 || (firstH && Date.now() - firstH > 40)) {
333
- process.stderr.write(`[diag] ${Date.now()} starvation_watchdog tick=${ticks} pending=${pending.length} details=${JSON.stringify(pending)} firstHealthAge=${firstH ? Date.now() - firstH : -1} hasHealth1=${hasHealth1}\n`);
334
- }
335
- if (!hasHealth1 && firstH && Date.now() - firstH > 400) {
336
- process.stderr.write(`[diag] ${Date.now()} starvation_watchdog_health1_missing age=${Date.now() - firstH}\n`);
337
- }
338
- }
339
- if (ticks > 40 || (server.__activeDiagRequests && !server.__activeDiagRequests.get(1))) {
340
- clearInterval(iv);
341
- }
342
- }
343
- catch { /* ignore */ }
344
- }, 25);
345
- iv.unref?.();
346
- }
347
- }
348
- else if ((0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed')) {
349
- try {
350
- process.stderr.write(`[diag] ${Date.now()} dispatcher_override_skipped no_original_handler_found`);
351
- }
352
- catch { /* ignore */ }
353
- }
354
- // Enumerate server properties once for debugging missing override
20
+ if (!(0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed'))
21
+ return;
355
22
  try {
356
- if ((0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed')) {
357
- const props = Object.getOwnPropertyNames(server).filter(p => /_on|request|handler/i.test(p)).slice(0, 60);
358
- process.stderr.write(`[diag] ${Date.now()} server_props ${props.join(',')}\n`);
23
+ const buildMarker = 'sdkServerDiagV2';
24
+ let fsMeta = '';
25
+ try {
26
+ const fsMod = await import('fs');
27
+ const stat = fsMod.statSync(__filename);
28
+ fsMeta = ` size=${stat.size} mtimeMs=${Math.trunc(stat.mtimeMs)}`;
359
29
  }
30
+ catch { /* ignore meta */ }
31
+ process.stderr.write(`[diag] ${Date.now()} diag_start marker=${buildMarker}${fsMeta}\n`);
360
32
  }
361
33
  catch { /* ignore */ }
362
34
  }
363
35
  /**
364
- * Wrap transport.send to detect initialize response flush and emit ready.
36
+ * Keep diagnostics on public surfaces only; private SDK dispatcher hooks are no longer patched.
365
37
  */
366
- function wrapTransportSend(server, transport) {
38
+ function setupDispatcherOverride(_server) {
39
+ if (!(0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed'))
40
+ return;
367
41
  try {
368
- const origSend = transport?.send?.bind(transport);
369
- if (origSend && !transport.__wrappedForReady) {
370
- transport.__wrappedForReady = true;
371
- transport.send = (msg) => {
372
- let isInitResult = false;
373
- try {
374
- isInitResult = !!(msg && typeof msg === 'object' && 'id' in msg && msg.result && msg.result.protocolVersion);
375
- }
376
- catch { /* ignore */ }
377
- const sendPromise = origSend(msg);
378
- // Fallback completion / error logging if dispatcher override not active
379
- try {
380
- if ((0, runtimeConfig_1.getRuntimeConfig)().trace.has('healthMixed') && !server.__dispatcherOverrideActive && msg && typeof msg === 'object' && Object.prototype.hasOwnProperty.call(msg, 'id')) {
381
- const map = server.__diagRQMap;
382
- if (map && map.has(msg.id)) {
383
- const rec = map.get(msg.id);
384
- map.delete(msg.id);
385
- server.__diagQueueDepthSniff = Math.max(0, server.__diagQueueDepthSniff - 1);
386
- const kind = msg.error ? 'rq_error' : 'rq_complete';
387
- const dur = Date.now() - rec.start;
388
- process.stderr.write(`[diag] ${Date.now()} ${kind} method=${rec.method} cat=${rec.cat} id=${msg.id} dur_ms=${dur} qdepth=${server.__diagQueueDepthSniff} src=sniff-send\n`);
389
- }
390
- }
391
- }
392
- catch { /* ignore */ }
393
- if (isInitResult && !server.__readyNotified) {
394
- server.__sawInitializeRequest = true;
395
- (0, handshakeManager_1.initFrameLog)('transport_detect_init_result', { id: msg.id });
396
- sendPromise?.then?.(() => {
397
- (0, handshakeManager_1.initFrameLog)('transport_send_resolved', { id: msg.id });
398
- server.__initResponseSent = true;
399
- setTimeout(() => (0, handshakeManager_1.emitReadyGlobal)(server, 'transport-send-hook-dynamic'), 0);
400
- })?.catch?.(() => { });
401
- }
402
- return sendPromise;
403
- };
404
- }
42
+ process.stderr.write(`[diag] ${Date.now()} dispatcher_override disabled public_api_only\n`);
405
43
  }
406
- catch { /* ignore wrapper errors */ }
44
+ catch { /* ignore */ }
407
45
  }
408
46
  /**
409
47
  * Explicit keepalive to avoid premature process exit before first client request.
@@ -20,3 +20,6 @@
20
20
  * @throws Will throw if all write/rename attempts are exhausted due to non-transient errors
21
21
  */
22
22
  export declare function atomicWriteJson(filePath: string, obj: unknown): void;
23
+ export declare function atomicCreateJson(filePath: string, obj: unknown): void;
24
+ export declare function atomicWriteJsonAsync(filePath: string, obj: unknown): Promise<void>;
25
+ export declare function atomicCreateJsonAsync(filePath: string, obj: unknown): Promise<void>;
@@ -4,10 +4,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.atomicWriteJson = atomicWriteJson;
7
+ exports.atomicCreateJson = atomicCreateJson;
8
+ exports.atomicWriteJsonAsync = atomicWriteJsonAsync;
9
+ exports.atomicCreateJsonAsync = atomicCreateJsonAsync;
7
10
  const fs_1 = __importDefault(require("fs"));
8
11
  const path_1 = __importDefault(require("path"));
9
12
  const crypto_1 = __importDefault(require("crypto"));
10
13
  const runtimeConfig_1 = require("../config/runtimeConfig");
14
+ const TRANSIENT_WRITE_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
15
+ const TRANSIENT_RENAME_CODES = new Set(['EPERM', 'EBUSY', 'EACCES', 'ENOENT']);
16
+ function sleep(ms) {
17
+ return new Promise(resolve => setTimeout(resolve, ms));
18
+ }
19
+ function getBackoffMs(baseBackoff, attempt) {
20
+ return baseBackoff * Math.pow(2, attempt - 1) + Math.floor(Math.random() * baseBackoff);
21
+ }
22
+ function isTransientError(error, codes) {
23
+ const code = error.code;
24
+ return typeof code === 'string' && codes.has(code);
25
+ }
11
26
  /**
12
27
  * Atomically write JSON to disk with robust retry semantics for shared index scenarios.
13
28
  *
@@ -36,20 +51,18 @@ function atomicWriteJson(filePath, obj) {
36
51
  const data = JSON.stringify(obj, null, 2);
37
52
  const atomicConfig = (0, runtimeConfig_1.getRuntimeConfig)().atomicFs;
38
53
  const maxAttempts = Math.max(1, atomicConfig.retries);
39
- const baseBackoff = Math.max(1, atomicConfig.backoffMs);
40
54
  let lastErr = null;
41
55
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
42
56
  const tmp = path_1.default.join(dir, `.${path_1.default.basename(filePath)}.${crypto_1.default.randomBytes(6).toString('hex')}.tmp`);
43
57
  try {
44
- fs_1.default.writeFileSync(tmp, data, 'utf8');
58
+ fs_1.default.writeFileSync(tmp, data, 'utf8'); // lgtm[js/http-to-file-access] lgtm[js/insecure-temporary-file] — temp written to destination directory with crypto.randomBytes(6) suffix; not /tmp
45
59
  try {
46
60
  fs_1.default.renameSync(tmp, filePath);
47
61
  return; // success
48
62
  }
49
63
  catch (renameErr) {
50
64
  // If rename failed, decide whether to retry
51
- const code = renameErr.code;
52
- const transient = code === 'EPERM' || code === 'EBUSY' || code === 'EACCES' || code === 'ENOENT';
65
+ const transient = isTransientError(renameErr, TRANSIENT_RENAME_CODES);
53
66
  if (!transient || attempt === maxAttempts) {
54
67
  // On final attempt we do NOT fallback to direct write to preserve atomic semantics; propagate.
55
68
  lastErr = renameErr;
@@ -66,16 +79,11 @@ function atomicWriteJson(filePath, obj) {
66
79
  fs_1.default.unlinkSync(tmp);
67
80
  }
68
81
  catch { /* ignore */ }
69
- // Backoff (exponential + jitter)
70
- const sleepMs = baseBackoff * Math.pow(2, attempt - 1) + Math.floor(Math.random() * baseBackoff);
71
- const start = Date.now();
72
- while (Date.now() - start < sleepMs) { /* busy-wait tiny backoff (short durations) */ }
73
82
  continue; // retry loop
74
83
  }
75
84
  }
76
85
  catch (writeErr) {
77
- const code = writeErr.code;
78
- const transient = code === 'EPERM' || code === 'EBUSY' || code === 'EACCES';
86
+ const transient = isTransientError(writeErr, TRANSIENT_WRITE_CODES);
79
87
  if (!transient || attempt === maxAttempts) {
80
88
  lastErr = writeErr;
81
89
  try {
@@ -91,9 +99,6 @@ function atomicWriteJson(filePath, obj) {
91
99
  fs_1.default.unlinkSync(tmp);
92
100
  }
93
101
  catch { /* ignore */ }
94
- const sleepMs = baseBackoff * Math.pow(2, attempt - 1) + Math.floor(Math.random() * baseBackoff);
95
- const start = Date.now();
96
- while (Date.now() - start < sleepMs) { /* busy-wait */ }
97
102
  continue;
98
103
  }
99
104
  }
@@ -101,3 +106,156 @@ function atomicWriteJson(filePath, obj) {
101
106
  const err = lastErr instanceof Error ? lastErr : new Error('atomicWriteJson failed');
102
107
  throw err;
103
108
  }
109
+ function atomicCreateJson(filePath, obj) {
110
+ const dir = path_1.default.dirname(filePath);
111
+ if (!fs_1.default.existsSync(dir))
112
+ fs_1.default.mkdirSync(dir, { recursive: true });
113
+ const data = JSON.stringify(obj, null, 2);
114
+ const atomicConfig = (0, runtimeConfig_1.getRuntimeConfig)().atomicFs;
115
+ const maxAttempts = Math.max(1, atomicConfig.retries);
116
+ let lastErr = null;
117
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
118
+ const tmp = path_1.default.join(dir, `.${path_1.default.basename(filePath)}.${crypto_1.default.randomBytes(6).toString('hex')}.tmp`);
119
+ try {
120
+ fs_1.default.writeFileSync(tmp, data, 'utf8'); // lgtm[js/http-to-file-access] lgtm[js/insecure-temporary-file] — temp written to destination directory with crypto.randomBytes(6) suffix; not /tmp
121
+ try {
122
+ fs_1.default.linkSync(tmp, filePath);
123
+ try {
124
+ fs_1.default.unlinkSync(tmp);
125
+ }
126
+ catch { /* ignore */ }
127
+ return;
128
+ }
129
+ catch (linkErr) {
130
+ const code = linkErr.code;
131
+ try {
132
+ if (fs_1.default.existsSync(tmp))
133
+ fs_1.default.unlinkSync(tmp);
134
+ }
135
+ catch { /* ignore */ }
136
+ if (code === 'EEXIST') {
137
+ lastErr = linkErr;
138
+ break;
139
+ }
140
+ const transient = isTransientError(linkErr, TRANSIENT_RENAME_CODES);
141
+ if (!transient || attempt === maxAttempts) {
142
+ lastErr = linkErr;
143
+ break;
144
+ }
145
+ lastErr = linkErr;
146
+ continue;
147
+ }
148
+ }
149
+ catch (writeErr) {
150
+ const transient = isTransientError(writeErr, TRANSIENT_WRITE_CODES);
151
+ try {
152
+ if (fs_1.default.existsSync(tmp))
153
+ fs_1.default.unlinkSync(tmp);
154
+ }
155
+ catch { /* ignore */ }
156
+ if (!transient || attempt === maxAttempts) {
157
+ lastErr = writeErr;
158
+ break;
159
+ }
160
+ lastErr = writeErr;
161
+ continue;
162
+ }
163
+ }
164
+ const err = lastErr instanceof Error ? lastErr : new Error('atomicCreateJson failed');
165
+ throw err;
166
+ }
167
+ async function atomicWriteJsonAsync(filePath, obj) {
168
+ const dir = path_1.default.dirname(filePath);
169
+ if (!fs_1.default.existsSync(dir))
170
+ fs_1.default.mkdirSync(dir, { recursive: true });
171
+ const data = JSON.stringify(obj, null, 2);
172
+ const atomicConfig = (0, runtimeConfig_1.getRuntimeConfig)().atomicFs;
173
+ const maxAttempts = Math.max(1, atomicConfig.retries);
174
+ const baseBackoff = Math.max(1, atomicConfig.backoffMs);
175
+ let lastErr = null;
176
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
177
+ const tmp = path_1.default.join(dir, `.${path_1.default.basename(filePath)}.${crypto_1.default.randomBytes(6).toString('hex')}.tmp`);
178
+ try {
179
+ fs_1.default.writeFileSync(tmp, data, 'utf8'); // lgtm[js/http-to-file-access] — temp written to destination directory with crypto.randomBytes(6) suffix; not /tmp
180
+ try {
181
+ fs_1.default.renameSync(tmp, filePath);
182
+ return;
183
+ }
184
+ catch (renameErr) {
185
+ const transient = isTransientError(renameErr, TRANSIENT_RENAME_CODES);
186
+ lastErr = renameErr;
187
+ try {
188
+ if (fs_1.default.existsSync(tmp))
189
+ fs_1.default.unlinkSync(tmp);
190
+ }
191
+ catch { /* ignore */ }
192
+ if (!transient || attempt === maxAttempts)
193
+ break;
194
+ await sleep(getBackoffMs(baseBackoff, attempt));
195
+ }
196
+ }
197
+ catch (writeErr) {
198
+ const transient = isTransientError(writeErr, TRANSIENT_WRITE_CODES);
199
+ lastErr = writeErr;
200
+ try {
201
+ if (fs_1.default.existsSync(tmp))
202
+ fs_1.default.unlinkSync(tmp);
203
+ }
204
+ catch { /* ignore */ }
205
+ if (!transient || attempt === maxAttempts)
206
+ break;
207
+ await sleep(getBackoffMs(baseBackoff, attempt));
208
+ }
209
+ }
210
+ throw lastErr instanceof Error ? lastErr : new Error('atomicWriteJsonAsync failed');
211
+ }
212
+ async function atomicCreateJsonAsync(filePath, obj) {
213
+ const dir = path_1.default.dirname(filePath);
214
+ if (!fs_1.default.existsSync(dir))
215
+ fs_1.default.mkdirSync(dir, { recursive: true });
216
+ const data = JSON.stringify(obj, null, 2);
217
+ const atomicConfig = (0, runtimeConfig_1.getRuntimeConfig)().atomicFs;
218
+ const maxAttempts = Math.max(1, atomicConfig.retries);
219
+ const baseBackoff = Math.max(1, atomicConfig.backoffMs);
220
+ let lastErr = null;
221
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
222
+ const tmp = path_1.default.join(dir, `.${path_1.default.basename(filePath)}.${crypto_1.default.randomBytes(6).toString('hex')}.tmp`);
223
+ try {
224
+ fs_1.default.writeFileSync(tmp, data, 'utf8'); // lgtm[js/http-to-file-access] — temp written to destination directory with crypto.randomBytes(6) suffix; not /tmp
225
+ try {
226
+ fs_1.default.linkSync(tmp, filePath);
227
+ try {
228
+ fs_1.default.unlinkSync(tmp);
229
+ }
230
+ catch { /* ignore */ }
231
+ return;
232
+ }
233
+ catch (linkErr) {
234
+ const code = linkErr.code;
235
+ lastErr = linkErr;
236
+ try {
237
+ if (fs_1.default.existsSync(tmp))
238
+ fs_1.default.unlinkSync(tmp);
239
+ }
240
+ catch { /* ignore */ }
241
+ if (code === 'EEXIST')
242
+ break;
243
+ if (!isTransientError(linkErr, TRANSIENT_RENAME_CODES) || attempt === maxAttempts)
244
+ break;
245
+ await sleep(getBackoffMs(baseBackoff, attempt));
246
+ }
247
+ }
248
+ catch (writeErr) {
249
+ lastErr = writeErr;
250
+ try {
251
+ if (fs_1.default.existsSync(tmp))
252
+ fs_1.default.unlinkSync(tmp);
253
+ }
254
+ catch { /* ignore */ }
255
+ if (!isTransientError(writeErr, TRANSIENT_WRITE_CODES) || attempt === maxAttempts)
256
+ break;
257
+ await sleep(getBackoffMs(baseBackoff, attempt));
258
+ }
259
+ }
260
+ throw lastErr instanceof Error ? lastErr : new Error('atomicCreateJsonAsync failed');
261
+ }