@jagilber-org/index-server 1.19.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 +1218 -0
- package/CODE_OF_CONDUCT.md +49 -0
- package/CONTRIBUTING.md +75 -0
- package/LICENSE +21 -0
- package/README.md +523 -0
- package/SECURITY.md +50 -0
- package/dist/config/configUtils.d.ts +11 -0
- package/dist/config/configUtils.js +87 -0
- package/dist/config/dashboardConfig.d.ts +45 -0
- package/dist/config/dashboardConfig.js +63 -0
- package/dist/config/defaultValues.d.ts +61 -0
- package/dist/config/defaultValues.js +70 -0
- package/dist/config/dirConstants.d.ts +17 -0
- package/dist/config/dirConstants.js +28 -0
- package/dist/config/featureConfig.d.ts +61 -0
- package/dist/config/featureConfig.js +121 -0
- package/dist/config/runtimeConfig.d.ts +145 -0
- package/dist/config/runtimeConfig.js +334 -0
- package/dist/config/serverConfig.d.ts +90 -0
- package/dist/config/serverConfig.js +164 -0
- package/dist/dashboard/analytics/AnalyticsEngine.d.ts +142 -0
- package/dist/dashboard/analytics/AnalyticsEngine.js +373 -0
- package/dist/dashboard/analytics/BusinessIntelligence.d.ts +187 -0
- package/dist/dashboard/analytics/BusinessIntelligence.js +594 -0
- package/dist/dashboard/client/admin.html +2150 -0
- package/dist/dashboard/client/chunks/mermaid-layout-elk.esm.min/chunk-SP2CHFBE.mjs +1 -0
- package/dist/dashboard/client/chunks/mermaid-layout-elk.esm.min/render-T6MDALS3.mjs +27 -0
- package/dist/dashboard/client/css/admin.css +1466 -0
- package/dist/dashboard/client/js/admin.boot.js +359 -0
- package/dist/dashboard/client/js/admin.config.js +196 -0
- package/dist/dashboard/client/js/admin.embeddings.js +425 -0
- package/dist/dashboard/client/js/admin.graph.js +583 -0
- package/dist/dashboard/client/js/admin.instances.js +120 -0
- package/dist/dashboard/client/js/admin.instructions.js +552 -0
- package/dist/dashboard/client/js/admin.logs.js +113 -0
- package/dist/dashboard/client/js/admin.maintenance.js +354 -0
- package/dist/dashboard/client/js/admin.messaging.js +635 -0
- package/dist/dashboard/client/js/admin.monitor.js +181 -0
- package/dist/dashboard/client/js/admin.overview.js +221 -0
- package/dist/dashboard/client/js/admin.performance.js +61 -0
- package/dist/dashboard/client/js/admin.sessions.js +293 -0
- package/dist/dashboard/client/js/admin.sqlite.js +366 -0
- package/dist/dashboard/client/js/admin.utils.js +49 -0
- package/dist/dashboard/client/js/chart.umd.js +14 -0
- package/dist/dashboard/client/js/elk.bundled.js +6696 -0
- package/dist/dashboard/client/js/marked.umd.js +74 -0
- package/dist/dashboard/client/js/mermaid.min.js +3022 -0
- package/dist/dashboard/client/mermaid-layout-elk.esm.min.mjs +1 -0
- package/dist/dashboard/export/DataExporter.d.ts +169 -0
- package/dist/dashboard/export/DataExporter.js +737 -0
- package/dist/dashboard/export/exporters/csvExporter.d.ts +11 -0
- package/dist/dashboard/export/exporters/csvExporter.js +46 -0
- package/dist/dashboard/export/exporters/exportTypes.d.ts +89 -0
- package/dist/dashboard/export/exporters/exportTypes.js +5 -0
- package/dist/dashboard/export/exporters/jsonExporter.d.ts +7 -0
- package/dist/dashboard/export/exporters/jsonExporter.js +22 -0
- package/dist/dashboard/export/exporters/xmlExporter.d.ts +17 -0
- package/dist/dashboard/export/exporters/xmlExporter.js +175 -0
- package/dist/dashboard/integration/APIIntegration.d.ts +41 -0
- package/dist/dashboard/integration/APIIntegration.js +95 -0
- package/dist/dashboard/security/SecurityMonitor.d.ts +167 -0
- package/dist/dashboard/security/SecurityMonitor.js +559 -0
- package/dist/dashboard/server/AdminPanel.d.ts +183 -0
- package/dist/dashboard/server/AdminPanel.js +792 -0
- package/dist/dashboard/server/AdminPanelConfig.d.ts +42 -0
- package/dist/dashboard/server/AdminPanelConfig.js +80 -0
- package/dist/dashboard/server/AdminPanelState.d.ts +47 -0
- package/dist/dashboard/server/AdminPanelState.js +214 -0
- package/dist/dashboard/server/ApiRoutes.d.ts +17 -0
- package/dist/dashboard/server/ApiRoutes.js +149 -0
- package/dist/dashboard/server/DashboardServer.d.ts +49 -0
- package/dist/dashboard/server/DashboardServer.js +159 -0
- package/dist/dashboard/server/FileMetricsStorage.d.ts +49 -0
- package/dist/dashboard/server/FileMetricsStorage.js +195 -0
- package/dist/dashboard/server/HttpTransport.d.ts +23 -0
- package/dist/dashboard/server/HttpTransport.js +116 -0
- package/dist/dashboard/server/InstanceManager.d.ts +53 -0
- package/dist/dashboard/server/InstanceManager.js +284 -0
- package/dist/dashboard/server/KnowledgeStore.d.ts +35 -0
- package/dist/dashboard/server/KnowledgeStore.js +105 -0
- package/dist/dashboard/server/LeaderElection.d.ts +81 -0
- package/dist/dashboard/server/LeaderElection.js +268 -0
- package/dist/dashboard/server/MetricsCollector.d.ts +200 -0
- package/dist/dashboard/server/MetricsCollector.js +803 -0
- package/dist/dashboard/server/SessionPersistenceManager.d.ts +88 -0
- package/dist/dashboard/server/SessionPersistenceManager.js +457 -0
- package/dist/dashboard/server/ThinClient.d.ts +64 -0
- package/dist/dashboard/server/ThinClient.js +237 -0
- package/dist/dashboard/server/WebSocketManager.d.ts +161 -0
- package/dist/dashboard/server/WebSocketManager.js +463 -0
- package/dist/dashboard/server/httpLifecycle.d.ts +17 -0
- package/dist/dashboard/server/httpLifecycle.js +35 -0
- package/dist/dashboard/server/legacyDashboardHtml.d.ts +9 -0
- package/dist/dashboard/server/legacyDashboardHtml.js +618 -0
- package/dist/dashboard/server/legacyDashboardStyles.d.ts +5 -0
- package/dist/dashboard/server/legacyDashboardStyles.js +490 -0
- package/dist/dashboard/server/metricsAggregation.d.ts +252 -0
- package/dist/dashboard/server/metricsAggregation.js +206 -0
- package/dist/dashboard/server/metricsSerializer.d.ts +25 -0
- package/dist/dashboard/server/metricsSerializer.js +195 -0
- package/dist/dashboard/server/routes/admin.routes.d.ts +16 -0
- package/dist/dashboard/server/routes/admin.routes.js +596 -0
- package/dist/dashboard/server/routes/alerts.routes.d.ts +7 -0
- package/dist/dashboard/server/routes/alerts.routes.js +93 -0
- package/dist/dashboard/server/routes/api.feedback.routes.d.ts +73 -0
- package/dist/dashboard/server/routes/api.feedback.routes.js +171 -0
- package/dist/dashboard/server/routes/api.instructions.routes.d.ts +101 -0
- package/dist/dashboard/server/routes/api.instructions.routes.js +213 -0
- package/dist/dashboard/server/routes/api.usage.routes.d.ts +57 -0
- package/dist/dashboard/server/routes/api.usage.routes.js +374 -0
- package/dist/dashboard/server/routes/embeddings.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/embeddings.routes.js +246 -0
- package/dist/dashboard/server/routes/graph.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/graph.routes.js +280 -0
- package/dist/dashboard/server/routes/index.d.ts +38 -0
- package/dist/dashboard/server/routes/index.js +194 -0
- package/dist/dashboard/server/routes/instances.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/instances.routes.js +35 -0
- package/dist/dashboard/server/routes/instructions.routes.d.ts +8 -0
- package/dist/dashboard/server/routes/instructions.routes.js +336 -0
- package/dist/dashboard/server/routes/knowledge.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/knowledge.routes.js +82 -0
- package/dist/dashboard/server/routes/logs.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/logs.routes.js +164 -0
- package/dist/dashboard/server/routes/messaging.routes.d.ts +16 -0
- package/dist/dashboard/server/routes/messaging.routes.js +293 -0
- package/dist/dashboard/server/routes/metrics.routes.d.ts +10 -0
- package/dist/dashboard/server/routes/metrics.routes.js +346 -0
- package/dist/dashboard/server/routes/scripts.routes.d.ts +9 -0
- package/dist/dashboard/server/routes/scripts.routes.js +84 -0
- package/dist/dashboard/server/routes/sqlite.routes.d.ts +9 -0
- package/dist/dashboard/server/routes/sqlite.routes.js +569 -0
- package/dist/dashboard/server/routes/status.routes.d.ts +7 -0
- package/dist/dashboard/server/routes/status.routes.js +183 -0
- package/dist/dashboard/server/routes/synthetic.routes.d.ts +7 -0
- package/dist/dashboard/server/routes/synthetic.routes.js +195 -0
- package/dist/dashboard/server/routes/tools.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/tools.routes.js +46 -0
- package/dist/dashboard/server/routes/usage.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/usage.routes.js +25 -0
- package/dist/dashboard/server/wsInit.d.ts +16 -0
- package/dist/dashboard/server/wsInit.js +35 -0
- package/dist/externalClientLib.d.ts +1 -0
- package/dist/externalClientLib.js +2 -0
- package/dist/minimal/index.d.ts +1 -0
- package/dist/minimal/index.js +140 -0
- package/dist/models/SessionPersistence.d.ts +115 -0
- package/dist/models/SessionPersistence.js +66 -0
- package/dist/models/instruction.d.ts +45 -0
- package/dist/models/instruction.js +2 -0
- package/dist/perf/benchmark.d.ts +1 -0
- package/dist/perf/benchmark.js +50 -0
- package/dist/portableClientWrapper.d.ts +1 -0
- package/dist/portableClientWrapper.js +2 -0
- package/dist/schemas/index.d.ts +128 -0
- package/dist/schemas/index.js +371 -0
- package/dist/scripts/runPerformanceBaseline.d.ts +1 -0
- package/dist/scripts/runPerformanceBaseline.js +17 -0
- package/dist/server/handshakeManager.d.ts +25 -0
- package/dist/server/handshakeManager.js +472 -0
- package/dist/server/index-server.d.ts +56 -0
- package/dist/server/index-server.js +822 -0
- package/dist/server/registry.d.ts +44 -0
- package/dist/server/registry.js +236 -0
- package/dist/server/sdkServer.d.ts +8 -0
- package/dist/server/sdkServer.js +299 -0
- package/dist/server/shutdownGuard.d.ts +41 -0
- package/dist/server/shutdownGuard.js +52 -0
- package/dist/server/thin-client.d.ts +22 -0
- package/dist/server/thin-client.js +111 -0
- package/dist/server/transport.d.ts +41 -0
- package/dist/server/transport.js +312 -0
- package/dist/server/transportFactory.d.ts +21 -0
- package/dist/server/transportFactory.js +429 -0
- package/dist/services/atomicFs.d.ts +22 -0
- package/dist/services/atomicFs.js +103 -0
- package/dist/services/auditLog.d.ts +38 -0
- package/dist/services/auditLog.js +142 -0
- package/dist/services/autoBackup.d.ts +14 -0
- package/dist/services/autoBackup.js +171 -0
- package/dist/services/autoSplit.d.ts +32 -0
- package/dist/services/autoSplit.js +113 -0
- package/dist/services/backupZip.d.ts +25 -0
- package/dist/services/backupZip.js +110 -0
- package/dist/services/bootstrapGating.d.ts +123 -0
- package/dist/services/bootstrapGating.js +221 -0
- package/dist/services/canonical.d.ts +23 -0
- package/dist/services/canonical.js +65 -0
- package/dist/services/categoryRules.d.ts +7 -0
- package/dist/services/categoryRules.js +37 -0
- package/dist/services/classificationService.d.ts +42 -0
- package/dist/services/classificationService.js +168 -0
- package/dist/services/embeddingService.d.ts +62 -0
- package/dist/services/embeddingService.js +259 -0
- package/dist/services/errors.d.ts +22 -0
- package/dist/services/errors.js +31 -0
- package/dist/services/featureFlags.d.ts +25 -0
- package/dist/services/featureFlags.js +89 -0
- package/dist/services/features.d.ts +13 -0
- package/dist/services/features.js +35 -0
- package/dist/services/handlers/instructions.add.d.ts +1 -0
- package/dist/services/handlers/instructions.add.js +496 -0
- package/dist/services/handlers/instructions.groom.d.ts +1 -0
- package/dist/services/handlers/instructions.groom.js +523 -0
- package/dist/services/handlers/instructions.import.d.ts +1 -0
- package/dist/services/handlers/instructions.import.js +173 -0
- package/dist/services/handlers/instructions.patch.d.ts +1 -0
- package/dist/services/handlers/instructions.patch.js +167 -0
- package/dist/services/handlers/instructions.query.d.ts +163 -0
- package/dist/services/handlers/instructions.query.js +522 -0
- package/dist/services/handlers/instructions.reload.d.ts +1 -0
- package/dist/services/handlers/instructions.reload.js +13 -0
- package/dist/services/handlers/instructions.remove.d.ts +1 -0
- package/dist/services/handlers/instructions.remove.js +118 -0
- package/dist/services/handlers/instructions.shared.d.ts +31 -0
- package/dist/services/handlers/instructions.shared.js +124 -0
- package/dist/services/handlers.activation.d.ts +1 -0
- package/dist/services/handlers.activation.js +203 -0
- package/dist/services/handlers.bootstrap.d.ts +1 -0
- package/dist/services/handlers.bootstrap.js +38 -0
- package/dist/services/handlers.dashboardConfig.d.ts +34 -0
- package/dist/services/handlers.dashboardConfig.js +108 -0
- package/dist/services/handlers.diagnostics.d.ts +1 -0
- package/dist/services/handlers.diagnostics.js +64 -0
- package/dist/services/handlers.feedback.d.ts +15 -0
- package/dist/services/handlers.feedback.js +378 -0
- package/dist/services/handlers.gates.d.ts +1 -0
- package/dist/services/handlers.gates.js +46 -0
- package/dist/services/handlers.graph.d.ts +53 -0
- package/dist/services/handlers.graph.js +231 -0
- package/dist/services/handlers.help.d.ts +1 -0
- package/dist/services/handlers.help.js +119 -0
- package/dist/services/handlers.instructionSchema.d.ts +1 -0
- package/dist/services/handlers.instructionSchema.js +227 -0
- package/dist/services/handlers.instructions.d.ts +8 -0
- package/dist/services/handlers.instructions.js +14 -0
- package/dist/services/handlers.instructionsDiagnostics.d.ts +1 -0
- package/dist/services/handlers.instructionsDiagnostics.js +14 -0
- package/dist/services/handlers.integrity.d.ts +1 -0
- package/dist/services/handlers.integrity.js +35 -0
- package/dist/services/handlers.manifest.d.ts +1 -0
- package/dist/services/handlers.manifest.js +24 -0
- package/dist/services/handlers.messaging.d.ts +12 -0
- package/dist/services/handlers.messaging.js +203 -0
- package/dist/services/handlers.metrics.d.ts +1 -0
- package/dist/services/handlers.metrics.js +43 -0
- package/dist/services/handlers.promote.d.ts +1 -0
- package/dist/services/handlers.promote.js +306 -0
- package/dist/services/handlers.prompt.d.ts +1 -0
- package/dist/services/handlers.prompt.js +7 -0
- package/dist/services/handlers.search.d.ts +69 -0
- package/dist/services/handlers.search.js +645 -0
- package/dist/services/handlers.testPrimitive.d.ts +1 -0
- package/dist/services/handlers.testPrimitive.js +5 -0
- package/dist/services/handlers.trace.d.ts +1 -0
- package/dist/services/handlers.trace.js +31 -0
- package/dist/services/handlers.usage.d.ts +1 -0
- package/dist/services/handlers.usage.js +11 -0
- package/dist/services/hotScore.d.ts +137 -0
- package/dist/services/hotScore.js +244 -0
- package/dist/services/indexContext.d.ts +117 -0
- package/dist/services/indexContext.js +968 -0
- package/dist/services/indexLoader.d.ts +44 -0
- package/dist/services/indexLoader.js +921 -0
- package/dist/services/indexRepository.d.ts +32 -0
- package/dist/services/indexRepository.js +71 -0
- package/dist/services/indexingService.d.ts +1 -0
- package/dist/services/indexingService.js +2 -0
- package/dist/services/instructions.dispatcher.d.ts +1 -0
- package/dist/services/instructions.dispatcher.js +231 -0
- package/dist/services/logPrefix.d.ts +1 -0
- package/dist/services/logPrefix.js +30 -0
- package/dist/services/logger.d.ts +52 -0
- package/dist/services/logger.js +268 -0
- package/dist/services/manifestManager.d.ts +82 -0
- package/dist/services/manifestManager.js +200 -0
- package/dist/services/messaging/agentMailbox.d.ts +60 -0
- package/dist/services/messaging/agentMailbox.js +353 -0
- package/dist/services/messaging/messagingPersistence.d.ts +20 -0
- package/dist/services/messaging/messagingPersistence.js +111 -0
- package/dist/services/messaging/messagingTypes.d.ts +150 -0
- package/dist/services/messaging/messagingTypes.js +66 -0
- package/dist/services/ownershipService.d.ts +1 -0
- package/dist/services/ownershipService.js +38 -0
- package/dist/services/performanceBaseline.d.ts +19 -0
- package/dist/services/performanceBaseline.js +210 -0
- package/dist/services/preflight.d.ts +12 -0
- package/dist/services/preflight.js +79 -0
- package/dist/services/promptReviewService.d.ts +44 -0
- package/dist/services/promptReviewService.js +101 -0
- package/dist/services/responseEnvelope.d.ts +6 -0
- package/dist/services/responseEnvelope.js +25 -0
- package/dist/services/seedBootstrap.d.ts +34 -0
- package/dist/services/seedBootstrap.js +427 -0
- package/dist/services/storage/factory.d.ts +17 -0
- package/dist/services/storage/factory.js +35 -0
- package/dist/services/storage/hashUtils.d.ts +11 -0
- package/dist/services/storage/hashUtils.js +35 -0
- package/dist/services/storage/index.d.ts +12 -0
- package/dist/services/storage/index.js +18 -0
- package/dist/services/storage/jsonFileStore.d.ts +32 -0
- package/dist/services/storage/jsonFileStore.js +241 -0
- package/dist/services/storage/migrationEngine.d.ts +35 -0
- package/dist/services/storage/migrationEngine.js +93 -0
- package/dist/services/storage/sqliteMessageStore.d.ts +53 -0
- package/dist/services/storage/sqliteMessageStore.js +146 -0
- package/dist/services/storage/sqliteSchema.d.ts +12 -0
- package/dist/services/storage/sqliteSchema.js +122 -0
- package/dist/services/storage/sqliteStore.d.ts +41 -0
- package/dist/services/storage/sqliteStore.js +339 -0
- package/dist/services/storage/sqliteUsageStore.d.ts +35 -0
- package/dist/services/storage/sqliteUsageStore.js +94 -0
- package/dist/services/storage/types.d.ts +171 -0
- package/dist/services/storage/types.js +12 -0
- package/dist/services/toolHandlers.d.ts +23 -0
- package/dist/services/toolHandlers.js +50 -0
- package/dist/services/toolRegistry.d.ts +20 -0
- package/dist/services/toolRegistry.js +490 -0
- package/dist/services/toolRegistry.zod.d.ts +10 -0
- package/dist/services/toolRegistry.zod.js +323 -0
- package/dist/services/tracing.d.ts +26 -0
- package/dist/services/tracing.js +260 -0
- package/dist/services/usageBuckets.d.ts +161 -0
- package/dist/services/usageBuckets.js +364 -0
- package/dist/services/validationService.d.ts +38 -0
- package/dist/services/validationService.js +125 -0
- package/dist/utils/BufferRing.d.ts +203 -0
- package/dist/utils/BufferRing.js +551 -0
- package/dist/utils/BufferRingExamples.d.ts +55 -0
- package/dist/utils/BufferRingExamples.js +188 -0
- package/dist/utils/envUtils.d.ts +42 -0
- package/dist/utils/envUtils.js +80 -0
- package/dist/utils/memoryMonitor.d.ts +83 -0
- package/dist/utils/memoryMonitor.js +275 -0
- package/dist/versioning/schemaVersion.d.ts +6 -0
- package/dist/versioning/schemaVersion.js +93 -0
- package/package.json +134 -0
- package/schemas/README.md +13 -0
- package/schemas/feedback-entry.schema.json +27 -0
- package/schemas/graph-export-v2.schema.json +60 -0
- package/schemas/index-server.code-schema.json +38477 -0
- package/schemas/instruction.schema.json +262 -0
- package/schemas/json-schema/SessionPersistence-persisted-admin-session.schema.json +54 -0
- package/schemas/json-schema/SessionPersistence-persisted-session-history-entry.schema.json +51 -0
- package/schemas/json-schema/SessionPersistence-persisted-web-socket-connection.schema.json +54 -0
- package/schemas/json-schema/SessionPersistence-session-persistence-config.schema.json +110 -0
- package/schemas/json-schema/SessionPersistence-session-persistence-data.schema.json +229 -0
- package/schemas/json-schema/SessionPersistence-session-persistence-manifest.schema.json +109 -0
- package/schemas/json-schema/SessionPersistence-session-persistence-metadata.schema.json +55 -0
- package/schemas/json-schema/instruction-audience-scope.schema.json +14 -0
- package/schemas/json-schema/instruction-content-type.schema.json +17 -0
- package/schemas/json-schema/instruction-instruction-entry.schema.json +206 -0
- package/schemas/json-schema/instruction-requirement-level.schema.json +16 -0
- package/schemas/manifest.json +78 -0
- package/schemas/manifest.schema.json +33 -0
- package/schemas/usage-batch.schema.json +16 -0
- package/schemas/usage-buckets.schema.json +30 -0
- package/schemas/usage-event.schema.json +17 -0
- package/scripts/copy-dashboard-assets.mjs +170 -0
- package/scripts/setup-hooks.cjs +28 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionPersistenceManager
|
|
3
|
+
*
|
|
4
|
+
* Manages persistent storage of admin sessions, websocket connections, and session history
|
|
5
|
+
* with deduplication, atomic writes, and backup integration.
|
|
6
|
+
*/
|
|
7
|
+
import { SessionPersistenceConfig, SessionPersistenceData } from '../../models/SessionPersistence.js';
|
|
8
|
+
export declare class SessionPersistenceManager {
|
|
9
|
+
private static persistenceQueues;
|
|
10
|
+
private config;
|
|
11
|
+
private persistenceTimer;
|
|
12
|
+
private lastPersistedData;
|
|
13
|
+
private isShuttingDown;
|
|
14
|
+
constructor(config?: Partial<SessionPersistenceConfig>);
|
|
15
|
+
/**
|
|
16
|
+
* Load configuration from the centralized dashboard config module
|
|
17
|
+
*/
|
|
18
|
+
private loadConfigFromEnvironment;
|
|
19
|
+
/**
|
|
20
|
+
* Setup periodic persistence timer
|
|
21
|
+
*/
|
|
22
|
+
private setupPeriodicPersistence;
|
|
23
|
+
/**
|
|
24
|
+
* Setup graceful shutdown handler
|
|
25
|
+
*/
|
|
26
|
+
private setupShutdownHandler;
|
|
27
|
+
/**
|
|
28
|
+
* Calculate checksum for deduplication
|
|
29
|
+
*/
|
|
30
|
+
private calculateChecksum;
|
|
31
|
+
/**
|
|
32
|
+
* Check if data has changed since last persistence
|
|
33
|
+
*/
|
|
34
|
+
private hasDataChanged;
|
|
35
|
+
/**
|
|
36
|
+
* Ensure persistence directories exist
|
|
37
|
+
*/
|
|
38
|
+
private ensureDirectories;
|
|
39
|
+
/**
|
|
40
|
+
* Atomic file write with backup
|
|
41
|
+
*/
|
|
42
|
+
private atomicWrite;
|
|
43
|
+
private getQueueKey;
|
|
44
|
+
private withPersistenceLock;
|
|
45
|
+
private defaultManifest;
|
|
46
|
+
private createDefaultMetadata;
|
|
47
|
+
private archiveCorruptFile;
|
|
48
|
+
private parseJsonFileOrFallback;
|
|
49
|
+
/**
|
|
50
|
+
* Apply retention policies to data
|
|
51
|
+
*/
|
|
52
|
+
private applyRetentionPolicies;
|
|
53
|
+
/**
|
|
54
|
+
* Create backup of current persistence data
|
|
55
|
+
*/
|
|
56
|
+
private createBackup;
|
|
57
|
+
/**
|
|
58
|
+
* Persist session data to disk
|
|
59
|
+
*/
|
|
60
|
+
persistData(data: SessionPersistenceData): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Load persisted session data from disk
|
|
63
|
+
*/
|
|
64
|
+
loadData(): Promise<SessionPersistenceData | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Clear all session history (for UI clear history button)
|
|
67
|
+
*/
|
|
68
|
+
clearHistory(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Get persistence status and statistics
|
|
71
|
+
*/
|
|
72
|
+
getStatus(): {
|
|
73
|
+
enabled: boolean;
|
|
74
|
+
lastPersisted: string | null;
|
|
75
|
+
totalSessions: number;
|
|
76
|
+
totalConnections: number;
|
|
77
|
+
totalHistoryEntries: number;
|
|
78
|
+
config: SessionPersistenceConfig;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Placeholder for current state persistence (to be implemented by callers)
|
|
82
|
+
*/
|
|
83
|
+
persistCurrentState(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Cleanup - stop timers and persist final state
|
|
86
|
+
*/
|
|
87
|
+
cleanup(): Promise<void>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SessionPersistenceManager
|
|
4
|
+
*
|
|
5
|
+
* Manages persistent storage of admin sessions, websocket connections, and session history
|
|
6
|
+
* with deduplication, atomic writes, and backup integration.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SessionPersistenceManager = void 0;
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const crypto_1 = require("crypto");
|
|
13
|
+
const SessionPersistence_js_1 = require("../../models/SessionPersistence.js");
|
|
14
|
+
const runtimeConfig_js_1 = require("../../config/runtimeConfig.js");
|
|
15
|
+
class SessionPersistenceManager {
|
|
16
|
+
static persistenceQueues = new Map();
|
|
17
|
+
config;
|
|
18
|
+
persistenceTimer = null;
|
|
19
|
+
lastPersistedData = null;
|
|
20
|
+
isShuttingDown = false;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = { ...SessionPersistence_js_1.DEFAULT_SESSION_PERSISTENCE_CONFIG, ...config };
|
|
23
|
+
this.loadConfigFromEnvironment();
|
|
24
|
+
if (this.config.enabled) {
|
|
25
|
+
this.setupPeriodicPersistence();
|
|
26
|
+
this.setupShutdownHandler();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load configuration from the centralized dashboard config module
|
|
31
|
+
*/
|
|
32
|
+
loadConfigFromEnvironment() {
|
|
33
|
+
const sp = (0, runtimeConfig_js_1.getRuntimeConfig)().dashboard.sessionPersistence;
|
|
34
|
+
this.config.enabled = sp.enabled;
|
|
35
|
+
this.config.persistenceDir = sp.persistenceDir;
|
|
36
|
+
this.config.backupIntegration = sp.backupIntegration;
|
|
37
|
+
this.config.retention.maxHistoryEntries = sp.retention.maxHistoryEntries;
|
|
38
|
+
this.config.retention.maxHistoryDays = sp.retention.maxHistoryDays;
|
|
39
|
+
this.config.retention.maxConnectionHistoryDays = sp.retention.maxConnectionHistoryDays;
|
|
40
|
+
this.config.persistence.intervalMs = sp.persistenceIntervalMs;
|
|
41
|
+
this.config.deduplication.enabled = sp.deduplicationEnabled;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Setup periodic persistence timer
|
|
45
|
+
*/
|
|
46
|
+
setupPeriodicPersistence() {
|
|
47
|
+
if (this.config.persistence.intervalMs > 0) {
|
|
48
|
+
this.persistenceTimer = setInterval(() => {
|
|
49
|
+
if (!this.isShuttingDown) {
|
|
50
|
+
this.persistCurrentState().catch(err => {
|
|
51
|
+
console.error('[SessionPersistence] Periodic persistence failed:', err);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}, this.config.persistence.intervalMs);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Setup graceful shutdown handler
|
|
59
|
+
*/
|
|
60
|
+
setupShutdownHandler() {
|
|
61
|
+
if (this.config.persistence.onShutdown) {
|
|
62
|
+
const gracefulShutdown = async () => {
|
|
63
|
+
if (!this.isShuttingDown) {
|
|
64
|
+
this.isShuttingDown = true;
|
|
65
|
+
console.log('[SessionPersistence] Graceful shutdown initiated...');
|
|
66
|
+
// Clear periodic timer
|
|
67
|
+
if (this.persistenceTimer) {
|
|
68
|
+
clearInterval(this.persistenceTimer);
|
|
69
|
+
this.persistenceTimer = null;
|
|
70
|
+
}
|
|
71
|
+
// Final persistence
|
|
72
|
+
try {
|
|
73
|
+
await this.persistCurrentState();
|
|
74
|
+
console.log('[SessionPersistence] Final state persisted successfully');
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.error('[SessionPersistence] Final persistence failed:', err);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
82
|
+
process.on('SIGINT', gracefulShutdown);
|
|
83
|
+
process.on('beforeExit', gracefulShutdown);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Calculate checksum for deduplication
|
|
88
|
+
*/
|
|
89
|
+
calculateChecksum(data) {
|
|
90
|
+
return (0, crypto_1.createHash)(this.config.deduplication.checksumAlgorithm)
|
|
91
|
+
.update(data)
|
|
92
|
+
.digest('hex');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if data has changed since last persistence
|
|
96
|
+
*/
|
|
97
|
+
hasDataChanged(data) {
|
|
98
|
+
if (!this.config.deduplication.enabled || !this.lastPersistedData) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
const currentChecksums = {
|
|
102
|
+
sessions: this.calculateChecksum(JSON.stringify(data.adminSessions.map((s) => s.id).sort())),
|
|
103
|
+
connections: this.calculateChecksum(JSON.stringify(data.webSocketConnections.map((c) => c.id).sort())),
|
|
104
|
+
history: this.calculateChecksum(JSON.stringify(data.sessionHistory.map((h) => h.id).sort()))
|
|
105
|
+
};
|
|
106
|
+
const lastChecksums = this.lastPersistedData.metadata.checksums;
|
|
107
|
+
return (currentChecksums.sessions !== lastChecksums.sessions ||
|
|
108
|
+
currentChecksums.connections !== lastChecksums.connections ||
|
|
109
|
+
currentChecksums.history !== lastChecksums.history);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Ensure persistence directories exist
|
|
113
|
+
*/
|
|
114
|
+
async ensureDirectories() {
|
|
115
|
+
const sessionsDir = (0, path_1.join)(this.config.persistenceDir, 'sessions');
|
|
116
|
+
const backupsDir = (0, path_1.join)(this.config.persistenceDir, 'backups');
|
|
117
|
+
await fs_1.promises.mkdir(sessionsDir, { recursive: true });
|
|
118
|
+
if (this.config.backupIntegration) {
|
|
119
|
+
await fs_1.promises.mkdir(backupsDir, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Atomic file write with backup
|
|
124
|
+
*/
|
|
125
|
+
async atomicWrite(filePath, content) {
|
|
126
|
+
if (!this.config.atomicWrites.enabled) {
|
|
127
|
+
await fs_1.promises.writeFile(filePath, content, 'utf8');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}${this.config.atomicWrites.tempSuffix}`;
|
|
131
|
+
const backupPath = filePath + this.config.atomicWrites.backupSuffix;
|
|
132
|
+
try {
|
|
133
|
+
// Write to temporary file
|
|
134
|
+
await fs_1.promises.writeFile(tempPath, content, 'utf8');
|
|
135
|
+
// Backup existing file if it exists
|
|
136
|
+
try {
|
|
137
|
+
await fs_1.promises.access(filePath);
|
|
138
|
+
await fs_1.promises.copyFile(filePath, backupPath);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// File doesn't exist, no backup needed
|
|
142
|
+
}
|
|
143
|
+
// Atomic move
|
|
144
|
+
await fs_1.promises.rename(tempPath, filePath);
|
|
145
|
+
// Remove backup if successful
|
|
146
|
+
try {
|
|
147
|
+
await fs_1.promises.unlink(backupPath);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Backup removal failure is not critical
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
// Cleanup temp file on failure
|
|
155
|
+
try {
|
|
156
|
+
await fs_1.promises.unlink(tempPath);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Cleanup failure is not critical
|
|
160
|
+
}
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
getQueueKey() {
|
|
165
|
+
return (0, path_1.join)(this.config.persistenceDir, 'sessions');
|
|
166
|
+
}
|
|
167
|
+
async withPersistenceLock(work) {
|
|
168
|
+
const queueKey = this.getQueueKey();
|
|
169
|
+
const previous = SessionPersistenceManager.persistenceQueues.get(queueKey) ?? Promise.resolve();
|
|
170
|
+
let release;
|
|
171
|
+
const current = new Promise((resolve) => {
|
|
172
|
+
release = resolve;
|
|
173
|
+
});
|
|
174
|
+
const tail = previous.catch(() => undefined).then(() => current);
|
|
175
|
+
SessionPersistenceManager.persistenceQueues.set(queueKey, tail);
|
|
176
|
+
await previous.catch(() => undefined);
|
|
177
|
+
try {
|
|
178
|
+
return await work();
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
release?.();
|
|
182
|
+
if (SessionPersistenceManager.persistenceQueues.get(queueKey) === tail) {
|
|
183
|
+
SessionPersistenceManager.persistenceQueues.delete(queueKey);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
defaultManifest() {
|
|
188
|
+
return {
|
|
189
|
+
metadata: this.createDefaultMetadata([], [], []),
|
|
190
|
+
files: {
|
|
191
|
+
adminSessions: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.ADMIN_SESSIONS,
|
|
192
|
+
webSocketConnections: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.WEBSOCKET_CONNECTIONS,
|
|
193
|
+
sessionHistory: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.SESSION_HISTORY,
|
|
194
|
+
},
|
|
195
|
+
retention: this.config.retention,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
createDefaultMetadata(adminSessions, webSocketConnections, sessionHistory) {
|
|
199
|
+
return {
|
|
200
|
+
lastPersisted: new Date(0).toISOString(),
|
|
201
|
+
version: 1,
|
|
202
|
+
totalSessions: adminSessions.length,
|
|
203
|
+
totalConnections: webSocketConnections.length,
|
|
204
|
+
totalHistoryEntries: sessionHistory.length,
|
|
205
|
+
checksums: {
|
|
206
|
+
sessions: this.calculateChecksum(JSON.stringify(adminSessions.map(session => session.id).sort())),
|
|
207
|
+
connections: this.calculateChecksum(JSON.stringify(webSocketConnections.map(connection => connection.id).sort())),
|
|
208
|
+
history: this.calculateChecksum(JSON.stringify(sessionHistory.map(entry => entry.id).sort())),
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async archiveCorruptFile(filePath) {
|
|
213
|
+
const corruptPath = `${filePath}.corrupt-${Date.now()}`;
|
|
214
|
+
try {
|
|
215
|
+
await fs_1.promises.rename(filePath, corruptPath);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Best effort only; keep going with fallback content.
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async parseJsonFileOrFallback(filePath, fallback, validate, label) {
|
|
222
|
+
try {
|
|
223
|
+
const content = await fs_1.promises.readFile(filePath, 'utf8');
|
|
224
|
+
const parsed = JSON.parse(content.replace(/^\uFEFF/, ''));
|
|
225
|
+
if (validate(parsed)) {
|
|
226
|
+
return parsed;
|
|
227
|
+
}
|
|
228
|
+
throw new Error(`Unexpected ${label} shape`);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
if (err.code === 'ENOENT') {
|
|
232
|
+
return fallback;
|
|
233
|
+
}
|
|
234
|
+
console.warn(`[SessionPersistence] Invalid ${label} in ${filePath}; archiving corrupt file and using fallback.`, err);
|
|
235
|
+
await this.archiveCorruptFile(filePath);
|
|
236
|
+
await this.atomicWrite(filePath, JSON.stringify(fallback, null, 2));
|
|
237
|
+
return fallback;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Apply retention policies to data
|
|
242
|
+
*/
|
|
243
|
+
applyRetentionPolicies(data) {
|
|
244
|
+
const now = new Date();
|
|
245
|
+
const maxHistoryDate = new Date(now.getTime() - (this.config.retention.maxHistoryDays * 24 * 60 * 60 * 1000));
|
|
246
|
+
const maxConnectionDate = new Date(now.getTime() - (this.config.retention.maxConnectionHistoryDays * 24 * 60 * 60 * 1000));
|
|
247
|
+
// Filter session history by date and count
|
|
248
|
+
const filteredHistory = data.sessionHistory
|
|
249
|
+
.filter(entry => {
|
|
250
|
+
const entryDate = new Date(entry.startTime);
|
|
251
|
+
return entryDate >= maxHistoryDate;
|
|
252
|
+
})
|
|
253
|
+
.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime())
|
|
254
|
+
.slice(0, this.config.retention.maxHistoryEntries);
|
|
255
|
+
// Filter connection history by date
|
|
256
|
+
const filteredConnections = data.webSocketConnections
|
|
257
|
+
.filter(conn => {
|
|
258
|
+
const connDate = new Date(conn.connectedAt);
|
|
259
|
+
return connDate >= maxConnectionDate;
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
...data,
|
|
263
|
+
sessionHistory: filteredHistory,
|
|
264
|
+
webSocketConnections: filteredConnections
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Create backup of current persistence data
|
|
269
|
+
*/
|
|
270
|
+
async createBackup(data) {
|
|
271
|
+
if (!this.config.backupIntegration) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
275
|
+
const backupDir = (0, path_1.join)(this.config.persistenceDir, 'backups', timestamp);
|
|
276
|
+
await fs_1.promises.mkdir(backupDir, { recursive: true });
|
|
277
|
+
const backupFiles = [
|
|
278
|
+
{ name: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.ADMIN_SESSIONS, data: data.adminSessions },
|
|
279
|
+
{ name: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.WEBSOCKET_CONNECTIONS, data: data.webSocketConnections },
|
|
280
|
+
{ name: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.SESSION_HISTORY, data: data.sessionHistory },
|
|
281
|
+
{ name: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.METADATA, data: data.metadata }
|
|
282
|
+
];
|
|
283
|
+
for (const file of backupFiles) {
|
|
284
|
+
const backupPath = (0, path_1.join)(backupDir, file.name);
|
|
285
|
+
await fs_1.promises.writeFile(backupPath, JSON.stringify(file.data, null, 2), 'utf8');
|
|
286
|
+
}
|
|
287
|
+
// Create manifest
|
|
288
|
+
const manifest = {
|
|
289
|
+
metadata: data.metadata,
|
|
290
|
+
files: {
|
|
291
|
+
adminSessions: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.ADMIN_SESSIONS,
|
|
292
|
+
webSocketConnections: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.WEBSOCKET_CONNECTIONS,
|
|
293
|
+
sessionHistory: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.SESSION_HISTORY
|
|
294
|
+
},
|
|
295
|
+
retention: this.config.retention
|
|
296
|
+
};
|
|
297
|
+
await fs_1.promises.writeFile((0, path_1.join)(backupDir, SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.MANIFEST), JSON.stringify(manifest, null, 2), 'utf8');
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Persist session data to disk
|
|
301
|
+
*/
|
|
302
|
+
async persistData(data) {
|
|
303
|
+
if (!this.config.enabled) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// Check for changes if deduplication is enabled
|
|
307
|
+
if (!this.hasDataChanged(data)) {
|
|
308
|
+
return; // No changes, skip persistence
|
|
309
|
+
}
|
|
310
|
+
// Apply retention policies
|
|
311
|
+
const filteredData = this.applyRetentionPolicies(data);
|
|
312
|
+
// Update metadata
|
|
313
|
+
const now = new Date().toISOString();
|
|
314
|
+
filteredData.metadata = {
|
|
315
|
+
...filteredData.metadata,
|
|
316
|
+
lastPersisted: now,
|
|
317
|
+
totalSessions: filteredData.adminSessions.length,
|
|
318
|
+
totalConnections: filteredData.webSocketConnections.length,
|
|
319
|
+
totalHistoryEntries: filteredData.sessionHistory.length,
|
|
320
|
+
checksums: {
|
|
321
|
+
sessions: this.calculateChecksum(JSON.stringify(filteredData.adminSessions.map(s => s.id).sort())),
|
|
322
|
+
connections: this.calculateChecksum(JSON.stringify(filteredData.webSocketConnections.map(c => c.id).sort())),
|
|
323
|
+
history: this.calculateChecksum(JSON.stringify(filteredData.sessionHistory.map(h => h.id).sort()))
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
await this.withPersistenceLock(async () => {
|
|
327
|
+
await this.ensureDirectories();
|
|
328
|
+
const sessionsDir = (0, path_1.join)(this.config.persistenceDir, 'sessions');
|
|
329
|
+
// Create backup before writing new data
|
|
330
|
+
await this.createBackup(filteredData);
|
|
331
|
+
// Write data files
|
|
332
|
+
await this.atomicWrite((0, path_1.join)(sessionsDir, SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.ADMIN_SESSIONS), JSON.stringify(filteredData.adminSessions, null, 2));
|
|
333
|
+
await this.atomicWrite((0, path_1.join)(sessionsDir, SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.WEBSOCKET_CONNECTIONS), JSON.stringify(filteredData.webSocketConnections, null, 2));
|
|
334
|
+
await this.atomicWrite((0, path_1.join)(sessionsDir, SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.SESSION_HISTORY), JSON.stringify(filteredData.sessionHistory, null, 2));
|
|
335
|
+
await this.atomicWrite((0, path_1.join)(sessionsDir, SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.METADATA), JSON.stringify(filteredData.metadata, null, 2));
|
|
336
|
+
// Write manifest
|
|
337
|
+
const manifest = {
|
|
338
|
+
metadata: filteredData.metadata,
|
|
339
|
+
files: {
|
|
340
|
+
adminSessions: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.ADMIN_SESSIONS,
|
|
341
|
+
webSocketConnections: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.WEBSOCKET_CONNECTIONS,
|
|
342
|
+
sessionHistory: SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.SESSION_HISTORY
|
|
343
|
+
},
|
|
344
|
+
retention: this.config.retention
|
|
345
|
+
};
|
|
346
|
+
await this.atomicWrite((0, path_1.join)(sessionsDir, SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.MANIFEST), JSON.stringify(manifest, null, 2));
|
|
347
|
+
this.lastPersistedData = filteredData;
|
|
348
|
+
}).catch((err) => {
|
|
349
|
+
console.error('[SessionPersistence] Failed to persist data:', err);
|
|
350
|
+
throw err;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Load persisted session data from disk
|
|
355
|
+
*/
|
|
356
|
+
async loadData() {
|
|
357
|
+
if (!this.config.enabled) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
const sessionsDir = (0, path_1.join)(this.config.persistenceDir, 'sessions');
|
|
361
|
+
const manifestPath = (0, path_1.join)(sessionsDir, SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.MANIFEST);
|
|
362
|
+
try {
|
|
363
|
+
// Check if manifest exists
|
|
364
|
+
await fs_1.promises.access(manifestPath);
|
|
365
|
+
const manifest = await this.parseJsonFileOrFallback(manifestPath, this.defaultManifest(), (value) => !!value && typeof value === 'object' && !Array.isArray(value), 'session persistence manifest');
|
|
366
|
+
// Load data files
|
|
367
|
+
const [adminSessions, webSocketConnections, sessionHistory, metadata] = await Promise.all([
|
|
368
|
+
this.parseJsonFileOrFallback((0, path_1.join)(sessionsDir, manifest.files.adminSessions), [], Array.isArray, 'admin sessions'),
|
|
369
|
+
this.parseJsonFileOrFallback((0, path_1.join)(sessionsDir, manifest.files.webSocketConnections), [], Array.isArray, 'websocket connections'),
|
|
370
|
+
this.parseJsonFileOrFallback((0, path_1.join)(sessionsDir, manifest.files.sessionHistory), [], Array.isArray, 'session history'),
|
|
371
|
+
this.parseJsonFileOrFallback((0, path_1.join)(sessionsDir, SessionPersistence_js_1.SESSION_PERSISTENCE_FILES.METADATA), this.createDefaultMetadata([], [], []), (value) => !!value && typeof value === 'object' && !Array.isArray(value), 'session metadata')
|
|
372
|
+
]);
|
|
373
|
+
const data = {
|
|
374
|
+
adminSessions,
|
|
375
|
+
webSocketConnections,
|
|
376
|
+
sessionHistory,
|
|
377
|
+
metadata: {
|
|
378
|
+
...this.createDefaultMetadata(adminSessions, webSocketConnections, sessionHistory),
|
|
379
|
+
...metadata,
|
|
380
|
+
totalSessions: adminSessions.length,
|
|
381
|
+
totalConnections: webSocketConnections.length,
|
|
382
|
+
totalHistoryEntries: sessionHistory.length,
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
this.lastPersistedData = data;
|
|
386
|
+
return data;
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
if (err.code === 'ENOENT') {
|
|
390
|
+
// No persisted data exists yet
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
console.error('[SessionPersistence] Failed to load data:', err);
|
|
394
|
+
throw err;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Clear all session history (for UI clear history button)
|
|
399
|
+
*/
|
|
400
|
+
async clearHistory() {
|
|
401
|
+
if (!this.config.enabled) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
// Load current data
|
|
405
|
+
const currentData = await this.loadData();
|
|
406
|
+
if (!currentData) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
// Clear history but keep active sessions and connections
|
|
410
|
+
const clearedData = {
|
|
411
|
+
...currentData,
|
|
412
|
+
sessionHistory: [],
|
|
413
|
+
metadata: {
|
|
414
|
+
...currentData.metadata,
|
|
415
|
+
totalHistoryEntries: 0,
|
|
416
|
+
checksums: {
|
|
417
|
+
...currentData.metadata.checksums,
|
|
418
|
+
history: this.calculateChecksum('[]')
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
await this.persistData(clearedData);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get persistence status and statistics
|
|
426
|
+
*/
|
|
427
|
+
getStatus() {
|
|
428
|
+
return {
|
|
429
|
+
enabled: this.config.enabled,
|
|
430
|
+
lastPersisted: this.lastPersistedData?.metadata.lastPersisted || null,
|
|
431
|
+
totalSessions: this.lastPersistedData?.metadata.totalSessions || 0,
|
|
432
|
+
totalConnections: this.lastPersistedData?.metadata.totalConnections || 0,
|
|
433
|
+
totalHistoryEntries: this.lastPersistedData?.metadata.totalHistoryEntries || 0,
|
|
434
|
+
config: this.config
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Placeholder for current state persistence (to be implemented by callers)
|
|
439
|
+
*/
|
|
440
|
+
async persistCurrentState() {
|
|
441
|
+
// No-op stub — callers (AdminPanel, WebSocketManager) may override.
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Cleanup - stop timers and persist final state
|
|
445
|
+
*/
|
|
446
|
+
async cleanup() {
|
|
447
|
+
this.isShuttingDown = true;
|
|
448
|
+
if (this.persistenceTimer) {
|
|
449
|
+
clearInterval(this.persistenceTimer);
|
|
450
|
+
this.persistenceTimer = null;
|
|
451
|
+
}
|
|
452
|
+
if (this.config.persistence.onShutdown) {
|
|
453
|
+
await this.persistCurrentState();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
exports.SessionPersistenceManager = SessionPersistenceManager;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThinClient - Stdio-to-HTTP bridge for MCP clients.
|
|
3
|
+
*
|
|
4
|
+
* **EXPERIMENTAL** — APIs, configuration, and behavior may change.
|
|
5
|
+
*
|
|
6
|
+
* A lightweight process that MCP hosts (VS Code, Claude) spawn via stdio.
|
|
7
|
+
* Instead of loading the full index, it forwards JSON-RPC frames to
|
|
8
|
+
* the leader server's HTTP endpoint.
|
|
9
|
+
*
|
|
10
|
+
* Flow: MCP Host --stdio--> ThinClient --HTTP--> Leader Server
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Reads JSON-RPC frames from stdin, POSTs to leader
|
|
14
|
+
* - Writes responses to stdout
|
|
15
|
+
* - Auto-discovers leader from instance state files
|
|
16
|
+
* - Reconnects on leader failover
|
|
17
|
+
*/
|
|
18
|
+
export interface ThinClientOptions {
|
|
19
|
+
/** URL of the leader's MCP transport (e.g., http://127.0.0.1:9090/mcp) */
|
|
20
|
+
leaderUrl?: string;
|
|
21
|
+
/** State directory to discover leader from port files */
|
|
22
|
+
stateDir?: string;
|
|
23
|
+
/** Retry settings */
|
|
24
|
+
maxRetries?: number;
|
|
25
|
+
retryDelayMs?: number;
|
|
26
|
+
/** Health check interval (ms) */
|
|
27
|
+
healthCheckMs?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class ThinClient {
|
|
30
|
+
private leaderUrl;
|
|
31
|
+
private readonly stateDir;
|
|
32
|
+
private readonly maxRetries;
|
|
33
|
+
private readonly retryDelayMs;
|
|
34
|
+
private _connected;
|
|
35
|
+
private _stopped;
|
|
36
|
+
private healthTimer;
|
|
37
|
+
constructor(options?: ThinClientOptions);
|
|
38
|
+
get connected(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Discover the leader URL from the state directory.
|
|
41
|
+
*/
|
|
42
|
+
discoverLeader(): string | null;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the leader URL (explicit or discovered).
|
|
45
|
+
* Re-discovers if the cached URL might be stale (leader changed port/host).
|
|
46
|
+
*/
|
|
47
|
+
resolveLeaderUrl(): string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Send a JSON-RPC request to the leader and return the response.
|
|
50
|
+
*/
|
|
51
|
+
sendRpc(method: string, params?: unknown, id?: number | string): Promise<unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* Check if the leader is healthy.
|
|
54
|
+
*/
|
|
55
|
+
checkHealth(): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* Process a raw JSON-RPC frame (as received from stdin).
|
|
58
|
+
*/
|
|
59
|
+
processFrame(frame: string): Promise<string>;
|
|
60
|
+
stop(): void;
|
|
61
|
+
private httpPost;
|
|
62
|
+
private httpGet;
|
|
63
|
+
private sleep;
|
|
64
|
+
}
|