@jagilber-org/index-server 1.22.1 → 1.26.1

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 (189) hide show
  1. package/CHANGELOG.md +87 -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 +11 -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/setup-wizard.mjs +781 -0
  183. package/server.json +1 -0
  184. package/dist/externalClientLib.d.ts +0 -1
  185. package/dist/externalClientLib.js +0 -2
  186. package/dist/portableClientWrapper.d.ts +0 -1
  187. package/dist/portableClientWrapper.js +0 -2
  188. package/dist/services/indexingService.d.ts +0 -1
  189. package/dist/services/indexingService.js +0 -2
@@ -50,6 +50,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
50
50
  exports.createMcpTransportRoutes = createMcpTransportRoutes;
51
51
  const express_1 = __importStar(require("express"));
52
52
  const registry_1 = require("../../server/registry");
53
+ const logger_1 = require("../../services/logger");
54
+ const auditLog_1 = require("../../services/auditLog");
53
55
  /**
54
56
  * Create an Express router for the MCP HTTP transport.
55
57
  */
@@ -105,6 +107,10 @@ function createMcpTransportRoutes(options = {}) {
105
107
  }
106
108
  catch (error) {
107
109
  const message = error instanceof Error ? error.message : 'Internal error';
110
+ const stack = error instanceof Error ? error.stack : undefined;
111
+ const errorType = error instanceof Error ? error.constructor.name : typeof error;
112
+ (0, logger_1.log)('ERROR', `[HttpTransport] RPC handler error for method '${method}': ${message}`, { detail: stack });
113
+ (0, auditLog_1.logAudit)('rpc_error', method, { error: message, errorType, stack: stack?.slice(0, 500), requestId: id ?? null }, 'http');
108
114
  res.status(500).json({
109
115
  jsonrpc: '2.0',
110
116
  error: { code: -32603, message },
@@ -212,8 +212,13 @@ function pingInstance(host, port, protocol = 'http', timeoutMs = 3000) {
212
212
  tlsOpts.ca = fs_1.default.readFileSync(caPath);
213
213
  }
214
214
  else {
215
- // Localhost health-check against self-signed certs when no CA configured
216
- tlsOpts.rejectUnauthorized = false; // lgtm[js/disabling-certificate-validation] localhost self-signed cert
215
+ // No trusted CA configured. Do NOT disable certificate verification
216
+ // in production codeinstead, skip the HTTPS reachability probe
217
+ // entirely. The earlier isProcessAlive() PID check still ensures the
218
+ // owning process exists; treat the instance as alive and defer
219
+ // liveness validation to the next iteration once a CA is configured.
220
+ resolve(true);
221
+ return;
217
222
  }
218
223
  }
219
224
  const opts = {
@@ -16,6 +16,7 @@ exports.createKnowledgeStore = createKnowledgeStore;
16
16
  const fs_1 = __importDefault(require("fs"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const runtimeConfig_1 = require("../../config/runtimeConfig");
19
+ const logger_js_1 = require("../../services/logger.js");
19
20
  class KnowledgeStore {
20
21
  entries = new Map();
21
22
  filePath;
@@ -36,7 +37,9 @@ class KnowledgeStore {
36
37
  }
37
38
  }
38
39
  }
39
- catch { /* ignore corrupt/missing file */ }
40
+ catch (error) {
41
+ (0, logger_js_1.logWarn)('[KnowledgeStore] Failed to load persisted knowledge store', error);
42
+ }
40
43
  }
41
44
  saveToDisk() {
42
45
  try {
@@ -45,7 +48,9 @@ class KnowledgeStore {
45
48
  fs_1.default.mkdirSync(dir, { recursive: true });
46
49
  fs_1.default.writeFileSync(this.filePath, JSON.stringify(Array.from(this.entries.values()), null, 2));
47
50
  }
48
- catch { /* ignore write errors */ }
51
+ catch (error) {
52
+ (0, logger_js_1.logWarn)('[KnowledgeStore] Failed to persist knowledge store', error);
53
+ }
49
54
  }
50
55
  upsert(key, content, metadata = {}) {
51
56
  const now = new Date().toISOString();
@@ -10,6 +10,7 @@ export type { ToolMetrics, ServerMetrics, ConnectionMetrics, MetricsSnapshot, Me
10
10
  import { type ToolMetrics, type MetricsSnapshot, type MetricsTimeSeriesEntry, type ToolCallEvent, type RealtimeMetrics, type RealtimeStreamingData, type SystemHealth, type EnhancedPerformanceMetrics, type AdvancedAnalytics, type Alert, type MetricsCollectorOptions, type ResourceSample } from './metricsAggregation.js';
11
11
  import type { ToolUsageChartData, PerformanceChartData, ToolUsageStats } from './metricsAggregation.js';
12
12
  export declare class MetricsCollector {
13
+ private persistenceHealth;
13
14
  private tools;
14
15
  private resourceSamples;
15
16
  private lastCpuUsageSample;
@@ -46,6 +47,19 @@ export declare class MetricsCollector {
46
47
  private appendFlushMs;
47
48
  private appendCompactMs;
48
49
  constructor(options?: MetricsCollectorOptions);
50
+ private formatPersistenceError;
51
+ private recordPersistenceFailure;
52
+ private recordPersistenceRecovery;
53
+ getPersistenceHealth(): {
54
+ degraded: boolean;
55
+ totalFailures: number;
56
+ appendFailures: number;
57
+ snapshotFailures: number;
58
+ truncateFailures: number;
59
+ lastError?: string;
60
+ lastFailureAt?: number;
61
+ lastRecoveredAt?: number;
62
+ };
49
63
  /**
50
64
  * Record a tool call event
51
65
  */
@@ -85,6 +99,7 @@ export declare class MetricsCollector {
85
99
  oldestTimestamp?: number;
86
100
  newestTimestamp?: number;
87
101
  memorySnapshots: number;
102
+ persistence: ReturnType<MetricsCollector['getPersistenceHealth']>;
88
103
  }>;
89
104
  /**
90
105
  * Get tool-specific metrics
@@ -176,6 +191,7 @@ export declare class MetricsCollector {
176
191
  toolCallEvents: BufferRingStats;
177
192
  performanceMetrics: BufferRingStats;
178
193
  };
194
+ persistence: ReturnType<MetricsCollector['getPersistenceHealth']>;
179
195
  historicalSnapshots?: MetricsTimeSeriesEntry[];
180
196
  toolCallEvents?: ToolCallEvent[];
181
197
  performanceMetrics?: Array<{
@@ -23,6 +23,16 @@ const logger_js_1 = require("../../services/logger.js");
23
23
  const metricsAggregation_js_1 = require("./metricsAggregation.js");
24
24
  const metricsSerializer_js_1 = require("./metricsSerializer.js");
25
25
  class MetricsCollector {
26
+ persistenceHealth = {
27
+ degraded: false,
28
+ totalFailures: 0,
29
+ appendFailures: 0,
30
+ snapshotFailures: 0,
31
+ truncateFailures: 0,
32
+ lastError: undefined,
33
+ lastFailureAt: undefined,
34
+ lastRecoveredAt: undefined,
35
+ };
26
36
  tools = new Map();
27
37
  // Resource usage samples (CPU/Memory) for leak/trend analysis
28
38
  resourceSamples;
@@ -139,7 +149,7 @@ class MetricsCollector {
139
149
  try {
140
150
  const stat = fs_1.default.statSync(this.appendLogPath);
141
151
  if (stat.size < 25 * 1024 * 1024) { // safety cap 25MB
142
- const raw = fs_1.default.readFileSync(this.appendLogPath, 'utf8');
152
+ const raw = fs_1.default.readFileSync(this.appendLogPath, 'utf8'); // lgtm[js/file-system-race] — appendLogPath is config-controlled metrics path; statSync above bounds size
143
153
  const lines = raw.split(/\r?\n/).filter(l => l.trim().length > 0);
144
154
  for (const line of lines) {
145
155
  try {
@@ -179,6 +189,49 @@ class MetricsCollector {
179
189
  // Start periodic collection
180
190
  this.startCollection();
181
191
  }
192
+ formatPersistenceError(error) {
193
+ if (error instanceof Error) {
194
+ return error.stack ?? error.message;
195
+ }
196
+ return String(error);
197
+ }
198
+ recordPersistenceFailure(operation, error) {
199
+ this.persistenceHealth.degraded = true;
200
+ this.persistenceHealth.totalFailures += 1;
201
+ this.persistenceHealth.lastError = this.formatPersistenceError(error);
202
+ this.persistenceHealth.lastFailureAt = Date.now();
203
+ if (operation === 'append') {
204
+ this.persistenceHealth.appendFailures += 1;
205
+ }
206
+ else if (operation === 'truncate') {
207
+ this.persistenceHealth.truncateFailures += 1;
208
+ }
209
+ else {
210
+ this.persistenceHealth.snapshotFailures += 1;
211
+ }
212
+ if (this.persistenceHealth.totalFailures === 1 || this.persistenceHealth.lastRecoveredAt !== undefined) {
213
+ (0, logger_js_1.logWarn)('[MetricsCollector] Metrics persistence degraded; in-memory buffers will retry writes', {
214
+ operation,
215
+ error: this.persistenceHealth.lastError,
216
+ totalFailures: this.persistenceHealth.totalFailures,
217
+ });
218
+ this.persistenceHealth.lastRecoveredAt = undefined;
219
+ }
220
+ }
221
+ recordPersistenceRecovery(operation) {
222
+ if (!this.persistenceHealth.degraded)
223
+ return;
224
+ this.persistenceHealth.degraded = false;
225
+ this.persistenceHealth.lastError = undefined;
226
+ this.persistenceHealth.lastRecoveredAt = Date.now();
227
+ (0, logger_js_1.logInfo)('[MetricsCollector] Metrics persistence recovered', {
228
+ operation,
229
+ totalFailures: this.persistenceHealth.totalFailures,
230
+ });
231
+ }
232
+ getPersistenceHealth() {
233
+ return { ...this.persistenceHealth };
234
+ }
182
235
  /**
183
236
  * Record a tool call event
184
237
  */
@@ -225,10 +278,18 @@ class MetricsCollector {
225
278
  this._pendingToolPersist++;
226
279
  const dueTime = now - this._lastToolPersist > this.appendFlushMs;
227
280
  if (this._pendingToolPersist >= this.appendChunkSize || dueTime) {
281
+ const pendingAtSchedule = this._pendingToolPersist;
228
282
  setTimeout(() => {
229
- this.toolCallEvents.saveToDisk().catch(() => { });
230
- this._lastToolPersist = Date.now();
231
- this._pendingToolPersist = 0;
283
+ this.toolCallEvents.saveToDisk()
284
+ .then(() => {
285
+ this._lastToolPersist = Date.now();
286
+ this._pendingToolPersist = Math.max(0, this._pendingToolPersist - pendingAtSchedule);
287
+ this.recordPersistenceRecovery('snapshot');
288
+ })
289
+ .catch((error) => {
290
+ this._pendingToolPersist = Math.max(this._pendingToolPersist, pendingAtSchedule);
291
+ this.recordPersistenceFailure('snapshot', error);
292
+ });
232
293
  }, 0).unref?.();
233
294
  }
234
295
  }
@@ -267,23 +328,54 @@ class MetricsCollector {
267
328
  return;
268
329
  const toWrite = this.pendingAppendEvents.splice(0, this.pendingAppendEvents.length);
269
330
  const lines = toWrite.map(e => JSON.stringify(e)).join('\n') + '\n';
270
- fs_1.default.promises.appendFile(this.appendLogPath, lines).catch(() => { });
271
- this.lastAppendFlush = now;
272
- // Periodic compaction: write full snapshot & truncate log
273
- if ((now - this.lastAppendCompact) >= this.appendCompactMs || force) {
274
- this.toolCallEvents.saveToDisk().catch(() => { });
275
- this.lastAppendCompact = now;
331
+ const shouldCompact = (now - this.lastAppendCompact) >= this.appendCompactMs || force;
332
+ void fs_1.default.promises.appendFile(this.appendLogPath, lines)
333
+ .then(async () => {
334
+ this.lastAppendFlush = now;
335
+ if (!shouldCompact) {
336
+ this.recordPersistenceRecovery('append');
337
+ return;
338
+ }
339
+ try {
340
+ await this.toolCallEvents.saveToDisk();
341
+ this.lastAppendCompact = now;
342
+ }
343
+ catch (error) {
344
+ this.recordPersistenceFailure('snapshot', error);
345
+ return;
346
+ }
276
347
  if (this.appendLogPath) {
277
- fs_1.default.promises.writeFile(this.appendLogPath, '').catch(() => { }); // truncate
348
+ try {
349
+ await fs_1.default.promises.writeFile(this.appendLogPath, '');
350
+ }
351
+ catch (error) {
352
+ this.recordPersistenceFailure('truncate', error);
353
+ return;
354
+ }
278
355
  }
279
- }
356
+ this.recordPersistenceRecovery('append');
357
+ })
358
+ .catch((error) => {
359
+ this.pendingAppendEvents.unshift(...toWrite);
360
+ this.lastAppendFlush = 0;
361
+ this.recordPersistenceFailure('append', error);
362
+ });
363
+ // Periodic compaction: write full snapshot & truncate log
280
364
  }
281
365
  else {
282
366
  // snapshot mode manual force
283
367
  if (force) {
284
- this.toolCallEvents.saveToDisk().catch(() => { });
285
- this._lastToolPersist = now;
286
- this._pendingToolPersist = 0;
368
+ const pendingAtForce = Math.max(this._pendingToolPersist, 1);
369
+ void this.toolCallEvents.saveToDisk()
370
+ .then(() => {
371
+ this._lastToolPersist = now;
372
+ this._pendingToolPersist = Math.max(0, this._pendingToolPersist - pendingAtForce);
373
+ this.recordPersistenceRecovery('snapshot');
374
+ })
375
+ .catch((error) => {
376
+ this._pendingToolPersist = Math.max(this._pendingToolPersist, pendingAtForce);
377
+ this.recordPersistenceFailure('snapshot', error);
378
+ });
287
379
  }
288
380
  }
289
381
  }
@@ -430,12 +522,14 @@ class MetricsCollector {
430
522
  fileCount: 0,
431
523
  totalSizeKB: 0,
432
524
  memorySnapshots: this.snapshots.length,
525
+ persistence: this.getPersistenceHealth(),
433
526
  };
434
527
  }
435
528
  const fileStats = await this.fileStorage.getStorageStats();
436
529
  return {
437
530
  ...fileStats,
438
531
  memorySnapshots: this.snapshots.length,
532
+ persistence: this.getPersistenceHealth(),
439
533
  };
440
534
  }
441
535
  /**
@@ -605,6 +699,7 @@ class MetricsCollector {
605
699
  getSystemHealth() {
606
700
  const cpu = (0, metricsAggregation_js_1.estimateCPUUsage)(this.snapshots.slice(-5));
607
701
  const memory = (0, metricsAggregation_js_1.estimateMemoryUsage)(this.connections.size, this.snapshots.length);
702
+ const baseStatus = (0, metricsAggregation_js_1.getOverallHealthStatus)(cpu, memory, (0, metricsAggregation_js_1.getErrorRate)(this.tools));
608
703
  return {
609
704
  cpuUsage: cpu,
610
705
  memoryUsage: memory,
@@ -612,7 +707,7 @@ class MetricsCollector {
612
707
  networkLatency: (0, metricsAggregation_js_1.getAverageResponseTime)(this.tools),
613
708
  uptime: Date.now() - this.startTime,
614
709
  lastHealthCheck: new Date(),
615
- status: (0, metricsAggregation_js_1.getOverallHealthStatus)(cpu, memory, (0, metricsAggregation_js_1.getErrorRate)(this.tools)),
710
+ status: this.persistenceHealth.degraded && baseStatus === 'healthy' ? 'warning' : baseStatus,
616
711
  };
617
712
  }
618
713
  getDetailedPerformanceMetrics() {
@@ -765,7 +860,8 @@ class MetricsCollector {
765
860
  const data = {
766
861
  timestamp: Date.now(),
767
862
  currentSnapshot: this.getCurrentSnapshot(),
768
- bufferStats: this.getBufferRingStats()
863
+ bufferStats: this.getBufferRingStats(),
864
+ persistence: this.getPersistenceHealth(),
769
865
  };
770
866
  const result = data;
771
867
  if (options.includeHistorical !== false) {
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- /* eslint-disable */
3
2
  /**
4
3
  * legacyDashboardHtml - generates the v1 legacy dashboard HTML page.
5
4
  * Extracted from DashboardServer.ts to keep the coordinator within line limits.
@@ -13,8 +12,14 @@ const legacyDashboardStyles_js_1 = require("./legacyDashboardStyles.js");
13
12
  // ---------------------------------------------------------------------------
14
13
  // stripGraphTab - removes graph-related markup when the graph feature is off
15
14
  // ---------------------------------------------------------------------------
15
+ // NOTE: These regexes operate on dashboard HTML built in this module from
16
+ // trusted constants — never on user input. The patterns are intentional
17
+ // targeted strips (matching specific data-section, comment markers, and the
18
+ // admin.graph.js script src) rather than a general HTML sanitizer.
19
+ // CodeQL bad-tag-filter / incomplete-multi-character-sanitization queries
20
+ // are suppressed for this file via .github/codeql/codeql-config.yml.
16
21
  function stripGraphTab(html) {
17
- html = html.replace(/<button[^>]*data-section="graph"[^>]*>Graph<\/button>\s*/i, ""); // lgtm[js/incomplete-multi-character-sanitization] — stripping graph tab, not sanitization
22
+ html = html.replace(/<button[^>]*data-section="graph"[^>]*>Graph<\/button>\s*/i, "");
18
23
  html = html.replace(/<!--\s*Graph Section\s*-->[\s\S]*?(?=<!--\s*Configuration Section\s*-->)/i, "");
19
24
  html = html.replace(/<script[^>]*src="js\/admin\.graph\.js[^"]*"[^>]*><\/script>\s*/i, "");
20
25
  return html;
@@ -22,4 +22,4 @@ export interface IndexLocals {
22
22
  * Middleware that eagerly loads the instruction index once per request.
23
23
  * Attach to any router whose handlers need index state.
24
24
  */
25
- export declare function ensureLoadedMiddleware(_req: Request, res: Response, next: NextFunction): void;
25
+ export declare function ensureLoadedMiddleware(_req: Request, res: Response, next: NextFunction): Promise<void>;
@@ -18,7 +18,12 @@ const indexContext_js_1 = require("../../../services/indexContext.js");
18
18
  * Middleware that eagerly loads the instruction index once per request.
19
19
  * Attach to any router whose handlers need index state.
20
20
  */
21
- function ensureLoadedMiddleware(_req, res, next) {
22
- res.locals.indexState = (0, indexContext_js_1.ensureLoaded)();
23
- next();
21
+ async function ensureLoadedMiddleware(_req, res, next) {
22
+ try {
23
+ res.locals.indexState = await (0, indexContext_js_1.ensureLoadedAsync)();
24
+ next();
25
+ }
26
+ catch (error) {
27
+ next(error);
28
+ }
24
29
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Admin Feedback CRUD Routes — Human-operator management of persisted feedback entries.
3
+ *
4
+ * Routes:
5
+ * GET /admin/feedback — list all entries
6
+ * POST /admin/feedback — create a new entry
7
+ * GET /admin/feedback/:id — get a single entry
8
+ * PATCH /admin/feedback/:id — update entry fields (e.g., status)
9
+ * DELETE /admin/feedback/:id — remove an entry
10
+ *
11
+ * Storage: shared via src/services/feedbackStorage.ts (no I/O duplication with MCP layer).
12
+ * This surface is NOT the webhook/external-connector surface in api.feedback.routes.ts.
13
+ */
14
+ import { Router } from 'express';
15
+ export declare function createAdminFeedbackRoutes(): Router;
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ /**
3
+ * Admin Feedback CRUD Routes — Human-operator management of persisted feedback entries.
4
+ *
5
+ * Routes:
6
+ * GET /admin/feedback — list all entries
7
+ * POST /admin/feedback — create a new entry
8
+ * GET /admin/feedback/:id — get a single entry
9
+ * PATCH /admin/feedback/:id — update entry fields (e.g., status)
10
+ * DELETE /admin/feedback/:id — remove an entry
11
+ *
12
+ * Storage: shared via src/services/feedbackStorage.ts (no I/O duplication with MCP layer).
13
+ * This surface is NOT the webhook/external-connector surface in api.feedback.routes.ts.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.createAdminFeedbackRoutes = createAdminFeedbackRoutes;
17
+ const express_1 = require("express");
18
+ const adminAuth_js_1 = require("./adminAuth.js");
19
+ const feedbackStorage_js_1 = require("../../../services/feedbackStorage.js");
20
+ const auditLog_js_1 = require("../../../services/auditLog.js");
21
+ const VALID_TYPES = new Set([
22
+ 'issue', 'status', 'security', 'feature-request',
23
+ 'bug-report', 'performance', 'usability', 'other',
24
+ ]);
25
+ const VALID_SEVERITIES = new Set(['low', 'medium', 'high', 'critical']);
26
+ const VALID_STATUSES = new Set(['new', 'acknowledged', 'in-progress', 'resolved', 'closed']);
27
+ const MAX_TITLE_LENGTH = 200;
28
+ const MAX_DESCRIPTION_LENGTH = 10_000;
29
+ const MAX_TAGS = 10;
30
+ function sanitizeTags(value) {
31
+ if (!Array.isArray(value))
32
+ return undefined;
33
+ const tags = value
34
+ .filter((tag) => typeof tag === 'string')
35
+ .map(tag => tag.trim())
36
+ .filter(Boolean)
37
+ .slice(0, MAX_TAGS);
38
+ return tags.length > 0 ? tags : undefined;
39
+ }
40
+ function createAdminFeedbackRoutes() {
41
+ const router = (0, express_1.Router)();
42
+ /** GET /admin/feedback — list all persisted feedback entries */
43
+ router.get('/admin/feedback', adminAuth_js_1.dashboardAdminAuth, (_req, res) => {
44
+ try {
45
+ const storage = (0, feedbackStorage_js_1.loadFeedbackStorage)();
46
+ res.json({
47
+ entries: storage.entries,
48
+ total: storage.entries.length,
49
+ lastUpdated: storage.lastUpdated,
50
+ });
51
+ }
52
+ catch (error) {
53
+ res.status(500).json({ error: 'Failed to load feedback entries', message: String(error) });
54
+ }
55
+ });
56
+ /** POST /admin/feedback — create a new feedback entry */
57
+ router.post('/admin/feedback', adminAuth_js_1.dashboardAdminAuth, (req, res) => {
58
+ try {
59
+ const body = req.body;
60
+ const { type, severity, title, description } = body;
61
+ if (!title || typeof title !== 'string' || !title.trim()) {
62
+ res.status(400).json({ error: 'Missing required field: title' });
63
+ return;
64
+ }
65
+ if (!type || !VALID_TYPES.has(String(type))) {
66
+ res.status(400).json({
67
+ error: `Missing or invalid field: type. Must be one of: ${[...VALID_TYPES].join(', ')}`,
68
+ });
69
+ return;
70
+ }
71
+ if (!severity || !VALID_SEVERITIES.has(String(severity))) {
72
+ res.status(400).json({
73
+ error: `Missing or invalid field: severity. Must be one of: ${[...VALID_SEVERITIES].join(', ')}`,
74
+ });
75
+ return;
76
+ }
77
+ const timestamp = new Date().toISOString();
78
+ const id = (0, feedbackStorage_js_1.generateFeedbackId)(String(type), timestamp);
79
+ const entry = {
80
+ id,
81
+ timestamp,
82
+ type: type,
83
+ severity: severity,
84
+ title: title.trim().slice(0, MAX_TITLE_LENGTH),
85
+ description: description ? String(description).slice(0, MAX_DESCRIPTION_LENGTH) : '',
86
+ status: 'new',
87
+ };
88
+ const tags = sanitizeTags(body.tags);
89
+ if (tags)
90
+ entry.tags = tags;
91
+ if (body.metadata && typeof body.metadata === 'object') {
92
+ entry.metadata = body.metadata;
93
+ }
94
+ const storage = (0, feedbackStorage_js_1.loadFeedbackStorage)();
95
+ storage.entries.push(entry);
96
+ (0, feedbackStorage_js_1.saveFeedbackStorage)(storage);
97
+ (0, auditLog_js_1.logAudit)('admin/feedback/create', [id], { title: entry.title, type: entry.type });
98
+ res.status(201).json(entry);
99
+ }
100
+ catch (error) {
101
+ res.status(500).json({ error: 'Failed to create feedback entry', message: String(error) });
102
+ }
103
+ });
104
+ /** GET /admin/feedback/:id — retrieve a single entry by id */
105
+ router.get('/admin/feedback/:id', adminAuth_js_1.dashboardAdminAuth, (req, res) => {
106
+ try {
107
+ const { id } = req.params;
108
+ const storage = (0, feedbackStorage_js_1.loadFeedbackStorage)();
109
+ const entry = storage.entries.find(e => e.id === id);
110
+ if (!entry) {
111
+ res.status(404).json({ error: `Feedback entry not found: ${id}` });
112
+ return;
113
+ }
114
+ res.json(entry);
115
+ }
116
+ catch (error) {
117
+ res.status(500).json({ error: 'Failed to get feedback entry', message: String(error) });
118
+ }
119
+ });
120
+ /** PATCH /admin/feedback/:id — update mutable fields on an entry */
121
+ router.patch('/admin/feedback/:id', adminAuth_js_1.dashboardAdminAuth, (req, res) => {
122
+ try {
123
+ const { id } = req.params;
124
+ const body = req.body;
125
+ const storage = (0, feedbackStorage_js_1.loadFeedbackStorage)();
126
+ const idx = storage.entries.findIndex(e => e.id === id);
127
+ if (idx === -1) {
128
+ res.status(404).json({ error: `Feedback entry not found: ${id}` });
129
+ return;
130
+ }
131
+ const entry = { ...storage.entries[idx] };
132
+ if (body.status !== undefined) {
133
+ if (!VALID_STATUSES.has(String(body.status))) {
134
+ res.status(400).json({
135
+ error: `Invalid status. Must be one of: ${[...VALID_STATUSES].join(', ')}`,
136
+ });
137
+ return;
138
+ }
139
+ entry.status = body.status;
140
+ }
141
+ if (body.title !== undefined && typeof body.title === 'string') {
142
+ entry.title = body.title.trim().slice(0, MAX_TITLE_LENGTH);
143
+ }
144
+ if (body.description !== undefined && typeof body.description === 'string') {
145
+ entry.description = body.description.slice(0, MAX_DESCRIPTION_LENGTH);
146
+ }
147
+ if (body.severity !== undefined) {
148
+ if (!VALID_SEVERITIES.has(String(body.severity))) {
149
+ res.status(400).json({
150
+ error: `Invalid severity. Must be one of: ${[...VALID_SEVERITIES].join(', ')}`,
151
+ });
152
+ return;
153
+ }
154
+ entry.severity = body.severity;
155
+ }
156
+ if (body.tags !== undefined) {
157
+ entry.tags = sanitizeTags(body.tags);
158
+ }
159
+ storage.entries[idx] = entry;
160
+ (0, feedbackStorage_js_1.saveFeedbackStorage)(storage);
161
+ (0, auditLog_js_1.logAudit)('admin/feedback/update', [id], { fields: Object.keys(body) });
162
+ res.json(entry);
163
+ }
164
+ catch (error) {
165
+ res.status(500).json({ error: 'Failed to update feedback entry', message: String(error) });
166
+ }
167
+ });
168
+ /** DELETE /admin/feedback/:id — remove an entry */
169
+ router.delete('/admin/feedback/:id', adminAuth_js_1.dashboardAdminAuth, (req, res) => {
170
+ try {
171
+ const { id } = req.params;
172
+ const storage = (0, feedbackStorage_js_1.loadFeedbackStorage)();
173
+ const idx = storage.entries.findIndex(e => e.id === id);
174
+ if (idx === -1) {
175
+ res.status(404).json({ error: `Feedback entry not found: ${id}` });
176
+ return;
177
+ }
178
+ storage.entries.splice(idx, 1);
179
+ (0, feedbackStorage_js_1.saveFeedbackStorage)(storage);
180
+ (0, auditLog_js_1.logAudit)('admin/feedback/delete', [id]);
181
+ res.status(200).json({ deleted: true, id });
182
+ }
183
+ catch (error) {
184
+ res.status(500).json({ error: 'Failed to delete feedback entry', message: String(error) });
185
+ }
186
+ });
187
+ return router;
188
+ }