@dollhousemcp/mcp-server 2.0.0-rc.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/CHANGELOG.md +52 -61
  2. package/README.github.md +2 -2
  3. package/README.md +2 -2
  4. package/README.md.backup +284 -224
  5. package/README.npm.md +2 -2
  6. package/dist/cache/LRUCache.d.ts +3 -0
  7. package/dist/cache/LRUCache.d.ts.map +1 -1
  8. package/dist/cache/LRUCache.js +36 -26
  9. package/dist/config/env.d.ts +14 -4
  10. package/dist/config/env.d.ts.map +1 -1
  11. package/dist/config/env.js +20 -6
  12. package/dist/di/Container.d.ts +21 -0
  13. package/dist/di/Container.d.ts.map +1 -1
  14. package/dist/di/Container.js +250 -53
  15. package/dist/elements/BaseElement.d.ts.map +1 -1
  16. package/dist/elements/BaseElement.js +5 -10
  17. package/dist/elements/base/BaseElementManager.d.ts +22 -0
  18. package/dist/elements/base/BaseElementManager.d.ts.map +1 -1
  19. package/dist/elements/base/BaseElementManager.js +47 -7
  20. package/dist/elements/memories/Memory.d.ts +1 -0
  21. package/dist/elements/memories/Memory.d.ts.map +1 -1
  22. package/dist/elements/memories/Memory.js +12 -8
  23. package/dist/elements/memories/MemoryManager.d.ts.map +1 -1
  24. package/dist/elements/memories/MemoryManager.js +23 -42
  25. package/dist/elements/memories/MemorySearchIndex.js +2 -2
  26. package/dist/generated/version.d.ts +2 -2
  27. package/dist/generated/version.d.ts.map +1 -1
  28. package/dist/generated/version.js +3 -3
  29. package/dist/handlers/EnhancedIndexHandler.js +6 -6
  30. package/dist/handlers/element-crud/listElements.d.ts +2 -0
  31. package/dist/handlers/element-crud/listElements.d.ts.map +1 -1
  32. package/dist/handlers/element-crud/listElements.js +3 -1
  33. package/dist/handlers/mcp-aql/Gatekeeper.d.ts.map +1 -1
  34. package/dist/handlers/mcp-aql/Gatekeeper.js +23 -17
  35. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts +14 -0
  36. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
  37. package/dist/handlers/mcp-aql/MCPAQLHandler.js +110 -14
  38. package/dist/handlers/mcp-aql/OperationRouter.d.ts.map +1 -1
  39. package/dist/handlers/mcp-aql/OperationRouter.js +13 -1
  40. package/dist/handlers/mcp-aql/OperationSchema.d.ts +7 -0
  41. package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
  42. package/dist/handlers/mcp-aql/OperationSchema.js +52 -1
  43. package/dist/handlers/mcp-aql/evaluatePermission.d.ts +53 -0
  44. package/dist/handlers/mcp-aql/evaluatePermission.d.ts.map +1 -0
  45. package/dist/handlers/mcp-aql/evaluatePermission.js +132 -0
  46. package/dist/handlers/mcp-aql/policies/ToolClassification.d.ts.map +1 -1
  47. package/dist/handlers/mcp-aql/policies/ToolClassification.js +2 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +3 -3
  50. package/dist/logging/LogHooks.js +11 -11
  51. package/dist/logging/LogManager.d.ts +0 -2
  52. package/dist/logging/LogManager.d.ts.map +1 -1
  53. package/dist/logging/LogManager.js +1 -3
  54. package/dist/logging/sinks/MemoryLogSink.d.ts +2 -0
  55. package/dist/logging/sinks/MemoryLogSink.d.ts.map +1 -1
  56. package/dist/logging/sinks/MemoryLogSink.js +12 -3
  57. package/dist/logging/types.d.ts +0 -2
  58. package/dist/logging/types.d.ts.map +1 -1
  59. package/dist/logging/types.js +1 -1
  60. package/dist/metrics/GatekeeperMetricsTracker.d.ts +32 -0
  61. package/dist/metrics/GatekeeperMetricsTracker.d.ts.map +1 -0
  62. package/dist/metrics/GatekeeperMetricsTracker.js +42 -0
  63. package/dist/metrics/MetricsManager.d.ts +47 -0
  64. package/dist/metrics/MetricsManager.d.ts.map +1 -0
  65. package/dist/metrics/MetricsManager.js +232 -0
  66. package/dist/metrics/OperationMetricsTracker.d.ts +32 -0
  67. package/dist/metrics/OperationMetricsTracker.d.ts.map +1 -0
  68. package/dist/metrics/OperationMetricsTracker.js +53 -0
  69. package/dist/metrics/collectors/DefaultElementProviderCollector.d.ts +27 -0
  70. package/dist/metrics/collectors/DefaultElementProviderCollector.d.ts.map +1 -0
  71. package/dist/metrics/collectors/DefaultElementProviderCollector.js +69 -0
  72. package/dist/metrics/collectors/FileLockManagerCollector.d.ts +16 -0
  73. package/dist/metrics/collectors/FileLockManagerCollector.d.ts.map +1 -0
  74. package/dist/metrics/collectors/FileLockManagerCollector.js +51 -0
  75. package/dist/metrics/collectors/GatekeeperMetricsCollector.d.ts +16 -0
  76. package/dist/metrics/collectors/GatekeeperMetricsCollector.d.ts.map +1 -0
  77. package/dist/metrics/collectors/GatekeeperMetricsCollector.js +76 -0
  78. package/dist/metrics/collectors/LRUCacheCollector.d.ts +18 -0
  79. package/dist/metrics/collectors/LRUCacheCollector.d.ts.map +1 -0
  80. package/dist/metrics/collectors/LRUCacheCollector.js +95 -0
  81. package/dist/metrics/collectors/OperationMetricsCollector.d.ts +16 -0
  82. package/dist/metrics/collectors/OperationMetricsCollector.d.ts.map +1 -0
  83. package/dist/metrics/collectors/OperationMetricsCollector.js +80 -0
  84. package/dist/metrics/collectors/OperationalTelemetryCollector.d.ts +17 -0
  85. package/dist/metrics/collectors/OperationalTelemetryCollector.d.ts.map +1 -0
  86. package/dist/metrics/collectors/OperationalTelemetryCollector.js +26 -0
  87. package/dist/metrics/collectors/PerformanceMonitorCollector.d.ts +14 -0
  88. package/dist/metrics/collectors/PerformanceMonitorCollector.d.ts.map +1 -0
  89. package/dist/metrics/collectors/PerformanceMonitorCollector.js +141 -0
  90. package/dist/metrics/collectors/SecurityMonitorCollector.d.ts +21 -0
  91. package/dist/metrics/collectors/SecurityMonitorCollector.d.ts.map +1 -0
  92. package/dist/metrics/collectors/SecurityMonitorCollector.js +56 -0
  93. package/dist/metrics/collectors/SecurityTelemetryCollector.d.ts +15 -0
  94. package/dist/metrics/collectors/SecurityTelemetryCollector.d.ts.map +1 -0
  95. package/dist/metrics/collectors/SecurityTelemetryCollector.js +112 -0
  96. package/dist/metrics/collectors/TriggerMetricsTrackerCollector.d.ts +16 -0
  97. package/dist/metrics/collectors/TriggerMetricsTrackerCollector.d.ts.map +1 -0
  98. package/dist/metrics/collectors/TriggerMetricsTrackerCollector.js +26 -0
  99. package/dist/metrics/collectors/index.d.ts +11 -0
  100. package/dist/metrics/collectors/index.d.ts.map +1 -0
  101. package/dist/metrics/collectors/index.js +11 -0
  102. package/dist/metrics/sinks/MemoryMetricsSink.d.ts +22 -0
  103. package/dist/metrics/sinks/MemoryMetricsSink.d.ts.map +1 -0
  104. package/dist/metrics/sinks/MemoryMetricsSink.js +121 -0
  105. package/dist/metrics/types.d.ts +98 -0
  106. package/dist/metrics/types.d.ts.map +1 -0
  107. package/dist/metrics/types.js +24 -0
  108. package/dist/portfolio/DefaultElementProvider.d.ts.map +1 -1
  109. package/dist/portfolio/DefaultElementProvider.js +1 -7
  110. package/dist/portfolio/EnhancedIndexManager.d.ts.map +1 -1
  111. package/dist/portfolio/EnhancedIndexManager.js +18 -18
  112. package/dist/portfolio/NLPScoringManager.d.ts.map +1 -1
  113. package/dist/portfolio/NLPScoringManager.js +5 -9
  114. package/dist/portfolio/PortfolioIndexManager.js +2 -2
  115. package/dist/portfolio/RelationshipManager.js +2 -2
  116. package/dist/portfolio/VerbTriggerManager.d.ts.map +1 -1
  117. package/dist/portfolio/VerbTriggerManager.js +5 -19
  118. package/dist/portfolio/config/IndexConfig.d.ts.map +1 -1
  119. package/dist/portfolio/config/IndexConfig.js +1 -12
  120. package/dist/portfolio/enhanced-index/ElementDefinitionBuilder.d.ts.map +1 -1
  121. package/dist/portfolio/enhanced-index/ElementDefinitionBuilder.js +3 -15
  122. package/dist/portfolio/enhanced-index/SemanticRelationshipService.d.ts.map +1 -1
  123. package/dist/portfolio/enhanced-index/SemanticRelationshipService.js +2 -16
  124. package/dist/portfolio/types/RelationshipTypes.d.ts.map +1 -1
  125. package/dist/portfolio/types/RelationshipTypes.js +3 -17
  126. package/dist/security/audit/config/suppressions.d.ts.map +1 -1
  127. package/dist/security/audit/config/suppressions.js +36 -8
  128. package/dist/security/constants.d.ts.map +1 -1
  129. package/dist/security/constants.js +10 -6
  130. package/dist/security/fileLockManager.d.ts.map +1 -1
  131. package/dist/security/fileLockManager.js +8 -6
  132. package/dist/security/secureYamlParser.d.ts.map +1 -1
  133. package/dist/security/secureYamlParser.js +1 -13
  134. package/dist/security/securityMonitor.d.ts +2 -1
  135. package/dist/security/securityMonitor.d.ts.map +1 -1
  136. package/dist/security/securityMonitor.js +14 -3
  137. package/dist/security/telemetry/SecurityTelemetry.d.ts +16 -0
  138. package/dist/security/telemetry/SecurityTelemetry.d.ts.map +1 -1
  139. package/dist/security/telemetry/SecurityTelemetry.js +30 -2
  140. package/dist/security/tokenManager.d.ts +3 -0
  141. package/dist/security/tokenManager.d.ts.map +1 -1
  142. package/dist/security/tokenManager.js +13 -5
  143. package/dist/security/validation/BackgroundValidator.d.ts.map +1 -1
  144. package/dist/security/validation/BackgroundValidator.js +7 -7
  145. package/dist/server/startup.d.ts.map +1 -1
  146. package/dist/server/startup.js +8 -24
  147. package/dist/server/tools/MCPAQLTools.js +4 -1
  148. package/dist/services/ActivationStore.d.ts.map +1 -1
  149. package/dist/services/ActivationStore.js +9 -3
  150. package/dist/services/FileWatchService.d.ts +1 -0
  151. package/dist/services/FileWatchService.d.ts.map +1 -1
  152. package/dist/services/FileWatchService.js +83 -48
  153. package/dist/services/MetadataService.d.ts.map +1 -1
  154. package/dist/services/MetadataService.js +7 -2
  155. package/dist/services/query/ElementQueryService.d.ts.map +1 -1
  156. package/dist/services/query/ElementQueryService.js +1 -41
  157. package/dist/services/query/PaginationService.d.ts.map +1 -1
  158. package/dist/services/query/PaginationService.js +1 -14
  159. package/dist/services/query/SortService.d.ts.map +1 -1
  160. package/dist/services/query/SortService.js +1 -6
  161. package/dist/services/validation/ValidationService.d.ts.map +1 -1
  162. package/dist/services/validation/ValidationService.js +3 -8
  163. package/dist/storage/ElementStorageLayer.d.ts.map +1 -1
  164. package/dist/storage/ElementStorageLayer.js +5 -2
  165. package/dist/storage/MemoryStorageLayer.d.ts.map +1 -1
  166. package/dist/storage/MemoryStorageLayer.js +5 -2
  167. package/dist/telemetry/OperationalTelemetry.js +2 -2
  168. package/dist/utils/EventDeduplicator.d.ts +44 -0
  169. package/dist/utils/EventDeduplicator.d.ts.map +1 -0
  170. package/dist/utils/EventDeduplicator.js +93 -0
  171. package/dist/utils/FileLock.d.ts.map +1 -1
  172. package/dist/utils/FileLock.js +1 -9
  173. package/dist/utils/PerformanceMonitor.d.ts.map +1 -1
  174. package/dist/utils/PerformanceMonitor.js +5 -5
  175. package/dist/utils/SlidingWindowRateLimiter.d.ts +13 -0
  176. package/dist/utils/SlidingWindowRateLimiter.d.ts.map +1 -0
  177. package/dist/utils/SlidingWindowRateLimiter.js +23 -0
  178. package/dist/web/console/IngestRoutes.d.ts +84 -0
  179. package/dist/web/console/IngestRoutes.d.ts.map +1 -0
  180. package/dist/web/console/IngestRoutes.js +252 -0
  181. package/dist/web/console/LeaderElection.d.ts +89 -0
  182. package/dist/web/console/LeaderElection.d.ts.map +1 -0
  183. package/dist/web/console/LeaderElection.js +205 -0
  184. package/dist/web/console/LeaderForwardingSink.d.ts +61 -0
  185. package/dist/web/console/LeaderForwardingSink.d.ts.map +1 -0
  186. package/dist/web/console/LeaderForwardingSink.js +197 -0
  187. package/dist/web/console/SessionNames.d.ts +46 -0
  188. package/dist/web/console/SessionNames.d.ts.map +1 -0
  189. package/dist/web/console/SessionNames.js +257 -0
  190. package/dist/web/console/UnifiedConsole.d.ts +64 -0
  191. package/dist/web/console/UnifiedConsole.d.ts.map +1 -0
  192. package/dist/web/console/UnifiedConsole.js +119 -0
  193. package/dist/web/contentPipeline.d.ts +58 -0
  194. package/dist/web/contentPipeline.d.ts.map +1 -0
  195. package/dist/web/contentPipeline.js +112 -0
  196. package/dist/web/portDiscovery.d.ts +58 -0
  197. package/dist/web/portDiscovery.d.ts.map +1 -0
  198. package/dist/web/portDiscovery.js +143 -0
  199. package/dist/web/public/app.js +148 -60
  200. package/dist/web/public/logs.js +638 -0
  201. package/dist/web/public/metrics.js +682 -0
  202. package/dist/web/public/permissions.js +394 -0
  203. package/dist/web/public/sessions.js +369 -0
  204. package/dist/web/routes/healthRoutes.d.ts +16 -0
  205. package/dist/web/routes/healthRoutes.d.ts.map +1 -0
  206. package/dist/web/routes/healthRoutes.js +29 -0
  207. package/dist/web/routes/logRoutes.d.ts +18 -0
  208. package/dist/web/routes/logRoutes.d.ts.map +1 -0
  209. package/dist/web/routes/logRoutes.js +126 -0
  210. package/dist/web/routes/metricsRoutes.d.ts +17 -0
  211. package/dist/web/routes/metricsRoutes.d.ts.map +1 -0
  212. package/dist/web/routes/metricsRoutes.js +90 -0
  213. package/dist/web/routes/permissionRoutes.d.ts +16 -0
  214. package/dist/web/routes/permissionRoutes.d.ts.map +1 -0
  215. package/dist/web/routes/permissionRoutes.js +133 -0
  216. package/dist/web/routes.d.ts.map +1 -1
  217. package/dist/web/routes.js +309 -339
  218. package/dist/web/server.d.ts +21 -1
  219. package/dist/web/server.d.ts.map +1 -1
  220. package/dist/web/server.js +42 -4
  221. package/dist/web/sinks/WebSSELogSink.d.ts +15 -0
  222. package/dist/web/sinks/WebSSELogSink.d.ts.map +1 -0
  223. package/dist/web/sinks/WebSSELogSink.js +22 -0
  224. package/dist/web/sinks/WebSSEMetricsSink.d.ts +16 -0
  225. package/dist/web/sinks/WebSSEMetricsSink.d.ts.map +1 -0
  226. package/dist/web/sinks/WebSSEMetricsSink.js +23 -0
  227. package/package.json +2 -2
  228. package/server.json +2 -2
  229. package/dist/constants/version.d.ts +0 -3
  230. package/dist/constants/version.d.ts.map +0 -1
  231. package/dist/constants/version.js +0 -4
  232. package/dist/logging/sinks/SSELogSink.d.ts +0 -35
  233. package/dist/logging/sinks/SSELogSink.d.ts.map +0 -1
  234. package/dist/logging/sinks/SSELogSink.js +0 -181
  235. package/dist/logging/viewer/viewerHtml.d.ts +0 -8
  236. package/dist/logging/viewer/viewerHtml.d.ts.map +0 -1
  237. package/dist/logging/viewer/viewerHtml.js +0 -204
  238. package/dist/web/public/public/app.js +0 -1878
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Event ingestion routes for the unified web console.
3
+ *
4
+ * The console leader mounts these routes so follower MCP servers can
5
+ * forward their logs, metrics, and session lifecycle events. All ingested
6
+ * entries are stamped with `_sessionId` in their data field and then
7
+ * broadcast to SSE clients via the existing log/metrics broadcast hooks.
8
+ *
9
+ * Routes:
10
+ * - POST /api/ingest/logs — Batched log entries from a follower
11
+ * - POST /api/ingest/metrics — Metric snapshots from a follower
12
+ * - POST /api/ingest/session — Session lifecycle events (started/stopped/heartbeat)
13
+ * - GET /api/sessions — Active session list for the UI
14
+ *
15
+ * @since v2.1.0 — Issue #1700
16
+ */
17
+ import express, { Router } from 'express';
18
+ import { SlidingWindowRateLimiter } from '../../utils/SlidingWindowRateLimiter.js';
19
+ import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
20
+ import { SessionNamePool } from './SessionNames.js';
21
+ import { logger } from '../../utils/logger.js';
22
+ /** Maximum payload size for ingestion requests */
23
+ const MAX_PAYLOAD_SIZE = '1mb';
24
+ /** Rate limit: max requests per window per source */
25
+ const RATE_LIMIT_MAX = 1000;
26
+ const RATE_LIMIT_WINDOW_MS = 60_000;
27
+ /** How often to check for stale sessions (ms) */
28
+ const REAPER_INTERVAL_MS = 5_000;
29
+ /** How long since last heartbeat before a session is considered dead (ms) */
30
+ const SESSION_STALE_MS = 15_000;
31
+ /** Normalize a string via UnicodeValidator (DMCP-SEC-004) */
32
+ function normalizeInput(s) {
33
+ return UnicodeValidator.normalize(s).normalizedContent;
34
+ }
35
+ /**
36
+ * Create the ingestion routes and session registry.
37
+ *
38
+ * @param broadcasts - Callbacks to forward ingested events to SSE clients
39
+ * @returns Router and session management functions
40
+ */
41
+ export function createIngestRoutes(broadcasts) {
42
+ const router = Router();
43
+ const sessions = new Map();
44
+ const namePool = new SessionNamePool();
45
+ const rateLimiter = new SlidingWindowRateLimiter(RATE_LIMIT_MAX, RATE_LIMIT_WINDOW_MS);
46
+ // JSON body parsing with size limit
47
+ router.use(express.json({ limit: MAX_PAYLOAD_SIZE }));
48
+ /**
49
+ * POST /api/ingest/logs — Receive batched log entries from a follower.
50
+ */
51
+ router.post('/api/ingest/logs', (req, res) => {
52
+ if (!rateLimiter.tryAcquire()) {
53
+ res.status(429).json({ error: 'Rate limit exceeded' });
54
+ return;
55
+ }
56
+ const payload = req.body;
57
+ if (!payload?.sessionId || !Array.isArray(payload.entries)) {
58
+ const received = payload ? Object.keys(payload) : [];
59
+ logger.warn('[IngestRoutes] Invalid log payload', { received, hasSessionId: !!payload?.sessionId, hasEntries: Array.isArray(payload?.entries) });
60
+ res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'entries'], received });
61
+ return;
62
+ }
63
+ payload.sessionId = normalizeInput(payload.sessionId);
64
+ let count = 0;
65
+ let skipped = 0;
66
+ for (const entry of payload.entries) {
67
+ if (!entry || typeof entry.message !== 'string') {
68
+ skipped++;
69
+ continue;
70
+ }
71
+ const stamped = {
72
+ ...entry,
73
+ data: { ...entry.data, _sessionId: payload.sessionId },
74
+ };
75
+ broadcasts.logBroadcast(stamped);
76
+ count++;
77
+ }
78
+ // Update session heartbeat
79
+ const session = sessions.get(payload.sessionId);
80
+ if (session) {
81
+ session.lastHeartbeat = new Date().toISOString();
82
+ }
83
+ if (skipped > 0) {
84
+ logger.debug(`[IngestRoutes] Log ingest from ${session?.displayName ?? payload.sessionId}: accepted=${count}, skipped=${skipped}`);
85
+ }
86
+ res.status(200).json({ accepted: count, skipped });
87
+ });
88
+ /**
89
+ * POST /api/ingest/metrics — Receive metric snapshots from a follower.
90
+ */
91
+ router.post('/api/ingest/metrics', (req, res) => {
92
+ if (!rateLimiter.tryAcquire()) {
93
+ res.status(429).json({ error: 'Rate limit exceeded' });
94
+ return;
95
+ }
96
+ const payload = req.body;
97
+ if (!payload?.sessionId || !payload.snapshot) {
98
+ const received = payload ? Object.keys(payload) : [];
99
+ logger.warn('[IngestRoutes] Invalid metrics payload', { received });
100
+ res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'snapshot'], received });
101
+ return;
102
+ }
103
+ payload.sessionId = normalizeInput(payload.sessionId);
104
+ if (broadcasts.metricsOnSnapshot) {
105
+ broadcasts.metricsOnSnapshot(payload.snapshot);
106
+ }
107
+ const session = sessions.get(payload.sessionId);
108
+ logger.debug(`[IngestRoutes] Metrics ingested from ${session?.displayName ?? payload.sessionId}`);
109
+ res.status(200).json({ accepted: true });
110
+ });
111
+ /**
112
+ * POST /api/ingest/session — Session lifecycle events.
113
+ */
114
+ router.post('/api/ingest/session', (req, res) => {
115
+ const payload = req.body;
116
+ if (!payload?.sessionId || !payload.event) {
117
+ const received = payload ? Object.keys(payload) : [];
118
+ logger.warn('[IngestRoutes] Invalid session event payload', { received });
119
+ res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'event'], received });
120
+ return;
121
+ }
122
+ payload.sessionId = normalizeInput(payload.sessionId);
123
+ const now = new Date().toISOString();
124
+ switch (payload.event) {
125
+ case 'started': {
126
+ const displayName = namePool.assign(payload.sessionId);
127
+ const color = namePool.getColor(payload.sessionId) ?? '#3b82f6';
128
+ const info = {
129
+ sessionId: payload.sessionId,
130
+ displayName,
131
+ color,
132
+ pid: payload.pid,
133
+ startedAt: payload.startedAt || now,
134
+ lastHeartbeat: now,
135
+ status: 'active',
136
+ isLeader: false,
137
+ };
138
+ sessions.set(payload.sessionId, info);
139
+ logger.info('[IngestRoutes] Session registered', {
140
+ displayName, sessionId: payload.sessionId, pid: payload.pid, color,
141
+ activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length,
142
+ });
143
+ broadcasts.sessionBroadcast?.(info);
144
+ break;
145
+ }
146
+ case 'stopped': {
147
+ const existing = sessions.get(payload.sessionId);
148
+ if (existing) {
149
+ existing.status = 'ended';
150
+ existing.lastHeartbeat = now;
151
+ namePool.release(payload.sessionId);
152
+ logger.info('[IngestRoutes] Session stopped', {
153
+ displayName: existing.displayName, sessionId: payload.sessionId, pid: existing.pid,
154
+ activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1,
155
+ });
156
+ broadcasts.sessionBroadcast?.(existing);
157
+ }
158
+ break;
159
+ }
160
+ case 'heartbeat': {
161
+ const existing = sessions.get(payload.sessionId);
162
+ if (existing) {
163
+ existing.lastHeartbeat = now;
164
+ }
165
+ break;
166
+ }
167
+ }
168
+ res.status(200).json({ ok: true });
169
+ });
170
+ /**
171
+ * GET /api/sessions — List all tracked sessions.
172
+ */
173
+ router.get('/api/sessions', (_req, res) => {
174
+ const allSessions = Array.from(sessions.values());
175
+ res.json({ sessions: allSessions });
176
+ });
177
+ /**
178
+ * POST /api/sessions/:sessionId/kill — Terminate a session's server process.
179
+ */
180
+ router.post('/api/sessions/:sessionId/kill', (req, res) => {
181
+ const sessionId = req.params['sessionId'];
182
+ const session = sessions.get(sessionId);
183
+ if (!session) {
184
+ logger.warn('[IngestRoutes] Kill requested for unknown session', { sessionId });
185
+ res.status(404).json({ error: 'Session not found', sessionId });
186
+ return;
187
+ }
188
+ if (!session.pid) {
189
+ res.status(400).json({ error: 'No PID for session', sessionId, displayName: session.displayName });
190
+ return;
191
+ }
192
+ try {
193
+ process.kill(session.pid, 'SIGTERM');
194
+ session.status = 'ended';
195
+ namePool.release(sessionId);
196
+ logger.info('[IngestRoutes] Session killed', {
197
+ displayName: session.displayName, sessionId, pid: session.pid,
198
+ activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1,
199
+ });
200
+ res.json({ ok: true, killed: session.displayName, pid: session.pid });
201
+ }
202
+ catch (err) {
203
+ const message = err.message;
204
+ logger.error('[IngestRoutes] Failed to kill session', {
205
+ displayName: session.displayName, sessionId, pid: session.pid, error: message,
206
+ });
207
+ res.status(500).json({ error: 'Failed to kill session', sessionId, displayName: session.displayName, pid: session.pid, detail: message });
208
+ }
209
+ });
210
+ // Reaper: periodically check for stale sessions whose heartbeat has expired
211
+ const reaperInterval = setInterval(() => {
212
+ const now = Date.now();
213
+ for (const [id, session] of sessions) {
214
+ if (session.status !== 'active')
215
+ continue;
216
+ if (session.isLeader)
217
+ continue; // leader manages itself
218
+ const age = now - new Date(session.lastHeartbeat).getTime();
219
+ if (age > SESSION_STALE_MS) {
220
+ session.status = 'ended';
221
+ namePool.release(id);
222
+ logger.info('[IngestRoutes] Reaped stale session', {
223
+ displayName: session.displayName, sessionId: id, pid: session.pid,
224
+ lastHeartbeatAgo: `${Math.round(age / 1000)}s`,
225
+ activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1,
226
+ });
227
+ broadcasts.sessionBroadcast?.(session);
228
+ }
229
+ }
230
+ }, REAPER_INTERVAL_MS);
231
+ reaperInterval.unref();
232
+ function getSessions() {
233
+ return Array.from(sessions.values()).filter(s => s.status === 'active');
234
+ }
235
+ function registerLeaderSession(sessionId, pid) {
236
+ const displayName = namePool.assign(sessionId, true);
237
+ const color = namePool.getColor(sessionId) ?? '#3b82f6';
238
+ sessions.set(sessionId, {
239
+ sessionId,
240
+ displayName,
241
+ color,
242
+ pid,
243
+ startedAt: new Date().toISOString(),
244
+ lastHeartbeat: new Date().toISOString(),
245
+ status: 'active',
246
+ isLeader: true,
247
+ });
248
+ logger.info('[IngestRoutes] Leader session registered', { displayName, sessionId, pid, color });
249
+ }
250
+ return { router, getSessions, registerLeaderSession };
251
+ }
252
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSW5nZXN0Um91dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3dlYi9jb25zb2xlL0luZ2VzdFJvdXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLE9BQU8sRUFBRSxFQUFFLE1BQU0sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUkxQyxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSx5Q0FBeUMsQ0FBQztBQUNuRixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUNqRixPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDcEQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRS9DLGtEQUFrRDtBQUNsRCxNQUFNLGdCQUFnQixHQUFHLEtBQUssQ0FBQztBQUUvQixxREFBcUQ7QUFDckQsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDO0FBQzVCLE1BQU0sb0JBQW9CLEdBQUcsTUFBTSxDQUFDO0FBRXBDLGlEQUFpRDtBQUNqRCxNQUFNLGtCQUFrQixHQUFHLEtBQUssQ0FBQztBQUVqQyw2RUFBNkU7QUFDN0UsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLENBQUM7QUFpRWhDLDZEQUE2RDtBQUM3RCxTQUFTLGNBQWMsQ0FBQyxDQUFTO0lBQy9CLE9BQU8sZ0JBQWdCLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLGlCQUFpQixDQUFDO0FBQ3pELENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxVQUE0QjtJQUM3RCxNQUFNLE1BQU0sR0FBRyxNQUFNLEVBQUUsQ0FBQztJQUN4QixNQUFNLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBdUIsQ0FBQztJQUNoRCxNQUFNLFFBQVEsR0FBRyxJQUFJLGVBQWUsRUFBRSxDQUFDO0lBQ3ZDLE1BQU0sV0FBVyxHQUFHLElBQUks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LENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxLQUFLLFFBQVEsQ0FBQyxDQUFDO0lBQzFFLENBQUM7SUFFRCxTQUFTLHFCQUFxQixDQUFDLFNBQWlCLEVBQUUsR0FBVztRQUMzRCxNQUFNLFdBQVcsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNyRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxJQUFJLFNBQVMsQ0FBQztRQUN4RCxRQUFRLENBQUMsR0FBRyxDQUFDLFNBQVMsRUFBRTtZQUN0QixTQUFTO1lBQ1QsV0FBVztZQUNYLEtBQUs7WUFDTCxHQUFHO1lBQ0gsU0FBUyxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFO1lBQ25DLGFBQWEsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRTtZQUN2QyxNQUFNLEVBQUUsUUFBUTtZQUNoQixRQUFRLEVBQUUsSUFBSTtTQUNmLENBQUMsQ0FBQztRQUNILE1BQU0sQ0FBQyxJQUFJLENBQUMsMENBQTBDLEVBQUUsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ2xHLENBQUM7SUFFRCxPQUFPLEVBQUUsTUFBTSxFQUFFLFdBQVcsRUFBRSxxQkFBcUIsRUFBRSxDQUFDO0FBQ3hELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEV2ZW50IGluZ2VzdGlvbiByb3V0ZXMgZm9yIHRoZSB1bmlmaWVkIHdlYiBjb25zb2xlLlxuICpcbiAqIFRoZSBjb25zb2xlIGxlYWRlciBtb3VudHMgdGhlc2Ugcm91dGVzIHNvIGZvbGxvd2VyIE1DUCBzZXJ2ZXJzIGNhblxuICogZm9yd2FyZCB0aGVpciBsb2dzLCBtZXRyaWNzLCBhbmQgc2Vzc2lvbiBsaWZlY3ljbGUgZXZlbnRzLiBBbGwgaW5nZXN0ZWRcbiAqIGVudHJpZXMgYXJlIHN0YW1wZWQgd2l0aCBgX3Nlc3Npb25JZGAgaW4gdGhlaXIgZGF0YSBmaWVsZCBhbmQgdGhlblxuICogYnJvYWRjYXN0IHRvIFNTRSBjbGllbnRzIHZpYSB0aGUgZXhpc3RpbmcgbG9nL21ldHJpY3MgYnJvYWRjYXN0IGhvb2tzLlxuICpcbiAqIFJvdXRlczpcbiAqIC0gUE9TVCAvYXBpL2luZ2VzdC9sb2dzICAgICDigJQgQmF0Y2hlZCBsb2cgZW50cmllcyBmcm9tIGEgZm9sbG93ZXJcbiAqIC0gUE9TVCAvYXBpL2luZ2VzdC9tZXRyaWNzICDigJQgTWV0cmljIHNuYXBzaG90cyBmcm9tIGEgZm9sbG93ZXJcbiAqIC0gUE9TVCAvYXBpL2luZ2VzdC9zZXNzaW9uICDigJQgU2Vzc2lvbiBsaWZlY3ljbGUgZXZlbnRzIChzdGFydGVkL3N0b3BwZWQvaGVhcnRiZWF0KVxuICogLSBHRVQgIC9hcGkvc2Vzc2lvbnMgICAgICAgIOKAlCBBY3RpdmUgc2Vzc2lvbiBsaXN0IGZvciB0aGUgVUlcbiAqXG4gKiBAc2luY2UgdjIuMS4wIOKAlCBJc3N1ZSAjMTcwMFxuICovXG5cbmltcG9ydCBleHByZXNzLCB7IFJvdXRlciB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0LCBSZXNwb25zZSB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHR5cGUgeyBVbmlmaWVkTG9nRW50cnkgfSBmcm9tICcuLi8uLi9sb2dnaW5nL3R5cGVzLmpzJztcbmltcG9ydCB0eXBlIHsgTWV0cmljU25hcHNob3QgfSBmcm9tICcuLi8uLi9tZXRyaWNzL3R5cGVzLmpzJztcbmltcG9ydCB7IFNsaWRpbmdXaW5kb3dSYXRlTGltaXRlciB9IGZyb20gJy4uLy4uL3V0aWxzL1NsaWRpbmdXaW5kb3dSYXRlTGltaXRlci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IFNlc3Npb25OYW1lUG9vbCB9IGZyb20gJy4vU2Vzc2lvbk5hbWVzLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbi8qKiBNYXhpbXVtIHBheWxvYWQgc2l6ZSBmb3IgaW5nZXN0aW9uIHJlcXVlc3RzICovXG5jb25zdCBNQVhfUEFZTE9BRF9TSVpFID0gJzFtYic7XG5cbi8qKiBSYXRlIGxpbWl0OiBtYXggcmVxdWVzdHMgcGVyIHdpbmRvdyBwZXIgc291cmNlICovXG5jb25zdCBSQVRFX0xJTUlUX01BWCA9IDEwMDA7XG5jb25zdCBSQVRFX0xJTUlUX1dJTkRPV19NUyA9IDYwXzAwMDtcblxuLyoqIEhvdyBvZnRlbiB0byBjaGVjayBmb3Igc3RhbGUgc2Vzc2lvbnMgKG1zKSAqL1xuY29uc3QgUkVBUEVSX0lOVEVSVkFMX01TID0gNV8wMDA7XG5cbi8qKiBIb3cgbG9uZyBzaW5jZSBsYXN0IGhlYXJ0YmVhdCBiZWZvcmUgYSBzZXNzaW9uIGlzIGNvbnNpZGVyZWQgZGVhZCAobXMpICovXG5jb25zdCBTRVNTSU9OX1NUQUxFX01TID0gMTVfMDAwO1xuXG4vKipcbiAqIFRyYWNrZWQgc2Vzc2lvbiBpbmZvcm1hdGlvbi5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZXNzaW9uSW5mbyB7XG4gIHNlc3Npb25JZDogc3RyaW5nO1xuICAvKiogRnJpZW5kbHkgcHVwcGV0IG5hbWUgKGUuZy4sIFwiS2VybWl0XCIsIFwiUHVuY2hcIikgKi9cbiAgZGlzcGxheU5hbWU6IHN0cmluZztcbiAgLyoqIENhbm9uaWNhbCBoZXggY29sb3IgZm9yIHRoaXMgcHVwcGV0IGNoYXJhY3RlciAqL1xuICBjb2xvcjogc3RyaW5nO1xuICBwaWQ6IG51bWJlcjtcbiAgc3RhcnRlZEF0OiBzdHJpbmc7XG4gIGxhc3RIZWFydGJlYXQ6IHN0cmluZztcbiAgc3RhdHVzOiAnYWN0aXZlJyB8ICdlbmRlZCc7XG4gIGlzTGVhZGVyOiBib29sZWFuO1xufVxuXG4vKipcbiAqIFBheWxvYWQgZm9yIFBPU1QgL2FwaS9pbmdlc3QvbG9nc1xuICovXG5leHBvcnQgaW50ZXJmYWNlIEluZ2VzdExvZ1BheWxvYWQge1xuICBzZXNzaW9uSWQ6IHN0cmluZztcbiAgZW50cmllczogVW5pZmllZExvZ0VudHJ5W107XG59XG5cbi8qKlxuICogUGF5bG9hZCBmb3IgUE9TVCAvYXBpL2luZ2VzdC9tZXRyaWNzXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSW5nZXN0TWV0cmljc1BheWxvYWQge1xuICBzZXNzaW9uSWQ6IHN0cmluZztcbiAgc25hcHNob3Q6IE1ldHJpY1NuYXBzaG90O1xufVxuXG4vKipcbiAqIFBheWxvYWQgZm9yIFBPU1QgL2FwaS9pbmdlc3Qvc2Vzc2lvblxuICovXG5leHBvcnQgaW50ZXJmYWNlIFNlc3Npb25FdmVudFBheWxvYWQge1xuICBzZXNzaW9uSWQ6IHN0cmluZztcbiAgZXZlbnQ6ICdzdGFydGVkJyB8ICdzdG9wcGVkJyB8ICdoZWFydGJlYXQnO1xuICBwaWQ6IG51bWJlcjtcbiAgc3RhcnRlZEF0OiBzdHJpbmc7XG59XG5cbi8qKlxuICogQ2FsbGJhY2tzIHByb3ZpZGVkIGJ5IHRoZSB1bmlmaWVkIGNvbnNvbGUgb3JjaGVzdHJhdG9yIGZvciBicm9hZGNhc3RpbmdcbiAqIGluZ2VzdGVkIGV2ZW50cyB0aHJvdWdoIHRoZSBleGlzdGluZyBTU0UgaW5mcmFzdHJ1Y3R1cmUuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSW5nZXN0QnJvYWRjYXN0cyB7XG4gIGxvZ0Jyb2FkY2FzdDogKGVudHJ5OiBVbmlmaWVkTG9nRW50cnkpID0+IHZvaWQ7XG4gIG1ldHJpY3NPblNuYXBzaG90PzogKHNuYXBzaG90OiBNZXRyaWNTbmFwc2hvdCkgPT4gdm9pZDtcbiAgc2Vzc2lvbkJyb2FkY2FzdD86IChldmVudDogU2Vzc2lvbkluZm8pID0+IHZvaWQ7XG59XG5cbi8qKlxuICogUmVzdWx0IG9mIGNyZWF0aW5nIGluZ2VzdCByb3V0ZXMuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSW5nZXN0Um91dGVzUmVzdWx0IHtcbiAgcm91dGVyOiBSb3V0ZXI7XG4gIC8qKiBHZXQgYWxsIHRyYWNrZWQgc2Vzc2lvbnMgKi9cbiAgZ2V0U2Vzc2lvbnM6ICgpID0+IFNlc3Npb25JbmZvW107XG4gIC8qKiBSZWdpc3RlciB0aGUgbGVhZGVyIGFzIGEgc2Vzc2lvbiAqL1xuICByZWdpc3RlckxlYWRlclNlc3Npb246IChzZXNzaW9uSWQ6IHN0cmluZywgcGlkOiBudW1iZXIpID0+IHZvaWQ7XG59XG5cbi8qKiBOb3JtYWxpemUgYSBzdHJpbmcgdmlhIFVuaWNvZGVWYWxpZGF0b3IgKERNQ1AtU0VDLTAwNCkgKi9cbmZ1bmN0aW9uIG5vcm1hbGl6ZUlucHV0KHM6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBVbmljb2RlVmFsaWRhdG9yLm5vcm1hbGl6ZShzKS5ub3JtYWxpemVkQ29udGVudDtcbn1cblxuLyoqXG4gKiBDcmVhdGUgdGhlIGluZ2VzdGlvbiByb3V0ZXMgYW5kIHNlc3Npb24gcmVnaXN0cnkuXG4gKlxuICogQHBhcmFtIGJyb2FkY2FzdHMgLSBDYWxsYmFja3MgdG8gZm9yd2FyZCBpbmdlc3RlZCBldmVudHMgdG8gU1NFIGNsaWVudHNcbiAqIEByZXR1cm5zIFJvdXRlciBhbmQgc2Vzc2lvbiBtYW5hZ2VtZW50IGZ1bmN0aW9uc1xuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlSW5nZXN0Um91dGVzKGJyb2FkY2FzdHM6IEluZ2VzdEJyb2FkY2FzdHMpOiBJbmdlc3RSb3V0ZXNSZXN1bHQge1xuICBjb25zdCByb3V0ZXIgPSBSb3V0ZXIoKTtcbiAgY29uc3Qgc2Vzc2lvbnMgPSBuZXcgTWFwPHN0cmluZywgU2Vzc2lvbkluZm8+KCk7XG4gIGNvbnN0IG5hbWVQb29sID0gbmV3IFNlc3Npb25OYW1lUG9vbCgpO1xuICBjb25zdCByYXRlTGltaXRlciA9IG5ldyBTbGlkaW5nV2luZG93UmF0ZUxpbWl0ZXIoUkFURV9MSU1JVF9NQVgsIFJBVEVfTElNSVRfV0lORE9XX01TKTtcblxuICAvLyBKU09OIGJvZHkgcGFyc2luZyB3aXRoIHNpemUgbGltaXRcbiAgcm91dGVyLnVzZShleHByZXNzLmpzb24oeyBsaW1pdDogTUFYX1BBWUxPQURfU0laRSB9KSk7XG5cbiAgLyoqXG4gICAqIFBPU1QgL2FwaS9pbmdlc3QvbG9ncyDigJQgUmVjZWl2ZSBiYXRjaGVkIGxvZyBlbnRyaWVzIGZyb20gYSBmb2xsb3dlci5cbiAgICovXG4gIHJvdXRlci5wb3N0KCcvYXBpL2luZ2VzdC9sb2dzJywgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIGlmICghcmF0ZUxpbWl0ZXIudHJ5QWNxdWlyZSgpKSB7XG4gICAgICByZXMuc3RhdHVzKDQyOSkuanNvbih7IGVycm9yOiAnUmF0ZSBsaW1pdCBleGNlZWRlZCcgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgcGF5bG9hZCA9IHJlcS5ib2R5IGFzIEluZ2VzdExvZ1BheWxvYWQ7XG4gICAgaWYgKCFwYXlsb2FkPy5zZXNzaW9uSWQgfHwgIUFycmF5LmlzQXJyYXkocGF5bG9hZC5lbnRyaWVzKSkge1xuICAgICAgY29uc3QgcmVjZWl2ZWQgPSBwYXlsb2FkID8gT2JqZWN0LmtleXMocGF5bG9hZCkgOiBbXTtcbiAgICAgIGxvZ2dlci53YXJuKCdbSW5nZXN0Um91dGVzXSBJbnZhbGlkIGxvZyBwYXlsb2FkJywgeyByZWNlaXZlZCwgaGFzU2Vzc2lvbklkOiAhIXBheWxvYWQ/LnNlc3Npb25JZCwgaGFzRW50cmllczogQXJyYXkuaXNBcnJheShwYXlsb2FkPy5lbnRyaWVzKSB9KTtcbiAgICAgIHJlcy5zdGF0dXMoNDAwKS5qc29uKHsgZXJyb3I6ICdJbnZhbGlkIHBheWxvYWQnLCByZXF1aXJlZDogWydzZXNzaW9uSWQnLCAnZW50cmllcyddLCByZWNlaXZlZCB9KTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgcGF5bG9hZC5zZXNzaW9uSWQgPSBub3JtYWxpemVJbnB1dChwYXlsb2FkLnNlc3Npb25JZCk7XG5cbiAgICBsZXQgY291bnQgPSAwO1xuICAgIGxldCBza2lwcGVkID0gMDtcbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIHBheWxvYWQuZW50cmllcykge1xuICAgICAgaWYgKCFlbnRyeSB8fCB0eXBlb2YgZW50cnkubWVzc2FnZSAhPT0gJ3N0cmluZycpIHsgc2tpcHBlZCsrOyBjb250aW51ZTsgfVxuICAgICAgY29uc3Qgc3RhbXBlZDogVW5pZmllZExvZ0VudHJ5ID0ge1xuICAgICAgICAuLi5lbnRyeSxcbiAgICAgICAgZGF0YTogeyAuLi5lbnRyeS5kYXRhLCBfc2Vzc2lvbklkOiBwYXlsb2FkLnNlc3Npb25JZCB9LFxuICAgICAgfTtcbiAgICAgIGJyb2FkY2FzdHMubG9nQnJvYWRjYXN0KHN0YW1wZWQpO1xuICAgICAgY291bnQrKztcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgc2Vzc2lvbiBoZWFydGJlYXRcbiAgICBjb25zdCBzZXNzaW9uID0gc2Vzc2lvbnMuZ2V0KHBheWxvYWQuc2Vzc2lvbklkKTtcbiAgICBpZiAoc2Vzc2lvbikge1xuICAgICAgc2Vzc2lvbi5sYXN0SGVhcnRiZWF0ID0gbmV3IERhdGUoKS50b0lTT1N0cmluZygpO1xuICAgIH1cblxuICAgIGlmIChza2lwcGVkID4gMCkge1xuICAgICAgbG9nZ2VyLmRlYnVnKGBbSW5nZXN0Um91dGVzXSBMb2cgaW5nZXN0IGZyb20gJHtzZXNzaW9uPy5kaXNwbGF5TmFtZSA/PyBwYXlsb2FkLnNlc3Npb25JZH06IGFjY2VwdGVkPSR7Y291bnR9LCBza2lwcGVkPSR7c2tpcHBlZH1gKTtcbiAgICB9XG5cbiAgICByZXMuc3RhdHVzKDIwMCkuanNvbih7IGFjY2VwdGVkOiBjb3VudCwgc2tpcHBlZCB9KTtcbiAgfSk7XG5cbiAgLyoqXG4gICAqIFBPU1QgL2FwaS9pbmdlc3QvbWV0cmljcyDigJQgUmVjZWl2ZSBtZXRyaWMgc25hcHNob3RzIGZyb20gYSBmb2xsb3dlci5cbiAgICovXG4gIHJvdXRlci5wb3N0KCcvYXBpL2luZ2VzdC9tZXRyaWNzJywgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIGlmICghcmF0ZUxpbWl0ZXIudHJ5QWNxdWlyZSgpKSB7XG4gICAgICByZXMuc3RhdHVzKDQyOSkuanNvbih7IGVycm9yOiAnUmF0ZSBsaW1pdCBleGNlZWRlZCcgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgcGF5bG9hZCA9IHJlcS5ib2R5IGFzIEluZ2VzdE1ldHJpY3NQYXlsb2FkO1xuICAgIGlmICghcGF5bG9hZD8uc2Vzc2lvbklkIHx8ICFwYXlsb2FkLnNuYXBzaG90KSB7XG4gICAgICBjb25zdCByZWNlaXZlZCA9IHBheWxvYWQgPyBPYmplY3Qua2V5cyhwYXlsb2FkKSA6IFtdO1xuICAgICAgbG9nZ2VyLndhcm4oJ1tJbmdlc3RSb3V0ZXNdIEludmFsaWQgbWV0cmljcyBwYXlsb2FkJywgeyByZWNlaXZlZCB9KTtcbiAgICAgIHJlcy5zdGF0dXMoNDAwKS5qc29uKHsgZXJyb3I6ICdJbnZhbGlkIHBheWxvYWQnLCByZXF1aXJlZDogWydzZXNzaW9uSWQnLCAnc25hcHNob3QnXSwgcmVjZWl2ZWQgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHBheWxvYWQuc2Vzc2lvbklkID0gbm9ybWFsaXplSW5wdXQocGF5bG9hZC5zZXNzaW9uSWQpO1xuXG4gICAgaWYgKGJyb2FkY2FzdHMubWV0cmljc09uU25hcHNob3QpIHtcbiAgICAgIGJyb2FkY2FzdHMubWV0cmljc09uU25hcHNob3QocGF5bG9hZC5zbmFwc2hvdCk7XG4gICAgfVxuXG4gICAgY29uc3Qgc2Vzc2lvbiA9IHNlc3Npb25zLmdldChwYXlsb2FkLnNlc3Npb25JZCk7XG4gICAgbG9nZ2VyLmRlYnVnKGBbSW5nZXN0Um91dGVzXSBNZXRyaWNzIGluZ2VzdGVkIGZyb20gJHtzZXNzaW9uPy5kaXNwbGF5TmFtZSA/PyBwYXlsb2FkLnNlc3Npb25JZH1gKTtcbiAgICByZXMuc3RhdHVzKDIwMCkuanNvbih7IGFjY2VwdGVkOiB0cnVlIH0pO1xuICB9KTtcblxuICAvKipcbiAgICogUE9TVCAvYXBpL2luZ2VzdC9zZXNzaW9uIOKAlCBTZXNzaW9uIGxpZmVjeWNsZSBldmVudHMuXG4gICAqL1xuICByb3V0ZXIucG9zdCgnL2FwaS9pbmdlc3Qvc2Vzc2lvbicsIChyZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UpID0+IHtcbiAgICBjb25zdCBwYXlsb2FkID0gcmVxLmJvZHkgYXMgU2Vzc2lvbkV2ZW50UGF5bG9hZDtcbiAgICBpZiAoIXBheWxvYWQ/LnNlc3Npb25JZCB8fCAhcGF5bG9hZC5ldmVudCkge1xuICAgICAgY29uc3QgcmVjZWl2ZWQgPSBwYXlsb2FkID8gT2JqZWN0LmtleXMocGF5bG9hZCkgOiBbXTtcbiAgICAgIGxvZ2dlci53YXJuKCdbSW5nZXN0Um91dGVzXSBJbnZhbGlkIHNlc3Npb24gZXZlbnQgcGF5bG9hZCcsIHsgcmVjZWl2ZWQgfSk7XG4gICAgICByZXMuc3RhdHVzKDQwMCkuanNvbih7IGVycm9yOiAnSW52YWxpZCBwYXlsb2FkJywgcmVxdWlyZWQ6IFsnc2Vzc2lvbklkJywgJ2V2ZW50J10sIHJlY2VpdmVkIH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBwYXlsb2FkLnNlc3Npb25JZCA9IG5vcm1hbGl6ZUlucHV0KHBheWxvYWQuc2Vzc2lvbklkKTtcblxuICAgIGNvbnN0IG5vdyA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcblxuICAgIHN3aXRjaCAocGF5bG9hZC5ldmVudCkge1xuICAgICAgY2FzZSAnc3RhcnRlZCc6IHtcbiAgICAgICAgY29uc3QgZGlzcGxheU5hbWUgPSBuYW1lUG9vbC5hc3NpZ24ocGF5bG9hZC5zZXNzaW9uSWQpO1xuICAgICAgICBjb25zdCBjb2xvciA9IG5hbWVQb29sLmdldENvbG9yKHBheWxvYWQuc2Vzc2lvbklkKSA/PyAnIzNiODJmNic7XG4gICAgICAgIGNvbnN0IGluZm86IFNlc3Npb25JbmZvID0ge1xuICAgICAgICAgIHNlc3Npb25JZDogcGF5bG9hZC5zZXNzaW9uSWQsXG4gICAgICAgICAgZGlzcGxheU5hbWUsXG4gICAgICAgICAgY29sb3IsXG4gICAgICAgICAgcGlkOiBwYXlsb2FkLnBpZCxcbiAgICAgICAgICBzdGFydGVkQXQ6IHBheWxvYWQuc3RhcnRlZEF0IHx8IG5vdyxcbiAgICAgICAgICBsYXN0SGVhcnRiZWF0OiBub3csXG4gICAgICAgICAgc3RhdHVzOiAnYWN0aXZlJyxcbiAgICAgICAgICBpc0xlYWRlcjogZmFsc2UsXG4gICAgICAgIH07XG4gICAgICAgIHNlc3Npb25zLnNldChwYXlsb2FkLnNlc3Npb25JZCwgaW5mbyk7XG4gICAgICAgIGxvZ2dlci5pbmZvKCdbSW5nZXN0Um91dGVzXSBTZXNzaW9uIHJlZ2lzdGVyZWQnLCB7XG4gICAgICAgICAgZGlzcGxheU5hbWUsIHNlc3Npb25JZDogcGF5bG9hZC5zZXNzaW9uSWQsIHBpZDogcGF5bG9hZC5waWQsIGNvbG9yLFxuICAgICAgICAgIGFjdGl2ZVNlc3Npb25zOiBBcnJheS5mcm9tKHNlc3Npb25zLnZhbHVlcygpKS5maWx0ZXIocyA9PiBzLnN0YXR1cyA9PT0gJ2FjdGl2ZScpLmxlbmd0aCxcbiAgICAgICAgfSk7XG4gICAgICAgIGJyb2FkY2FzdHMuc2Vzc2lvbkJyb2FkY2FzdD8uKGluZm8pO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICAgIGNhc2UgJ3N0b3BwZWQnOiB7XG4gICAgICAgIGNvbnN0IGV4aXN0aW5nID0gc2Vzc2lvbnMuZ2V0KHBheWxvYWQuc2Vzc2lvbklkKTtcbiAgICAgICAgaWYgKGV4aXN0aW5nKSB7XG4gICAgICAgICAgZXhpc3Rpbmcuc3RhdHVzID0gJ2VuZGVkJztcbiAgICAgICAgICBleGlzdGluZy5sYXN0SGVhcnRiZWF0ID0gbm93O1xuICAgICAgICAgIG5hbWVQb29sLnJlbGVhc2UocGF5bG9hZC5zZXNzaW9uSWQpO1xuICAgICAgICAgIGxvZ2dlci5pbmZvKCdbSW5nZXN0Um91dGVzXSBTZXNzaW9uIHN0b3BwZWQnLCB7XG4gICAgICAgICAgICBkaXNwbGF5TmFtZTogZXhpc3RpbmcuZGlzcGxheU5hbWUsIHNlc3Npb25JZDogcGF5bG9hZC5zZXNzaW9uSWQsIHBpZDogZXhpc3RpbmcucGlkLFxuICAgICAgICAgICAgYWN0aXZlU2Vzc2lvbnM6IEFycmF5LmZyb20oc2Vzc2lvbnMudmFsdWVzKCkpLmZpbHRlcihzID0+IHMuc3RhdHVzID09PSAnYWN0aXZlJykubGVuZ3RoIC0gMSxcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBicm9hZGNhc3RzLnNlc3Npb25Ccm9hZGNhc3Q/LihleGlzdGluZyk7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBjYXNlICdoZWFydGJlYXQnOiB7XG4gICAgICAgIGNvbnN0IGV4aXN0aW5nID0gc2Vzc2lvbnMuZ2V0KHBheWxvYWQuc2Vzc2lvbklkKTtcbiAgICAgICAgaWYgKGV4aXN0aW5nKSB7XG4gICAgICAgICAgZXhpc3RpbmcubGFzdEhlYXJ0YmVhdCA9IG5vdztcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXMuc3RhdHVzKDIwMCkuanNvbih7IG9rOiB0cnVlIH0pO1xuICB9KTtcblxuICAvKipcbiAgICogR0VUIC9hcGkvc2Vzc2lvbnMg4oCUIExpc3QgYWxsIHRyYWNrZWQgc2Vzc2lvbnMuXG4gICAqL1xuICByb3V0ZXIuZ2V0KCcvYXBpL3Nlc3Npb25zJywgKF9yZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UpID0+IHtcbiAgICBjb25zdCBhbGxTZXNzaW9ucyA9IEFycmF5LmZyb20oc2Vzc2lvbnMudmFsdWVzKCkpO1xuICAgIHJlcy5qc29uKHsgc2Vzc2lvbnM6IGFsbFNlc3Npb25zIH0pO1xuICB9KTtcblxuICAvKipcbiAgICogUE9TVCAvYXBpL3Nlc3Npb25zLzpzZXNzaW9uSWQva2lsbCDigJQgVGVybWluYXRlIGEgc2Vzc2lvbidzIHNlcnZlciBwcm9jZXNzLlxuICAgKi9cbiAgcm91dGVyLnBvc3QoJy9hcGkvc2Vzc2lvbnMvOnNlc3Npb25JZC9raWxsJywgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIGNvbnN0IHNlc3Npb25JZCA9IHJlcS5wYXJhbXNbJ3Nlc3Npb25JZCddIGFzIHN0cmluZztcbiAgICBjb25zdCBzZXNzaW9uID0gc2Vzc2lvbnMuZ2V0KHNlc3Npb25JZCk7XG5cbiAgICBpZiAoIXNlc3Npb24pIHtcbiAgICAgIGxvZ2dlci53YXJuKCdbSW5nZXN0Um91dGVzXSBLaWxsIHJlcXVlc3RlZCBmb3IgdW5rbm93biBzZXNzaW9uJywgeyBzZXNzaW9uSWQgfSk7XG4gICAgICByZXMuc3RhdHVzKDQwNCkuanNvbih7IGVycm9yOiAnU2Vzc2lvbiBub3QgZm91bmQnLCBzZXNzaW9uSWQgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKCFzZXNzaW9uLnBpZCkge1xuICAgICAgcmVzLnN0YXR1cyg0MDApLmpzb24oeyBlcnJvcjogJ05vIFBJRCBmb3Igc2Vzc2lvbicsIHNlc3Npb25JZCwgZGlzcGxheU5hbWU6IHNlc3Npb24uZGlzcGxheU5hbWUgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIHByb2Nlc3Mua2lsbChzZXNzaW9uLnBpZCwgJ1NJR1RFUk0nKTtcbiAgICAgIHNlc3Npb24uc3RhdHVzID0gJ2VuZGVkJztcbiAgICAgIG5hbWVQb29sLnJlbGVhc2Uoc2Vzc2lvbklkKTtcbiAgICAgIGxvZ2dlci5pbmZvKCdbSW5nZXN0Um91dGVzXSBTZXNzaW9uIGtpbGxlZCcsIHtcbiAgICAgICAgZGlzcGxheU5hbWU6IHNlc3Npb24uZGlzcGxheU5hbWUsIHNlc3Npb25JZCwgcGlkOiBzZXNzaW9uLnBpZCxcbiAgICAgICAgYWN0aXZlU2Vzc2lvbnM6IEFycmF5LmZyb20oc2Vzc2lvbnMudmFsdWVzKCkpLmZpbHRlcihzID0+IHMuc3RhdHVzID09PSAnYWN0aXZlJykubGVuZ3RoIC0gMSxcbiAgICAgIH0pO1xuICAgICAgcmVzLmpzb24oeyBvazogdHJ1ZSwga2lsbGVkOiBzZXNzaW9uLmRpc3BsYXlOYW1lLCBwaWQ6IHNlc3Npb24ucGlkIH0pO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgY29uc3QgbWVzc2FnZSA9IChlcnIgYXMgRXJyb3IpLm1lc3NhZ2U7XG4gICAgICBsb2dnZXIuZXJyb3IoJ1tJbmdlc3RSb3V0ZXNdIEZhaWxlZCB0byBraWxsIHNlc3Npb24nLCB7XG4gICAgICAgIGRpc3BsYXlOYW1lOiBzZXNzaW9uLmRpc3BsYXlOYW1lLCBzZXNzaW9uSWQsIHBpZDogc2Vzc2lvbi5waWQsIGVycm9yOiBtZXNzYWdlLFxuICAgICAgfSk7XG4gICAgICByZXMuc3RhdHVzKDUwMCkuanNvbih7IGVycm9yOiAnRmFpbGVkIHRvIGtpbGwgc2Vzc2lvbicsIHNlc3Npb25JZCwgZGlzcGxheU5hbWU6IHNlc3Npb24uZGlzcGxheU5hbWUsIHBpZDogc2Vzc2lvbi5waWQsIGRldGFpbDogbWVzc2FnZSB9KTtcbiAgICB9XG4gIH0pO1xuXG4gIC8vIFJlYXBlcjogcGVyaW9kaWNhbGx5IGNoZWNrIGZvciBzdGFsZSBzZXNzaW9ucyB3aG9zZSBoZWFydGJlYXQgaGFzIGV4cGlyZWRcbiAgY29uc3QgcmVhcGVySW50ZXJ2YWwgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgICBmb3IgKGNvbnN0IFtpZCwgc2Vzc2lvbl0gb2Ygc2Vzc2lvbnMpIHtcbiAgICAgIGlmIChzZXNzaW9uLnN0YXR1cyAhPT0gJ2FjdGl2ZScpIGNvbnRpbnVlO1xuICAgICAgaWYgKHNlc3Npb24uaXNMZWFkZXIpIGNvbnRpbnVlOyAvLyBsZWFkZXIgbWFuYWdlcyBpdHNlbGZcbiAgICAgIGNvbnN0IGFnZSA9IG5vdyAtIG5ldyBEYXRlKHNlc3Npb24ubGFzdEhlYXJ0YmVhdCkuZ2V0VGltZSgpO1xuICAgICAgaWYgKGFnZSA+IFNFU1NJT05fU1RBTEVfTVMpIHtcbiAgICAgICAgc2Vzc2lvbi5zdGF0dXMgPSAnZW5kZWQnO1xuICAgICAgICBuYW1lUG9vbC5yZWxlYXNlKGlkKTtcbiAgICAgICAgbG9nZ2VyLmluZm8oJ1tJbmdlc3RSb3V0ZXNdIFJlYXBlZCBzdGFsZSBzZXNzaW9uJywge1xuICAgICAgICAgIGRpc3BsYXlOYW1lOiBzZXNzaW9uLmRpc3BsYXlOYW1lLCBzZXNzaW9uSWQ6IGlkLCBwaWQ6IHNlc3Npb24ucGlkLFxuICAgICAgICAgIGxhc3RIZWFydGJlYXRBZ286IGAke01hdGgucm91bmQoYWdlIC8gMTAwMCl9c2AsXG4gICAgICAgICAgYWN0aXZlU2Vzc2lvbnM6IEFycmF5LmZyb20oc2Vzc2lvbnMudmFsdWVzKCkpLmZpbHRlcihzID0+IHMuc3RhdHVzID09PSAnYWN0aXZlJykubGVuZ3RoIC0gMSxcbiAgICAgICAgfSk7XG4gICAgICAgIGJyb2FkY2FzdHMuc2Vzc2lvbkJyb2FkY2FzdD8uKHNlc3Npb24pO1xuICAgICAgfVxuICAgIH1cbiAgfSwgUkVBUEVSX0lOVEVSVkFMX01TKTtcbiAgcmVhcGVySW50ZXJ2YWwudW5yZWYoKTtcblxuICBmdW5jdGlvbiBnZXRTZXNzaW9ucygpOiBTZXNzaW9uSW5mb1tdIHtcbiAgICByZXR1cm4gQXJyYXkuZnJvbShzZXNzaW9ucy52YWx1ZXMoKSkuZmlsdGVyKHMgPT4gcy5zdGF0dXMgPT09ICdhY3RpdmUnKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHJlZ2lzdGVyTGVhZGVyU2Vzc2lvbihzZXNzaW9uSWQ6IHN0cmluZywgcGlkOiBudW1iZXIpOiB2b2lkIHtcbiAgICBjb25zdCBkaXNwbGF5TmFtZSA9IG5hbWVQb29sLmFzc2lnbihzZXNzaW9uSWQsIHRydWUpO1xuICAgIGNvbnN0IGNvbG9yID0gbmFtZVBvb2wuZ2V0Q29sb3Ioc2Vzc2lvbklkKSA/PyAnIzNiODJmNic7XG4gICAgc2Vzc2lvbnMuc2V0KHNlc3Npb25JZCwge1xuICAgICAgc2Vzc2lvbklkLFxuICAgICAgZGlzcGxheU5hbWUsXG4gICAgICBjb2xvcixcbiAgICAgIHBpZCxcbiAgICAgIHN0YXJ0ZWRBdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgbGFzdEhlYXJ0YmVhdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgc3RhdHVzOiAnYWN0aXZlJyxcbiAgICAgIGlzTGVhZGVyOiB0cnVlLFxuICAgIH0pO1xuICAgIGxvZ2dlci5pbmZvKCdbSW5nZXN0Um91dGVzXSBMZWFkZXIgc2Vzc2lvbiByZWdpc3RlcmVkJywgeyBkaXNwbGF5TmFtZSwgc2Vzc2lvbklkLCBwaWQsIGNvbG9yIH0pO1xuICB9XG5cbiAgcmV0dXJuIHsgcm91dGVyLCBnZXRTZXNzaW9ucywgcmVnaXN0ZXJMZWFkZXJTZXNzaW9uIH07XG59XG4iXX0=
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Leader election for the unified web console.
3
+ *
4
+ * When multiple MCP server instances run concurrently, only one should host
5
+ * the web console (the "leader"). Others become "followers" that forward
6
+ * events to the leader. This module handles:
7
+ *
8
+ * 1. Reading/writing a leader lock file at ~/.dollhouse/run/console-leader.lock
9
+ * 2. Atomic claim via temp+rename to prevent TOCTOU races
10
+ * 3. PID-based stale detection (signal-0 liveness check)
11
+ * 4. Heartbeat updates (10s interval) so followers can detect hung leaders
12
+ * 5. Cleanup on process exit
13
+ *
14
+ * The port 3939 binding is the ultimate tiebreaker: even if two processes
15
+ * both write the lock file, only one can bind the port.
16
+ *
17
+ * @since v2.1.0 — Issue #1700
18
+ */
19
+ /**
20
+ * Information stored in the leader lock file.
21
+ */
22
+ export interface ConsoleLeaderInfo {
23
+ version: number;
24
+ pid: number;
25
+ port: number;
26
+ sessionId: string;
27
+ startedAt: string;
28
+ heartbeat: string;
29
+ }
30
+ /**
31
+ * Result of a leader election attempt.
32
+ */
33
+ export interface ElectionResult {
34
+ role: 'leader' | 'follower';
35
+ /** Leader info — for followers, this is the existing leader's info */
36
+ leaderInfo: ConsoleLeaderInfo;
37
+ }
38
+ /**
39
+ * Check whether a process with the given PID is alive.
40
+ * Uses signal 0 which checks existence without sending a signal.
41
+ */
42
+ export declare function isProcessAlive(pid: number): boolean;
43
+ /**
44
+ * Read and parse the leader lock file.
45
+ * Returns null if the file doesn't exist, is unreadable, or has invalid content.
46
+ */
47
+ export declare function readLeaderLock(): Promise<ConsoleLeaderInfo | null>;
48
+ /**
49
+ * Check if a leader lock is stale (dead process or expired heartbeat).
50
+ */
51
+ export declare function isLockStale(info: ConsoleLeaderInfo): boolean;
52
+ /**
53
+ * Attempt to atomically claim leadership.
54
+ *
55
+ * Writes to a temp file then renames to the lock path. On POSIX systems
56
+ * rename is atomic, so only one writer wins. After renaming, re-reads the
57
+ * lock to verify our PID won.
58
+ *
59
+ * @returns true if this process successfully claimed leadership
60
+ */
61
+ export declare function claimLeadership(info: ConsoleLeaderInfo): Promise<boolean>;
62
+ /**
63
+ * Delete the leader lock file (for cleanup or takeover).
64
+ */
65
+ export declare function deleteLeaderLock(): Promise<void>;
66
+ /**
67
+ * Run the leader election protocol.
68
+ *
69
+ * 1. If no lock exists or lock is stale → claim leadership
70
+ * 2. If lock exists with a live, responsive leader → become follower
71
+ *
72
+ * @param sessionId - This process's unique session identifier
73
+ * @param port - The port this process would use as leader (typically 3939)
74
+ * @returns Election result with role and leader info
75
+ */
76
+ export declare function electLeader(sessionId: string, port: number): Promise<ElectionResult>;
77
+ /**
78
+ * Start the leader heartbeat loop.
79
+ * Updates the lock file every HEARTBEAT_INTERVAL_MS so followers know the leader is alive.
80
+ *
81
+ * @returns A stop function to clear the interval
82
+ */
83
+ export declare function startHeartbeat(info: ConsoleLeaderInfo): () => void;
84
+ /**
85
+ * Register cleanup handlers to remove the leader lock on process exit.
86
+ * Should only be called by the leader.
87
+ */
88
+ export declare function registerLeaderCleanup(): void;
89
+ //# sourceMappingURL=LeaderElection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeaderElection.d.ts","sourceRoot":"","sources":["../../../src/web/console/LeaderElection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAuBH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,sEAAsE;IACtE,UAAU,EAAE,iBAAiB,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAWxE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAM5D;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAe/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEtD;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAyD1F;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAgBlE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Leader election for the unified web console.
3
+ *
4
+ * When multiple MCP server instances run concurrently, only one should host
5
+ * the web console (the "leader"). Others become "followers" that forward
6
+ * events to the leader. This module handles:
7
+ *
8
+ * 1. Reading/writing a leader lock file at ~/.dollhouse/run/console-leader.lock
9
+ * 2. Atomic claim via temp+rename to prevent TOCTOU races
10
+ * 3. PID-based stale detection (signal-0 liveness check)
11
+ * 4. Heartbeat updates (10s interval) so followers can detect hung leaders
12
+ * 5. Cleanup on process exit
13
+ *
14
+ * The port 3939 binding is the ultimate tiebreaker: even if two processes
15
+ * both write the lock file, only one can bind the port.
16
+ *
17
+ * @since v2.1.0 — Issue #1700
18
+ */
19
+ import { homedir } from 'node:os';
20
+ import { join } from 'node:path';
21
+ import { mkdir, readFile, writeFile, rename, unlink } from 'node:fs/promises';
22
+ import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
23
+ import { logger } from '../../utils/logger.js';
24
+ /** Directory for runtime state files */
25
+ const RUN_DIR = join(homedir(), '.dollhouse', 'run');
26
+ /** Path to the leader lock file */
27
+ const LOCK_FILE = join(RUN_DIR, 'console-leader.lock');
28
+ /** How often the leader updates its heartbeat (ms) */
29
+ const HEARTBEAT_INTERVAL_MS = 10_000;
30
+ /** How long before a heartbeat is considered stale (ms) */
31
+ const HEARTBEAT_STALE_MS = 30_000;
32
+ /** Current lock file schema version */
33
+ const LOCK_VERSION = 1;
34
+ /**
35
+ * Check whether a process with the given PID is alive.
36
+ * Uses signal 0 which checks existence without sending a signal.
37
+ */
38
+ export function isProcessAlive(pid) {
39
+ try {
40
+ process.kill(pid, 0);
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ /**
48
+ * Read and parse the leader lock file.
49
+ * Returns null if the file doesn't exist, is unreadable, or has invalid content.
50
+ */
51
+ export async function readLeaderLock() {
52
+ try {
53
+ const content = await readFile(LOCK_FILE, 'utf-8');
54
+ const data = JSON.parse(content);
55
+ if (data.version !== LOCK_VERSION || !data.pid || !data.port || !data.sessionId) {
56
+ return null;
57
+ }
58
+ return data;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /**
65
+ * Check if a leader lock is stale (dead process or expired heartbeat).
66
+ */
67
+ export function isLockStale(info) {
68
+ if (!isProcessAlive(info.pid)) {
69
+ return true;
70
+ }
71
+ const heartbeatAge = Date.now() - new Date(info.heartbeat).getTime();
72
+ return heartbeatAge > HEARTBEAT_STALE_MS;
73
+ }
74
+ /**
75
+ * Attempt to atomically claim leadership.
76
+ *
77
+ * Writes to a temp file then renames to the lock path. On POSIX systems
78
+ * rename is atomic, so only one writer wins. After renaming, re-reads the
79
+ * lock to verify our PID won.
80
+ *
81
+ * @returns true if this process successfully claimed leadership
82
+ */
83
+ export async function claimLeadership(info) {
84
+ await mkdir(RUN_DIR, { recursive: true });
85
+ const tmpFile = join(RUN_DIR, `console-leader.lock.${process.pid}.tmp`);
86
+ try {
87
+ await writeFile(tmpFile, JSON.stringify(info, null, 2), 'utf-8');
88
+ await rename(tmpFile, LOCK_FILE);
89
+ // Verify we won the race
90
+ const written = await readLeaderLock();
91
+ return written !== null && written.pid === info.pid;
92
+ }
93
+ catch {
94
+ // Clean up temp file on failure
95
+ try {
96
+ await unlink(tmpFile);
97
+ }
98
+ catch { /* ignore */ }
99
+ return false;
100
+ }
101
+ }
102
+ /**
103
+ * Delete the leader lock file (for cleanup or takeover).
104
+ */
105
+ export async function deleteLeaderLock() {
106
+ try {
107
+ await unlink(LOCK_FILE);
108
+ }
109
+ catch { /* already gone */ }
110
+ }
111
+ /**
112
+ * Run the leader election protocol.
113
+ *
114
+ * 1. If no lock exists or lock is stale → claim leadership
115
+ * 2. If lock exists with a live, responsive leader → become follower
116
+ *
117
+ * @param sessionId - This process's unique session identifier
118
+ * @param port - The port this process would use as leader (typically 3939)
119
+ * @returns Election result with role and leader info
120
+ */
121
+ export async function electLeader(sessionId, port) {
122
+ sessionId = UnicodeValidator.normalize(sessionId).normalizedContent;
123
+ const existingLock = await readLeaderLock();
124
+ if (existingLock && !isLockStale(existingLock)) {
125
+ logger.info('[LeaderElection] Existing leader found — becoming follower', {
126
+ leaderSession: existingLock.sessionId, leaderPid: existingLock.pid,
127
+ leaderPort: existingLock.port, mySession: sessionId, myPid: process.pid,
128
+ });
129
+ return { role: 'follower', leaderInfo: existingLock };
130
+ }
131
+ // No valid leader — try to claim
132
+ if (existingLock) {
133
+ const alive = isProcessAlive(existingLock.pid);
134
+ const heartbeatAge = Date.now() - new Date(existingLock.heartbeat).getTime();
135
+ logger.info('[LeaderElection] Stale leader lock — taking over', {
136
+ stalePid: existingLock.pid, alive, heartbeatAgeMs: heartbeatAge,
137
+ staleSession: existingLock.sessionId, mySession: sessionId,
138
+ });
139
+ await deleteLeaderLock();
140
+ }
141
+ const now = new Date().toISOString();
142
+ const myInfo = {
143
+ version: LOCK_VERSION,
144
+ pid: process.pid,
145
+ port,
146
+ sessionId,
147
+ startedAt: now,
148
+ heartbeat: now,
149
+ };
150
+ const claimed = await claimLeadership(myInfo);
151
+ if (claimed) {
152
+ logger.info('[LeaderElection] Claimed leadership', { sessionId, port, pid: process.pid });
153
+ return { role: 'leader', leaderInfo: myInfo };
154
+ }
155
+ // Another process won the race — re-read and become follower
156
+ const winner = await readLeaderLock();
157
+ if (winner) {
158
+ logger.info('[LeaderElection] Lost election — becoming follower', {
159
+ winnerPid: winner.pid, winnerSession: winner.sessionId, mySession: sessionId, myPid: process.pid,
160
+ });
161
+ return { role: 'follower', leaderInfo: winner };
162
+ }
163
+ // Extremely unlikely: lock disappeared between our claim and re-read. Retry once.
164
+ logger.warn('[LeaderElection] Lock vanished after failed claim. Retrying.');
165
+ const retryInfo = { ...myInfo, heartbeat: new Date().toISOString() };
166
+ const retryClaimed = await claimLeadership(retryInfo);
167
+ if (retryClaimed) {
168
+ return { role: 'leader', leaderInfo: retryInfo };
169
+ }
170
+ const actualLeader = await readLeaderLock();
171
+ return { role: 'follower', leaderInfo: actualLeader ?? retryInfo };
172
+ }
173
+ /**
174
+ * Start the leader heartbeat loop.
175
+ * Updates the lock file every HEARTBEAT_INTERVAL_MS so followers know the leader is alive.
176
+ *
177
+ * @returns A stop function to clear the interval
178
+ */
179
+ export function startHeartbeat(info) {
180
+ const interval = setInterval(async () => {
181
+ try {
182
+ const updated = { ...info, heartbeat: new Date().toISOString() };
183
+ const tmpFile = join(RUN_DIR, `console-leader.lock.${process.pid}.tmp`);
184
+ await writeFile(tmpFile, JSON.stringify(updated, null, 2), 'utf-8');
185
+ await rename(tmpFile, LOCK_FILE);
186
+ }
187
+ catch (err) {
188
+ logger.debug('[LeaderElection] Heartbeat write failed:', err);
189
+ }
190
+ }, HEARTBEAT_INTERVAL_MS);
191
+ // Don't let the heartbeat interval keep the process alive
192
+ interval.unref();
193
+ return () => clearInterval(interval);
194
+ }
195
+ /**
196
+ * Register cleanup handlers to remove the leader lock on process exit.
197
+ * Should only be called by the leader.
198
+ */
199
+ export function registerLeaderCleanup() {
200
+ const cleanup = () => { deleteLeaderLock().catch(() => { }); };
201
+ process.once('exit', cleanup);
202
+ process.once('SIGTERM', cleanup);
203
+ process.once('SIGINT', cleanup);
204
+ }
205
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTGVhZGVyRWxlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvd2ViL2NvbnNvbGUvTGVhZGVyRWxlY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBRUgsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNsQyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2pDLE9BQU8sRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDOUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sK0NBQStDLENBQUM7QUFDakYsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRS9DLHdDQUF3QztBQUN4QyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsWUFBWSxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBRXJELG1DQUFtQztBQUNuQyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsT0FBTyxFQUFFLHFCQUFxQixDQUFDLENBQUM7QUFFdkQsc0RBQXNEO0FBQ3RELE1BQU0scUJBQXFCLEdBQUcsTUFBTSxDQUFDO0FBRXJDLDJEQUEyRDtBQUMzRCxNQUFNLGtCQUFrQixHQUFHLE1BQU0sQ0FBQztBQUVsQyx1Q0FBdUM7QUFDdkMsTUFBTSxZQUFZLEdBQUcsQ0FBQyxDQUFDO0FBdUJ2Qjs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLEdBQVc7SUFDeEMsSUFBSSxDQUFDO1FBQ0gsT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDckIsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0FBQ0gsQ0FB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QyxJQUFJLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ2xDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIExlYWRlciBlbGVjdGlvbiBmb3IgdGhlIHVuaWZpZWQgd2ViIGNvbnNvbGUuXG4gKlxuICogV2hlbiBtdWx0aXBsZSBNQ1Agc2VydmVyIGluc3RhbmNlcyBydW4gY29uY3VycmVudGx5LCBvbmx5IG9uZSBzaG91bGQgaG9zdFxuICogdGhlIHdlYiBjb25zb2xlICh0aGUgXCJsZWFkZXJcIikuIE90aGVycyBiZWNvbWUgXCJmb2xsb3dlcnNcIiB0aGF0IGZvcndhcmRcbiAqIGV2ZW50cyB0byB0aGUgbGVhZGVyLiBUaGlzIG1vZHVsZSBoYW5kbGVzOlxuICpcbiAqIDEuIFJlYWRpbmcvd3JpdGluZyBhIGxlYWRlciBsb2NrIGZpbGUgYXQgfi8uZG9sbGhvdXNlL3J1bi9jb25zb2xlLWxlYWRlci5sb2NrXG4gKiAyLiBBdG9taWMgY2xhaW0gdmlhIHRlbXArcmVuYW1lIHRvIHByZXZlbnQgVE9DVE9VIHJhY2VzXG4gKiAzLiBQSUQtYmFzZWQgc3RhbGUgZGV0ZWN0aW9uIChzaWduYWwtMCBsaXZlbmVzcyBjaGVjaylcbiAqIDQuIEhlYXJ0YmVhdCB1cGRhdGVzICgxMHMgaW50ZXJ2YWwpIHNvIGZvbGxvd2VycyBjYW4gZGV0ZWN0IGh1bmcgbGVhZGVyc1xuICogNS4gQ2xlYW51cCBvbiBwcm9jZXNzIGV4aXRcbiAqXG4gKiBUaGUgcG9ydCAzOTM5IGJpbmRpbmcgaXMgdGhlIHVsdGltYXRlIHRpZWJyZWFrZXI6IGV2ZW4gaWYgdHdvIHByb2Nlc3Nlc1xuICogYm90aCB3cml0ZSB0aGUgbG9jayBmaWxlLCBvbmx5IG9uZSBjYW4gYmluZCB0aGUgcG9ydC5cbiAqXG4gKiBAc2luY2UgdjIuMS4wIOKAlCBJc3N1ZSAjMTcwMFxuICovXG5cbmltcG9ydCB7IGhvbWVkaXIgfSBmcm9tICdub2RlOm9zJztcbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgbWtkaXIsIHJlYWRGaWxlLCB3cml0ZUZpbGUsIHJlbmFtZSwgdW5saW5rIH0gZnJvbSAnbm9kZTpmcy9wcm9taXNlcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbi8qKiBEaXJlY3RvcnkgZm9yIHJ1bnRpbWUgc3RhdGUgZmlsZXMgKi9cbmNvbnN0IFJVTl9ESVIgPSBqb2luKGhvbWVkaXIoKSwgJy5kb2xsaG91c2UnLCAncnVuJyk7XG5cbi8qKiBQYXRoIHRvIHRoZSBsZWFkZXIgbG9jayBmaWxlICovXG5jb25zdCBMT0NLX0ZJTEUgPSBqb2luKFJVTl9ESVIsICdjb25zb2xlLWxlYWRlci5sb2NrJyk7XG5cbi8qKiBIb3cgb2Z0ZW4gdGhlIGxlYWRlciB1cGRhdGVzIGl0cyBoZWFydGJlYXQgKG1zKSAqL1xuY29uc3QgSEVBUlRCRUFUX0lOVEVSVkFMX01TID0gMTBfMDAwO1xuXG4vKiogSG93IGxvbmcgYmVmb3JlIGEgaGVhcnRiZWF0IGlzIGNvbnNpZGVyZWQgc3RhbGUgKG1zKSAqL1xuY29uc3QgSEVBUlRCRUFUX1NUQUxFX01TID0gMzBfMDAwO1xuXG4vKiogQ3VycmVudCBsb2NrIGZpbGUgc2NoZW1hIHZlcnNpb24gKi9cbmNvbnN0IExPQ0tfVkVSU0lPTiA9IDE7XG5cbi8qKlxuICogSW5mb3JtYXRpb24gc3RvcmVkIGluIHRoZSBsZWFkZXIgbG9jayBmaWxlLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIENvbnNvbGVMZWFkZXJJbmZvIHtcbiAgdmVyc2lvbjogbnVtYmVyO1xuICBwaWQ6IG51bWJlcjtcbiAgcG9ydDogbnVtYmVyO1xuICBzZXNzaW9uSWQ6IHN0cmluZztcbiAgc3RhcnRlZEF0OiBzdHJpbmc7XG4gIGhlYXJ0YmVhdDogc3RyaW5nO1xufVxuXG4vKipcbiAqIFJlc3VsdCBvZiBhIGxlYWRlciBlbGVjdGlvbiBhdHRlbXB0LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEVsZWN0aW9uUmVzdWx0IHtcbiAgcm9sZTogJ2xlYWRlcicgfCAnZm9sbG93ZXInO1xuICAvKiogTGVhZGVyIGluZm8g4oCUIGZvciBmb2xsb3dlcnMsIHRoaXMgaXMgdGhlIGV4aXN0aW5nIGxlYWRlcidzIGluZm8gKi9cbiAgbGVhZGVySW5mbzogQ29uc29sZUxlYWRlckluZm87XG59XG5cbi8qKlxuICogQ2hlY2sgd2hldGhlciBhIHByb2Nlc3Mgd2l0aCB0aGUgZ2l2ZW4gUElEIGlzIGFsaXZlLlxuICogVXNlcyBzaWduYWwgMCB3aGljaCBjaGVja3MgZXhpc3RlbmNlIHdpdGhvdXQgc2VuZGluZyBhIHNpZ25hbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzUHJvY2Vzc0FsaXZlKHBpZDogbnVtYmVyKTogYm9vbGVhbiB7XG4gIHRyeSB7XG4gICAgcHJvY2Vzcy5raWxsKHBpZCwgMCk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxufVxuXG4vKipcbiAqIFJlYWQgYW5kIHBhcnNlIHRoZSBsZWFkZXIgbG9jayBmaWxlLlxuICogUmV0dXJucyBudWxsIGlmIHRoZSBmaWxlIGRvZXNuJ3QgZXhpc3QsIGlzIHVucmVhZGFibGUsIG9yIGhhcyBpbnZhbGlkIGNvbnRlbnQuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZWFkTGVhZGVyTG9jaygpOiBQcm9taXNlPENvbnNvbGVMZWFkZXJJbmZvIHwgbnVsbD4ge1xuICB0cnkge1xuICAgIGNvbnN0IGNvbnRlbnQgPSBhd2FpdCByZWFkRmlsZShMT0NLX0ZJTEUsICd1dGYtOCcpO1xuICAgIGNvbnN0IGRhdGEgPSBKU09OLnBhcnNlKGNvbnRlbnQpIGFzIENvbnNvbGVMZWFkZXJJbmZvO1xuICAgIGlmIChkYXRhLnZlcnNpb24gIT09IExPQ0tfVkVSU0lPTiB8fCAhZGF0YS5waWQgfHwgIWRhdGEucG9ydCB8fCAhZGF0YS5zZXNzaW9uSWQpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICByZXR1cm4gZGF0YTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cblxuLyoqXG4gKiBDaGVjayBpZiBhIGxlYWRlciBsb2NrIGlzIHN0YWxlIChkZWFkIHByb2Nlc3Mgb3IgZXhwaXJlZCBoZWFydGJlYXQpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaXNMb2NrU3RhbGUoaW5mbzogQ29uc29sZUxlYWRlckluZm8pOiBib29sZWFuIHtcbiAgaWYgKCFpc1Byb2Nlc3NBbGl2ZShpbmZvLnBpZCkpIHtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuICBjb25zdCBoZWFydGJlYXRBZ2UgPSBEYXRlLm5vdygpIC0gbmV3IERhdGUoaW5mby5oZWFydGJlYXQpLmdldFRpbWUoKTtcbiAgcmV0dXJuIGhlYXJ0YmVhdEFnZSA+IEhFQVJUQkVBVF9TVEFMRV9NUztcbn1cblxuLyoqXG4gKiBBdHRlbXB0IHRvIGF0b21pY2FsbHkgY2xhaW0gbGVhZGVyc2hpcC5cbiAqXG4gKiBXcml0ZXMgdG8gYSB0ZW1wIGZpbGUgdGhlbiByZW5hbWVzIHRvIHRoZSBsb2NrIHBhdGguIE9uIFBPU0lYIHN5c3RlbXNcbiAqIHJlbmFtZSBpcyBhdG9taWMsIHNvIG9ubHkgb25lIHdyaXRlciB3aW5zLiBBZnRlciByZW5hbWluZywgcmUtcmVhZHMgdGhlXG4gKiBsb2NrIHRvIHZlcmlmeSBvdXIgUElEIHdvbi5cbiAqXG4gKiBAcmV0dXJucyB0cnVlIGlmIHRoaXMgcHJvY2VzcyBzdWNjZXNzZnVsbHkgY2xhaW1lZCBsZWFkZXJzaGlwXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjbGFpbUxlYWRlcnNoaXAoaW5mbzogQ29uc29sZUxlYWRlckluZm8pOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgYXdhaXQgbWtkaXIoUlVOX0RJUiwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gIGNvbnN0IHRtcEZpbGUgPSBqb2luKFJVTl9ESVIsIGBjb25zb2xlLWxlYWRlci5sb2NrLiR7cHJvY2Vzcy5waWR9LnRtcGApO1xuICB0cnkge1xuICAgIGF3YWl0IHdyaXRlRmlsZSh0bXBGaWxlLCBKU09OLnN0cmluZ2lmeShpbmZvLCBudWxsLCAyKSwgJ3V0Zi04Jyk7XG4gICAgYXdhaXQgcmVuYW1lKHRtcEZpbGUsIExPQ0tfRklMRSk7XG5cbiAgICAvLyBWZXJpZnkgd2Ugd29uIHRoZSByYWNlXG4gICAgY29uc3Qgd3JpdHRlbiA9IGF3YWl0IHJlYWRMZWFkZXJMb2NrKCk7XG4gICAgcmV0dXJuIHdyaXR0ZW4gIT09IG51bGwgJiYgd3JpdHRlbi5waWQgPT09IGluZm8ucGlkO1xuICB9IGNhdGNoIHtcbiAgICAvLyBDbGVhbiB1cCB0ZW1wIGZpbGUgb24gZmFpbHVyZVxuICAgIHRyeSB7IGF3YWl0IHVubGluayh0bXBGaWxlKTsgfSBjYXRjaCB7IC8qIGlnbm9yZSAqLyB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbi8qKlxuICogRGVsZXRlIHRoZSBsZWFkZXIgbG9jayBmaWxlIChmb3IgY2xlYW51cCBvciB0YWtlb3ZlcikuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBkZWxldGVMZWFkZXJMb2NrKCk6IFByb21pc2U8dm9pZD4ge1xuICB0cnkgeyBhd2FpdCB1bmxpbmsoTE9DS19GSUxFKTsgfSBjYXRjaCB7IC8qIGFscmVhZHkgZ29uZSAqLyB9XG59XG5cbi8qKlxuICogUnVuIHRoZSBsZWFkZXIgZWxlY3Rpb24gcHJvdG9jb2wuXG4gKlxuICogMS4gSWYgbm8gbG9jayBleGlzdHMgb3IgbG9jayBpcyBzdGFsZSDihpIgY2xhaW0gbGVhZGVyc2hpcFxuICogMi4gSWYgbG9jayBleGlzdHMgd2l0aCBhIGxpdmUsIHJlc3BvbnNpdmUgbGVhZGVyIOKGkiBiZWNvbWUgZm9sbG93ZXJcbiAqXG4gKiBAcGFyYW0gc2Vzc2lvbklkIC0gVGhpcyBwcm9jZXNzJ3MgdW5pcXVlIHNlc3Npb24gaWRlbnRpZmllclxuICogQHBhcmFtIHBvcnQgLSBUaGUgcG9ydCB0aGlzIHByb2Nlc3Mgd291bGQgdXNlIGFzIGxlYWRlciAodHlwaWNhbGx5IDM5MzkpXG4gKiBAcmV0dXJucyBFbGVjdGlvbiByZXN1bHQgd2l0aCByb2xlIGFuZCBsZWFkZXIgaW5mb1xuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZWxlY3RMZWFkZXIoc2Vzc2lvbklkOiBzdHJpbmcsIHBvcnQ6IG51bWJlcik6IFByb21pc2U8RWxlY3Rpb25SZXN1bHQ+IHtcbiAgc2Vzc2lvbklkID0gVW5pY29kZVZhbGlkYXRvci5ub3JtYWxpemUoc2Vzc2lvbklkKS5ub3JtYWxpemVkQ29udGVudDtcbiAgY29uc3QgZXhpc3RpbmdMb2NrID0gYXdhaXQgcmVhZExlYWRlckxvY2soKTtcblxuICBpZiAoZXhpc3RpbmdMb2NrICYmICFpc0xvY2tTdGFsZShleGlzdGluZ0xvY2spKSB7XG4gICAgbG9nZ2VyLmluZm8oJ1tMZWFkZXJFbGVjdGlvbl0gRXhpc3RpbmcgbGVhZGVyIGZvdW5kIOKAlCBiZWNvbWluZyBmb2xsb3dlcicsIHtcbiAgICAgIGxlYWRlclNlc3Npb246IGV4aXN0aW5nTG9jay5zZXNzaW9uSWQsIGxlYWRlclBpZDogZXhpc3RpbmdMb2NrLnBpZCxcbiAgICAgIGxlYWRlclBvcnQ6IGV4aXN0aW5nTG9jay5wb3J0LCBteVNlc3Npb246IHNlc3Npb25JZCwgbXlQaWQ6IHByb2Nlc3MucGlkLFxuICAgIH0pO1xuICAgIHJldHVybiB7IHJvbGU6ICdmb2xsb3dlcicsIGxlYWRlckluZm86IGV4aXN0aW5nTG9jayB9O1xuICB9XG5cbiAgLy8gTm8gdmFsaWQgbGVhZGVyIOKAlCB0cnkgdG8gY2xhaW1cbiAgaWYgKGV4aXN0aW5nTG9jaykge1xuICAgIGNvbnN0IGFsaXZlID0gaXNQcm9jZXNzQWxpdmUoZXhpc3RpbmdMb2NrLnBpZCk7XG4gICAgY29uc3QgaGVhcnRiZWF0QWdlID0gRGF0ZS5ub3coKSAtIG5ldyBEYXRlKGV4aXN0aW5nTG9jay5oZWFydGJlYXQpLmdldFRpbWUoKTtcbiAgICBsb2dnZXIuaW5mbygnW0xlYWRlckVsZWN0aW9uXSBTdGFsZSBsZWFkZXIgbG9jayDigJQgdGFraW5nIG92ZXInLCB7XG4gICAgICBzdGFsZVBpZDogZXhpc3RpbmdMb2NrLnBpZCwgYWxpdmUsIGhlYXJ0YmVhdEFnZU1zOiBoZWFydGJlYXRBZ2UsXG4gICAgICBzdGFsZVNlc3Npb246IGV4aXN0aW5nTG9jay5zZXNzaW9uSWQsIG15U2Vzc2lvbjogc2Vzc2lvbklkLFxuICAgIH0pO1xuICAgIGF3YWl0IGRlbGV0ZUxlYWRlckxvY2soKTtcbiAgfVxuXG4gIGNvbnN0IG5vdyA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcbiAgY29uc3QgbXlJbmZvOiBDb25zb2xlTGVhZGVySW5mbyA9IHtcbiAgICB2ZXJzaW9uOiBMT0NLX1ZFUlNJT04sXG4gICAgcGlkOiBwcm9jZXNzLnBpZCxcbiAgICBwb3J0LFxuICAgIHNlc3Npb25JZCxcbiAgICBzdGFydGVkQXQ6IG5vdyxcbiAgICBoZWFydGJlYXQ6IG5vdyxcbiAgfTtcblxuICBjb25zdCBjbGFpbWVkID0gYXdhaXQgY2xhaW1MZWFkZXJzaGlwKG15SW5mbyk7XG4gIGlmIChjbGFpbWVkKSB7XG4gICAgbG9nZ2VyLmluZm8oJ1tMZWFkZXJFbGVjdGlvbl0gQ2xhaW1lZCBsZWFkZXJzaGlwJywgeyBzZXNzaW9uSWQsIHBvcnQsIHBpZDogcHJvY2Vzcy5waWQgfSk7XG4gICAgcmV0dXJuIHsgcm9sZTogJ2xlYWRlcicsIGxlYWRlckluZm86IG15SW5mbyB9O1xuICB9XG5cbiAgLy8gQW5vdGhlciBwcm9jZXNzIHdvbiB0aGUgcmFjZSDigJQgcmUtcmVhZCBhbmQgYmVjb21lIGZvbGxvd2VyXG4gIGNvbnN0IHdpbm5lciA9IGF3YWl0IHJlYWRMZWFkZXJMb2NrKCk7XG4gIGlmICh3aW5uZXIpIHtcbiAgICBsb2dnZXIuaW5mbygnW0xlYWRlckVsZWN0aW9uXSBMb3N0IGVsZWN0aW9uIOKAlCBiZWNvbWluZyBmb2xsb3dlcicsIHtcbiAgICAgIHdpbm5lclBpZDogd2lubmVyLnBpZCwgd2lubmVyU2Vzc2lvbjogd2lubmVyLnNlc3Npb25JZCwgbXlTZXNzaW9uOiBzZXNzaW9uSWQsIG15UGlkOiBwcm9jZXNzLnBpZCxcbiAgICB9KTtcbiAgICByZXR1cm4geyByb2xlOiAnZm9sbG93ZXInLCBsZWFkZXJJbmZvOiB3aW5uZXIgfTtcbiAgfVxuXG4gIC8vIEV4dHJlbWVseSB1bmxpa2VseTogbG9jayBkaXNhcHBlYXJlZCBiZXR3ZWVuIG91ciBjbGFpbSBhbmQgcmUtcmVhZC4gUmV0cnkgb25jZS5cbiAgbG9nZ2VyLndhcm4oJ1tMZWFkZXJFbGVjdGlvbl0gTG9jayB2YW5pc2hlZCBhZnRlciBmYWlsZWQgY2xhaW0uIFJldHJ5aW5nLicpO1xuICBjb25zdCByZXRyeUluZm86IENvbnNvbGVMZWFkZXJJbmZvID0geyAuLi5teUluZm8sIGhlYXJ0YmVhdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpIH07XG4gIGNvbnN0IHJldHJ5Q2xhaW1lZCA9IGF3YWl0IGNsYWltTGVhZGVyc2hpcChyZXRyeUluZm8pO1xuICBpZiAocmV0cnlDbGFpbWVkKSB7XG4gICAgcmV0dXJuIHsgcm9sZTogJ2xlYWRlcicsIGxlYWRlckluZm86IHJldHJ5SW5mbyB9O1xuICB9XG4gIGNvbnN0IGFjdHVhbExlYWRlciA9IGF3YWl0IHJlYWRMZWFkZXJMb2NrKCk7XG4gIHJldHVybiB7IHJvbGU6ICdmb2xsb3dlcicsIGxlYWRlckluZm86IGFjdHVhbExlYWRlciA/PyByZXRyeUluZm8gfTtcbn1cblxuLyoqXG4gKiBTdGFydCB0aGUgbGVhZGVyIGhlYXJ0YmVhdCBsb29wLlxuICogVXBkYXRlcyB0aGUgbG9jayBmaWxlIGV2ZXJ5IEhFQVJUQkVBVF9JTlRFUlZBTF9NUyBzbyBmb2xsb3dlcnMga25vdyB0aGUgbGVhZGVyIGlzIGFsaXZlLlxuICpcbiAqIEByZXR1cm5zIEEgc3RvcCBmdW5jdGlvbiB0byBjbGVhciB0aGUgaW50ZXJ2YWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0YXJ0SGVhcnRiZWF0KGluZm86IENvbnNvbGVMZWFkZXJJbmZvKTogKCkgPT4gdm9pZCB7XG4gIGNvbnN0IGludGVydmFsID0gc2V0SW50ZXJ2YWwoYXN5bmMgKCkgPT4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCB1cGRhdGVkOiBDb25zb2xlTGVhZGVySW5mbyA9IHsgLi4uaW5mbywgaGVhcnRiZWF0OiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCkgfTtcbiAgICAgIGNvbnN0IHRtcEZpbGUgPSBqb2luKFJVTl9ESVIsIGBjb25zb2xlLWxlYWRlci5sb2NrLiR7cHJvY2Vzcy5waWR9LnRtcGApO1xuICAgICAgYXdhaXQgd3JpdGVGaWxlKHRtcEZpbGUsIEpTT04uc3RyaW5naWZ5KHVwZGF0ZWQsIG51bGwsIDIpLCAndXRmLTgnKTtcbiAgICAgIGF3YWl0IHJlbmFtZSh0bXBGaWxlLCBMT0NLX0ZJTEUpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgbG9nZ2VyLmRlYnVnKCdbTGVhZGVyRWxlY3Rpb25dIEhlYXJ0YmVhdCB3cml0ZSBmYWlsZWQ6JywgZXJyKTtcbiAgICB9XG4gIH0sIEhFQVJUQkVBVF9JTlRFUlZBTF9NUyk7XG5cbiAgLy8gRG9uJ3QgbGV0IHRoZSBoZWFydGJlYXQgaW50ZXJ2YWwga2VlcCB0aGUgcHJvY2VzcyBhbGl2ZVxuICBpbnRlcnZhbC51bnJlZigpO1xuXG4gIHJldHVybiAoKSA9PiBjbGVhckludGVydmFsKGludGVydmFsKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlciBjbGVhbnVwIGhhbmRsZXJzIHRvIHJlbW92ZSB0aGUgbGVhZGVyIGxvY2sgb24gcHJvY2VzcyBleGl0LlxuICogU2hvdWxkIG9ubHkgYmUgY2FsbGVkIGJ5IHRoZSBsZWFkZXIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWdpc3RlckxlYWRlckNsZWFudXAoKTogdm9pZCB7XG4gIGNvbnN0IGNsZWFudXAgPSAoKSA9PiB7IGRlbGV0ZUxlYWRlckxvY2soKS5jYXRjaCgoKSA9PiB7fSk7IH07XG4gIHByb2Nlc3Mub25jZSgnZXhpdCcsIGNsZWFudXApO1xuICBwcm9jZXNzLm9uY2UoJ1NJR1RFUk0nLCBjbGVhbnVwKTtcbiAgcHJvY2Vzcy5vbmNlKCdTSUdJTlQnLCBjbGVhbnVwKTtcbn1cbiJdfQ==