@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.
- package/CHANGELOG.md +87 -2
- package/CODE_OF_CONDUCT.md +2 -0
- package/CONTRIBUTING.md +32 -2
- package/README.md +82 -19
- package/SECURITY.md +17 -5
- package/dist/config/dashboardConfig.d.ts +3 -0
- package/dist/config/dashboardConfig.js +3 -0
- package/dist/config/defaultValues.d.ts +1 -1
- package/dist/config/defaultValues.js +1 -1
- package/dist/config/featureConfig.d.ts +2 -0
- package/dist/config/featureConfig.js +6 -1
- package/dist/config/runtimeConfig.d.ts +1 -1
- package/dist/config/runtimeConfig.js +8 -9
- package/dist/dashboard/client/admin.html +170 -53
- package/dist/dashboard/client/css/admin.css +132 -0
- package/dist/dashboard/client/js/admin.auth.js +25 -11
- package/dist/dashboard/client/js/admin.config.js +1 -1
- package/dist/dashboard/client/js/admin.feedback.js +328 -0
- package/dist/dashboard/client/js/admin.graph.js +120 -18
- package/dist/dashboard/client/js/admin.instructions.js +27 -13
- package/dist/dashboard/client/js/admin.logs.js +1 -5
- package/dist/dashboard/client/js/admin.maintenance.js +53 -8
- package/dist/dashboard/client/js/admin.messaging.js +1 -4
- package/dist/dashboard/client/js/admin.overview.js +5 -1
- package/dist/dashboard/client/js/admin.sessions.js +1 -1
- package/dist/dashboard/client/js/admin.utils.js +43 -1
- package/dist/dashboard/client/js/mermaid.min.js +813 -537
- package/dist/dashboard/export/DataExporter.js +2 -1
- package/dist/dashboard/server/AdminPanel.d.ts +3 -0
- package/dist/dashboard/server/AdminPanel.js +132 -35
- package/dist/dashboard/server/ApiRoutes.js +40 -9
- package/dist/dashboard/server/DashboardServer.js +1 -1
- package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
- package/dist/dashboard/server/FileMetricsStorage.js +52 -5
- package/dist/dashboard/server/HttpTransport.js +6 -0
- package/dist/dashboard/server/InstanceManager.js +7 -2
- package/dist/dashboard/server/KnowledgeStore.js +7 -2
- package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
- package/dist/dashboard/server/MetricsCollector.js +113 -17
- package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
- package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
- package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
- package/dist/dashboard/server/routes/admin.routes.js +35 -27
- package/dist/dashboard/server/routes/alerts.routes.js +4 -3
- package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
- package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
- package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
- package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
- package/dist/dashboard/server/routes/graph.routes.js +10 -13
- package/dist/dashboard/server/routes/index.d.ts +1 -0
- package/dist/dashboard/server/routes/index.js +74 -39
- package/dist/dashboard/server/routes/instances.routes.js +2 -1
- package/dist/dashboard/server/routes/instructions.routes.js +46 -27
- package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
- package/dist/dashboard/server/routes/logs.routes.js +5 -4
- package/dist/dashboard/server/routes/messaging.routes.js +15 -14
- package/dist/dashboard/server/routes/metrics.routes.js +14 -13
- package/dist/dashboard/server/routes/scripts.routes.js +6 -3
- package/dist/dashboard/server/routes/status.routes.js +5 -4
- package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
- package/dist/dashboard/server/routes/usage.routes.js +2 -1
- package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
- package/dist/dashboard/server/utils/escapeHtml.js +11 -0
- package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
- package/dist/dashboard/server/utils/pathContainment.js +15 -0
- package/dist/dashboard/server/wsInit.js +2 -2
- package/dist/lib/mcpStdioLogging.d.ts +165 -0
- package/dist/lib/mcpStdioLogging.js +287 -0
- package/dist/schemas/index.d.ts +37 -2
- package/dist/schemas/index.js +27 -3
- package/dist/server/backgroundServicesStartup.d.ts +7 -1
- package/dist/server/backgroundServicesStartup.js +25 -8
- package/dist/server/certInit.d.ts +97 -0
- package/dist/server/certInit.js +359 -0
- package/dist/server/certInit.types.d.ts +92 -0
- package/dist/server/certInit.types.js +34 -0
- package/dist/server/handshake/fallbackFrames.d.ts +31 -0
- package/dist/server/handshake/fallbackFrames.js +38 -0
- package/dist/server/handshake/initializeDetector.d.ts +31 -0
- package/dist/server/handshake/initializeDetector.js +88 -0
- package/dist/server/handshake/protocol.d.ts +15 -0
- package/dist/server/handshake/protocol.js +37 -0
- package/dist/server/handshake/readyEmitter.d.ts +6 -0
- package/dist/server/handshake/readyEmitter.js +88 -0
- package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
- package/dist/server/handshake/safetyFallbacks.js +134 -0
- package/dist/server/handshake/stdinSniffer.d.ts +1 -0
- package/dist/server/handshake/stdinSniffer.js +260 -0
- package/dist/server/handshake/tracing.d.ts +16 -0
- package/dist/server/handshake/tracing.js +95 -0
- package/dist/server/handshakeManager.d.ts +23 -23
- package/dist/server/handshakeManager.js +36 -466
- package/dist/server/index-server.d.ts +23 -0
- package/dist/server/index-server.js +194 -9
- package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
- package/dist/server/mcpReadOnlySurfaces.js +297 -0
- package/dist/server/sdkServer.js +69 -7
- package/dist/server/transport.d.ts +5 -6
- package/dist/server/transport.js +46 -64
- package/dist/server/transportFactory.d.ts +3 -9
- package/dist/server/transportFactory.js +18 -380
- package/dist/services/atomicFs.d.ts +3 -0
- package/dist/services/atomicFs.js +171 -13
- package/dist/services/auditLog.d.ts +17 -2
- package/dist/services/auditLog.js +75 -14
- package/dist/services/bootstrapGating.js +1 -1
- package/dist/services/categoryRules.d.ts +10 -0
- package/dist/services/categoryRules.js +17 -0
- package/dist/services/classificationService.js +7 -5
- package/dist/services/embeddingService.d.ts +27 -11
- package/dist/services/embeddingService.js +51 -14
- package/dist/services/feedbackStorage.d.ts +39 -0
- package/dist/services/feedbackStorage.js +88 -0
- package/dist/services/handlers/instructions.add.js +429 -317
- package/dist/services/handlers/instructions.groom.js +128 -31
- package/dist/services/handlers/instructions.import.js +56 -23
- package/dist/services/handlers/instructions.patch.js +43 -32
- package/dist/services/handlers/instructions.query.js +20 -29
- package/dist/services/handlers/instructions.shared.d.ts +54 -0
- package/dist/services/handlers/instructions.shared.js +126 -1
- package/dist/services/handlers.activation.js +83 -81
- package/dist/services/handlers.dashboardConfig.d.ts +2 -2
- package/dist/services/handlers.dashboardConfig.js +1 -2
- package/dist/services/handlers.diagnostics.js +75 -54
- package/dist/services/handlers.feedback.d.ts +4 -11
- package/dist/services/handlers.feedback.js +11 -333
- package/dist/services/handlers.gates.js +69 -37
- package/dist/services/handlers.graph.js +2 -2
- package/dist/services/handlers.help.js +2 -2
- package/dist/services/handlers.instructionSchema.js +4 -2
- package/dist/services/handlers.integrity.js +42 -22
- package/dist/services/handlers.messaging.js +1 -1
- package/dist/services/handlers.metrics.js +51 -6
- package/dist/services/handlers.prompt.js +10 -2
- package/dist/services/handlers.search.js +94 -44
- package/dist/services/handlers.trace.js +1 -1
- package/dist/services/handlers.usage.js +38 -7
- package/dist/services/indexContext.d.ts +21 -1
- package/dist/services/indexContext.js +263 -78
- package/dist/services/indexLoader.d.ts +1 -0
- package/dist/services/indexLoader.js +28 -8
- package/dist/services/instructionRecordValidation.d.ts +39 -0
- package/dist/services/instructionRecordValidation.js +388 -0
- package/dist/services/instructions.dispatcher.js +4 -4
- package/dist/services/loaderSchemaValidator.d.ts +15 -0
- package/dist/services/loaderSchemaValidator.js +69 -0
- package/dist/services/logger.js +11 -2
- package/dist/services/mcpLogBridge.d.ts +49 -0
- package/dist/services/mcpLogBridge.js +83 -0
- package/dist/services/ownershipService.js +18 -8
- package/dist/services/performanceBaseline.js +23 -22
- package/dist/services/promptReviewService.d.ts +3 -1
- package/dist/services/promptReviewService.js +41 -13
- package/dist/services/regexSafety.d.ts +6 -0
- package/dist/services/regexSafety.js +46 -0
- package/dist/services/seedBootstrap.js +1 -1
- package/dist/services/storage/factory.d.ts +14 -1
- package/dist/services/storage/factory.js +61 -1
- package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
- package/dist/services/storage/jsonEmbeddingStore.js +83 -0
- package/dist/services/storage/jsonFileStore.d.ts +3 -1
- package/dist/services/storage/jsonFileStore.js +8 -6
- package/dist/services/storage/migrationEngine.d.ts +13 -0
- package/dist/services/storage/migrationEngine.js +31 -0
- package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
- package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
- package/dist/services/storage/sqliteStore.d.ts +3 -1
- package/dist/services/storage/sqliteStore.js +2 -2
- package/dist/services/storage/types.d.ts +48 -1
- package/dist/services/toolRegistry.js +77 -67
- package/dist/services/toolRegistry.zod.js +89 -86
- package/dist/services/tracing.js +5 -4
- package/dist/utils/envUtils.d.ts +4 -0
- package/dist/utils/envUtils.js +7 -0
- package/dist/utils/memoryMonitor.js +11 -10
- package/package.json +11 -4
- package/schemas/instruction.schema.json +38 -1
- package/scripts/copy-dashboard-assets.mjs +1 -1
- package/scripts/dist/README.md +1 -1
- package/scripts/setup-wizard.mjs +781 -0
- package/server.json +1 -0
- package/dist/externalClientLib.d.ts +0 -1
- package/dist/externalClientLib.js +0 -2
- package/dist/portableClientWrapper.d.ts +0 -1
- package/dist/portableClientWrapper.js +0 -2
- package/dist/services/indexingService.d.ts +0 -1
- 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
|
-
//
|
|
216
|
-
|
|
215
|
+
// No trusted CA configured. Do NOT disable certificate verification
|
|
216
|
+
// in production code — instead, 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 {
|
|
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 {
|
|
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()
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
271
|
-
this.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
285
|
-
this.
|
|
286
|
-
|
|
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:
|
|
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, "");
|
|
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
|
-
|
|
23
|
-
|
|
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
|
+
}
|