@dollhousemcp/mcp-server 2.0.0-rc.6 → 2.0.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 +52 -61
- package/README.github.md +2 -2
- package/README.md +284 -224
- package/README.md.backup +284 -224
- package/README.npm.md +284 -224
- package/dist/cache/LRUCache.d.ts +3 -0
- package/dist/cache/LRUCache.d.ts.map +1 -1
- package/dist/cache/LRUCache.js +36 -26
- package/dist/config/env.d.ts +14 -4
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +20 -6
- package/dist/di/Container.d.ts +21 -0
- package/dist/di/Container.d.ts.map +1 -1
- package/dist/di/Container.js +250 -53
- package/dist/elements/BaseElement.d.ts.map +1 -1
- package/dist/elements/BaseElement.js +5 -10
- package/dist/elements/base/BaseElementManager.d.ts +22 -0
- package/dist/elements/base/BaseElementManager.d.ts.map +1 -1
- package/dist/elements/base/BaseElementManager.js +47 -7
- package/dist/elements/memories/Memory.d.ts +1 -0
- package/dist/elements/memories/Memory.d.ts.map +1 -1
- package/dist/elements/memories/Memory.js +12 -8
- package/dist/elements/memories/MemoryManager.d.ts.map +1 -1
- package/dist/elements/memories/MemoryManager.js +23 -42
- package/dist/elements/memories/MemorySearchIndex.js +2 -2
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.d.ts.map +1 -1
- package/dist/generated/version.js +3 -3
- package/dist/handlers/EnhancedIndexHandler.js +6 -6
- package/dist/handlers/element-crud/listElements.d.ts +2 -0
- package/dist/handlers/element-crud/listElements.d.ts.map +1 -1
- package/dist/handlers/element-crud/listElements.js +3 -1
- package/dist/handlers/mcp-aql/Gatekeeper.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/Gatekeeper.js +23 -17
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts +14 -0
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/MCPAQLHandler.js +110 -14
- package/dist/handlers/mcp-aql/OperationRouter.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/OperationRouter.js +13 -1
- package/dist/handlers/mcp-aql/OperationSchema.d.ts +7 -0
- package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/OperationSchema.js +52 -1
- package/dist/handlers/mcp-aql/evaluatePermission.d.ts +53 -0
- package/dist/handlers/mcp-aql/evaluatePermission.d.ts.map +1 -0
- package/dist/handlers/mcp-aql/evaluatePermission.js +132 -0
- package/dist/handlers/mcp-aql/policies/ToolClassification.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/policies/ToolClassification.js +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/logging/LogHooks.js +11 -11
- package/dist/logging/LogManager.d.ts +0 -2
- package/dist/logging/LogManager.d.ts.map +1 -1
- package/dist/logging/LogManager.js +1 -3
- package/dist/logging/sinks/MemoryLogSink.d.ts +2 -0
- package/dist/logging/sinks/MemoryLogSink.d.ts.map +1 -1
- package/dist/logging/sinks/MemoryLogSink.js +12 -3
- package/dist/logging/types.d.ts +0 -2
- package/dist/logging/types.d.ts.map +1 -1
- package/dist/logging/types.js +1 -1
- package/dist/metrics/GatekeeperMetricsTracker.d.ts +32 -0
- package/dist/metrics/GatekeeperMetricsTracker.d.ts.map +1 -0
- package/dist/metrics/GatekeeperMetricsTracker.js +42 -0
- package/dist/metrics/MetricsManager.d.ts +47 -0
- package/dist/metrics/MetricsManager.d.ts.map +1 -0
- package/dist/metrics/MetricsManager.js +232 -0
- package/dist/metrics/OperationMetricsTracker.d.ts +32 -0
- package/dist/metrics/OperationMetricsTracker.d.ts.map +1 -0
- package/dist/metrics/OperationMetricsTracker.js +53 -0
- package/dist/metrics/collectors/DefaultElementProviderCollector.d.ts +27 -0
- package/dist/metrics/collectors/DefaultElementProviderCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/DefaultElementProviderCollector.js +69 -0
- package/dist/metrics/collectors/FileLockManagerCollector.d.ts +16 -0
- package/dist/metrics/collectors/FileLockManagerCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/FileLockManagerCollector.js +51 -0
- package/dist/metrics/collectors/GatekeeperMetricsCollector.d.ts +16 -0
- package/dist/metrics/collectors/GatekeeperMetricsCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/GatekeeperMetricsCollector.js +76 -0
- package/dist/metrics/collectors/LRUCacheCollector.d.ts +18 -0
- package/dist/metrics/collectors/LRUCacheCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/LRUCacheCollector.js +95 -0
- package/dist/metrics/collectors/OperationMetricsCollector.d.ts +16 -0
- package/dist/metrics/collectors/OperationMetricsCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/OperationMetricsCollector.js +80 -0
- package/dist/metrics/collectors/OperationalTelemetryCollector.d.ts +17 -0
- package/dist/metrics/collectors/OperationalTelemetryCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/OperationalTelemetryCollector.js +26 -0
- package/dist/metrics/collectors/PerformanceMonitorCollector.d.ts +14 -0
- package/dist/metrics/collectors/PerformanceMonitorCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/PerformanceMonitorCollector.js +141 -0
- package/dist/metrics/collectors/SecurityMonitorCollector.d.ts +21 -0
- package/dist/metrics/collectors/SecurityMonitorCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/SecurityMonitorCollector.js +56 -0
- package/dist/metrics/collectors/SecurityTelemetryCollector.d.ts +15 -0
- package/dist/metrics/collectors/SecurityTelemetryCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/SecurityTelemetryCollector.js +112 -0
- package/dist/metrics/collectors/TriggerMetricsTrackerCollector.d.ts +16 -0
- package/dist/metrics/collectors/TriggerMetricsTrackerCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/TriggerMetricsTrackerCollector.js +26 -0
- package/dist/metrics/collectors/index.d.ts +11 -0
- package/dist/metrics/collectors/index.d.ts.map +1 -0
- package/dist/metrics/collectors/index.js +11 -0
- package/dist/metrics/sinks/MemoryMetricsSink.d.ts +22 -0
- package/dist/metrics/sinks/MemoryMetricsSink.d.ts.map +1 -0
- package/dist/metrics/sinks/MemoryMetricsSink.js +121 -0
- package/dist/metrics/types.d.ts +98 -0
- package/dist/metrics/types.d.ts.map +1 -0
- package/dist/metrics/types.js +24 -0
- package/dist/portfolio/DefaultElementProvider.d.ts.map +1 -1
- package/dist/portfolio/DefaultElementProvider.js +1 -7
- package/dist/portfolio/EnhancedIndexManager.d.ts.map +1 -1
- package/dist/portfolio/EnhancedIndexManager.js +18 -18
- package/dist/portfolio/NLPScoringManager.d.ts.map +1 -1
- package/dist/portfolio/NLPScoringManager.js +5 -9
- package/dist/portfolio/PortfolioIndexManager.js +2 -2
- package/dist/portfolio/RelationshipManager.js +2 -2
- package/dist/portfolio/VerbTriggerManager.d.ts.map +1 -1
- package/dist/portfolio/VerbTriggerManager.js +5 -19
- package/dist/portfolio/config/IndexConfig.d.ts.map +1 -1
- package/dist/portfolio/config/IndexConfig.js +1 -12
- package/dist/portfolio/enhanced-index/ElementDefinitionBuilder.d.ts.map +1 -1
- package/dist/portfolio/enhanced-index/ElementDefinitionBuilder.js +3 -15
- package/dist/portfolio/enhanced-index/SemanticRelationshipService.d.ts.map +1 -1
- package/dist/portfolio/enhanced-index/SemanticRelationshipService.js +2 -16
- package/dist/portfolio/types/RelationshipTypes.d.ts.map +1 -1
- package/dist/portfolio/types/RelationshipTypes.js +3 -17
- package/dist/security/audit/config/suppressions.d.ts.map +1 -1
- package/dist/security/audit/config/suppressions.js +36 -8
- package/dist/security/constants.d.ts.map +1 -1
- package/dist/security/constants.js +10 -6
- package/dist/security/fileLockManager.d.ts.map +1 -1
- package/dist/security/fileLockManager.js +8 -6
- package/dist/security/secureYamlParser.d.ts.map +1 -1
- package/dist/security/secureYamlParser.js +1 -13
- package/dist/security/securityMonitor.d.ts +2 -1
- package/dist/security/securityMonitor.d.ts.map +1 -1
- package/dist/security/securityMonitor.js +14 -3
- package/dist/security/telemetry/SecurityTelemetry.d.ts +16 -0
- package/dist/security/telemetry/SecurityTelemetry.d.ts.map +1 -1
- package/dist/security/telemetry/SecurityTelemetry.js +30 -2
- package/dist/security/tokenManager.d.ts +3 -0
- package/dist/security/tokenManager.d.ts.map +1 -1
- package/dist/security/tokenManager.js +13 -5
- package/dist/security/validation/BackgroundValidator.d.ts.map +1 -1
- package/dist/security/validation/BackgroundValidator.js +7 -7
- package/dist/server/startup.d.ts.map +1 -1
- package/dist/server/startup.js +8 -24
- package/dist/server/tools/MCPAQLTools.js +4 -1
- package/dist/services/ActivationStore.d.ts.map +1 -1
- package/dist/services/ActivationStore.js +9 -3
- package/dist/services/FileWatchService.d.ts +1 -0
- package/dist/services/FileWatchService.d.ts.map +1 -1
- package/dist/services/FileWatchService.js +83 -48
- package/dist/services/MetadataService.d.ts.map +1 -1
- package/dist/services/MetadataService.js +7 -2
- package/dist/services/query/ElementQueryService.d.ts.map +1 -1
- package/dist/services/query/ElementQueryService.js +1 -41
- package/dist/services/query/PaginationService.d.ts.map +1 -1
- package/dist/services/query/PaginationService.js +1 -14
- package/dist/services/query/SortService.d.ts.map +1 -1
- package/dist/services/query/SortService.js +1 -6
- package/dist/services/validation/ValidationService.d.ts.map +1 -1
- package/dist/services/validation/ValidationService.js +3 -8
- package/dist/storage/ElementStorageLayer.d.ts.map +1 -1
- package/dist/storage/ElementStorageLayer.js +5 -2
- package/dist/storage/MemoryStorageLayer.d.ts.map +1 -1
- package/dist/storage/MemoryStorageLayer.js +5 -2
- package/dist/telemetry/OperationalTelemetry.js +2 -2
- package/dist/utils/EventDeduplicator.d.ts +44 -0
- package/dist/utils/EventDeduplicator.d.ts.map +1 -0
- package/dist/utils/EventDeduplicator.js +93 -0
- package/dist/utils/FileLock.d.ts.map +1 -1
- package/dist/utils/FileLock.js +1 -9
- package/dist/utils/PerformanceMonitor.d.ts.map +1 -1
- package/dist/utils/PerformanceMonitor.js +5 -5
- package/dist/utils/SlidingWindowRateLimiter.d.ts +13 -0
- package/dist/utils/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/utils/SlidingWindowRateLimiter.js +23 -0
- package/dist/web/console/IngestRoutes.d.ts +84 -0
- package/dist/web/console/IngestRoutes.d.ts.map +1 -0
- package/dist/web/console/IngestRoutes.js +252 -0
- package/dist/web/console/LeaderElection.d.ts +89 -0
- package/dist/web/console/LeaderElection.d.ts.map +1 -0
- package/dist/web/console/LeaderElection.js +205 -0
- package/dist/web/console/LeaderForwardingSink.d.ts +61 -0
- package/dist/web/console/LeaderForwardingSink.d.ts.map +1 -0
- package/dist/web/console/LeaderForwardingSink.js +197 -0
- package/dist/web/console/SessionNames.d.ts +46 -0
- package/dist/web/console/SessionNames.d.ts.map +1 -0
- package/dist/web/console/SessionNames.js +257 -0
- package/dist/web/console/UnifiedConsole.d.ts +64 -0
- package/dist/web/console/UnifiedConsole.d.ts.map +1 -0
- package/dist/web/console/UnifiedConsole.js +119 -0
- package/dist/web/contentPipeline.d.ts +58 -0
- package/dist/web/contentPipeline.d.ts.map +1 -0
- package/dist/web/contentPipeline.js +112 -0
- package/dist/web/portDiscovery.d.ts +58 -0
- package/dist/web/portDiscovery.d.ts.map +1 -0
- package/dist/web/portDiscovery.js +143 -0
- package/dist/web/public/app.js +148 -60
- package/dist/web/public/logs.js +638 -0
- package/dist/web/public/metrics.js +682 -0
- package/dist/web/public/permissions.js +394 -0
- package/dist/web/public/sessions.js +369 -0
- package/dist/web/routes/healthRoutes.d.ts +16 -0
- package/dist/web/routes/healthRoutes.d.ts.map +1 -0
- package/dist/web/routes/healthRoutes.js +29 -0
- package/dist/web/routes/logRoutes.d.ts +18 -0
- package/dist/web/routes/logRoutes.d.ts.map +1 -0
- package/dist/web/routes/logRoutes.js +126 -0
- package/dist/web/routes/metricsRoutes.d.ts +17 -0
- package/dist/web/routes/metricsRoutes.d.ts.map +1 -0
- package/dist/web/routes/metricsRoutes.js +90 -0
- package/dist/web/routes/permissionRoutes.d.ts +16 -0
- package/dist/web/routes/permissionRoutes.d.ts.map +1 -0
- package/dist/web/routes/permissionRoutes.js +133 -0
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +309 -339
- package/dist/web/server.d.ts +21 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +42 -4
- package/dist/web/sinks/WebSSELogSink.d.ts +15 -0
- package/dist/web/sinks/WebSSELogSink.d.ts.map +1 -0
- package/dist/web/sinks/WebSSELogSink.js +22 -0
- package/dist/web/sinks/WebSSEMetricsSink.d.ts +16 -0
- package/dist/web/sinks/WebSSEMetricsSink.d.ts.map +1 -0
- package/dist/web/sinks/WebSSEMetricsSink.js +23 -0
- package/package.json +2 -2
- package/server.json +2 -2
- package/dist/web/public/public/app.js +0 -1878
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified web console orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Ties together leader election, console startup, follower wiring,
|
|
5
|
+
* and session lifecycle management. This is the main entry point
|
|
6
|
+
* called by the DI container during deferred setup.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. Run leader election (read lock file, claim or follow)
|
|
10
|
+
* 2. If leader: start web server on fixed port, mount ingest routes, start heartbeat
|
|
11
|
+
* 3. If follower: register forwarding sinks with LogManager, start session heartbeat
|
|
12
|
+
*
|
|
13
|
+
* @since v2.1.0 — Issue #1700
|
|
14
|
+
*/
|
|
15
|
+
import type { UnifiedLogEntry } from '../../logging/types.js';
|
|
16
|
+
import type { MetricSnapshot } from '../../metrics/types.js';
|
|
17
|
+
import type { MemoryLogSink } from '../../logging/sinks/MemoryLogSink.js';
|
|
18
|
+
import type { MemoryMetricsSink } from '../../metrics/sinks/MemoryMetricsSink.js';
|
|
19
|
+
import { type ElectionResult } from './LeaderElection.js';
|
|
20
|
+
/**
|
|
21
|
+
* Options for starting the unified console.
|
|
22
|
+
*/
|
|
23
|
+
export interface UnifiedConsoleOptions {
|
|
24
|
+
/** This process's unique session ID */
|
|
25
|
+
sessionId: string;
|
|
26
|
+
/** Portfolio base directory (for startWebServer) */
|
|
27
|
+
portfolioDir: string;
|
|
28
|
+
/** Log memory sink (for console history) */
|
|
29
|
+
memorySink: MemoryLogSink;
|
|
30
|
+
/** Metrics memory sink */
|
|
31
|
+
metricsSink?: MemoryMetricsSink;
|
|
32
|
+
/** MCP-AQL handler for permission routes (typed as any to avoid circular imports) */
|
|
33
|
+
mcpAqlHandler?: any;
|
|
34
|
+
/** Callback to register a log sink with the LogManager */
|
|
35
|
+
registerLogSink: (sink: {
|
|
36
|
+
write(entry: UnifiedLogEntry): void;
|
|
37
|
+
flush(): Promise<void>;
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
}) => void;
|
|
40
|
+
/** Callback to wire SSE broadcasts after web server starts */
|
|
41
|
+
wireSSEBroadcasts: (webResult: {
|
|
42
|
+
logBroadcast?: (entry: UnifiedLogEntry) => void;
|
|
43
|
+
metricsOnSnapshot?: (snapshot: MetricSnapshot) => void;
|
|
44
|
+
}, metricsSink?: MemoryMetricsSink) => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Result of starting the unified console.
|
|
48
|
+
*/
|
|
49
|
+
export interface UnifiedConsoleResult {
|
|
50
|
+
role: 'leader' | 'follower';
|
|
51
|
+
election: ElectionResult;
|
|
52
|
+
/** Port the console is running on (leader only) */
|
|
53
|
+
port?: number;
|
|
54
|
+
/** Cleanup function to call on shutdown */
|
|
55
|
+
cleanup: () => Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Start the unified web console.
|
|
59
|
+
*
|
|
60
|
+
* Runs leader election, then either starts the full console (leader)
|
|
61
|
+
* or sets up event forwarding (follower).
|
|
62
|
+
*/
|
|
63
|
+
export declare function startUnifiedConsole(options: UnifiedConsoleOptions): Promise<UnifiedConsoleResult>;
|
|
64
|
+
//# sourceMappingURL=UnifiedConsole.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnifiedConsole.d.ts","sourceRoot":"","sources":["../../../src/web/console/UnifiedConsole.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAElF,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAU7B;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,UAAU,EAAE,aAAa,CAAC;IAC1B,0BAA0B;IAC1B,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,qFAAqF;IACrF,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,0DAA0D;IAC1D,eAAe,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;QAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IACzH,8DAA8D;IAC9D,iBAAiB,EAAE,CAAC,SAAS,EAAE;QAAE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;QAAC,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAA;KAAE,EAAE,WAAW,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACtL;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,QAAQ,EAAE,cAAc,CAAC;IACzB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAQvG"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified web console orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Ties together leader election, console startup, follower wiring,
|
|
5
|
+
* and session lifecycle management. This is the main entry point
|
|
6
|
+
* called by the DI container during deferred setup.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. Run leader election (read lock file, claim or follow)
|
|
10
|
+
* 2. If leader: start web server on fixed port, mount ingest routes, start heartbeat
|
|
11
|
+
* 3. If follower: register forwarding sinks with LogManager, start session heartbeat
|
|
12
|
+
*
|
|
13
|
+
* @since v2.1.0 — Issue #1700
|
|
14
|
+
*/
|
|
15
|
+
import { logger } from '../../utils/logger.js';
|
|
16
|
+
import { electLeader, startHeartbeat, registerLeaderCleanup, } from './LeaderElection.js';
|
|
17
|
+
import { createIngestRoutes } from './IngestRoutes.js';
|
|
18
|
+
import { LeaderForwardingLogSink, SessionHeartbeat, } from './LeaderForwardingSink.js';
|
|
19
|
+
/** Fixed port for the unified console leader */
|
|
20
|
+
const CONSOLE_PORT = 3939;
|
|
21
|
+
/**
|
|
22
|
+
* Start the unified web console.
|
|
23
|
+
*
|
|
24
|
+
* Runs leader election, then either starts the full console (leader)
|
|
25
|
+
* or sets up event forwarding (follower).
|
|
26
|
+
*/
|
|
27
|
+
export async function startUnifiedConsole(options) {
|
|
28
|
+
const election = await electLeader(options.sessionId, CONSOLE_PORT);
|
|
29
|
+
if (election.role === 'leader') {
|
|
30
|
+
return startAsLeader(options, election);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return startAsFollower(options, election);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Start as the console leader.
|
|
38
|
+
* Binds port 3939, mounts all routes including ingestion, starts heartbeat.
|
|
39
|
+
*/
|
|
40
|
+
async function startAsLeader(options, election) {
|
|
41
|
+
const { startWebServer } = await import('../server.js');
|
|
42
|
+
// Pre-create a placeholder broadcast that we'll wire up after the server starts
|
|
43
|
+
let liveBroadcast;
|
|
44
|
+
let liveMetricsOnSnapshot;
|
|
45
|
+
// Create ingestion routes with a deferred broadcast (wired after server starts)
|
|
46
|
+
const ingestResult = createIngestRoutes({
|
|
47
|
+
logBroadcast: (entry) => liveBroadcast?.(entry),
|
|
48
|
+
metricsOnSnapshot: (snapshot) => liveMetricsOnSnapshot?.(snapshot),
|
|
49
|
+
});
|
|
50
|
+
// Register the leader as a session
|
|
51
|
+
ingestResult.registerLeaderSession(options.sessionId, process.pid);
|
|
52
|
+
// Start the web server with ingest routes mounted before the SPA fallback
|
|
53
|
+
const webResult = await startWebServer({
|
|
54
|
+
portfolioDir: options.portfolioDir,
|
|
55
|
+
memorySink: options.memorySink,
|
|
56
|
+
metricsSink: options.metricsSink,
|
|
57
|
+
port: CONSOLE_PORT,
|
|
58
|
+
additionalRouters: [ingestResult.router],
|
|
59
|
+
...(options.mcpAqlHandler ? { mcpAqlHandler: options.mcpAqlHandler } : {}),
|
|
60
|
+
});
|
|
61
|
+
// Wire SSE broadcasts for this leader's own events
|
|
62
|
+
options.wireSSEBroadcasts(webResult, options.metricsSink);
|
|
63
|
+
// Now wire the live broadcast functions into the ingest routes
|
|
64
|
+
if (webResult.logBroadcast) {
|
|
65
|
+
const originalBroadcast = webResult.logBroadcast;
|
|
66
|
+
// Stamp leader's own entries with session ID
|
|
67
|
+
liveBroadcast = (entry) => {
|
|
68
|
+
const stamped = {
|
|
69
|
+
...entry,
|
|
70
|
+
data: { ...entry.data, _sessionId: options.sessionId },
|
|
71
|
+
};
|
|
72
|
+
originalBroadcast(stamped);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
liveMetricsOnSnapshot = webResult.metricsOnSnapshot;
|
|
76
|
+
logger.info('[UnifiedConsole] Ingestion routes mounted');
|
|
77
|
+
// Start heartbeat and register cleanup
|
|
78
|
+
const stopHeartbeat = startHeartbeat(election.leaderInfo);
|
|
79
|
+
registerLeaderCleanup();
|
|
80
|
+
logger.info('[UnifiedConsole] Leader started', {
|
|
81
|
+
sessionId: options.sessionId, port: CONSOLE_PORT, pid: process.pid,
|
|
82
|
+
role: 'leader', ingestRoutes: ['/api/ingest/logs', '/api/ingest/metrics', '/api/ingest/session', '/api/sessions'],
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
role: 'leader',
|
|
86
|
+
election,
|
|
87
|
+
port: CONSOLE_PORT,
|
|
88
|
+
cleanup: async () => {
|
|
89
|
+
stopHeartbeat();
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Start as a follower.
|
|
95
|
+
* Registers forwarding sinks with the LogManager, starts session heartbeat.
|
|
96
|
+
*/
|
|
97
|
+
async function startAsFollower(options, election) {
|
|
98
|
+
const leaderUrl = `http://127.0.0.1:${election.leaderInfo.port}`;
|
|
99
|
+
// Register a forwarding log sink
|
|
100
|
+
const forwardingSink = new LeaderForwardingLogSink(leaderUrl, options.sessionId);
|
|
101
|
+
options.registerLogSink(forwardingSink);
|
|
102
|
+
// Start session heartbeat to the leader
|
|
103
|
+
const sessionHeartbeat = new SessionHeartbeat(leaderUrl, options.sessionId, process.pid);
|
|
104
|
+
await sessionHeartbeat.start();
|
|
105
|
+
logger.info('[UnifiedConsole] Follower started', {
|
|
106
|
+
sessionId: options.sessionId, pid: process.pid, role: 'follower',
|
|
107
|
+
leaderSession: election.leaderInfo.sessionId, leaderPid: election.leaderInfo.pid,
|
|
108
|
+
leaderPort: election.leaderInfo.port, leaderUrl,
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
role: 'follower',
|
|
112
|
+
election,
|
|
113
|
+
cleanup: async () => {
|
|
114
|
+
await sessionHeartbeat.stop();
|
|
115
|
+
await forwardingSink.close();
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVW5pZmllZENvbnNvbGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvd2ViL2NvbnNvbGUvVW5pZmllZENvbnNvbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7R0FhRztBQU1ILE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUMvQyxPQUFPLEVBQ0wsV0FBVyxFQUNYLGNBQWMsRUFDZCxxQkFBcUIsR0FFdEIsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUN2RCxPQUFPLEVBQ0wsdUJBQXVCLEVBQ3ZCLGdCQUFnQixHQUNqQixNQUFNLDJCQUEyQixDQUFDO0FBRW5DLGdEQUFnRDtBQUNoRCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUM7QUFrQzFCOzs7OztHQUtHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxtQkFBbUIsQ0FBQyxPQUE4QjtJQUN0RSxNQUFNLFFBQVEsR0FBRyxNQUFNLFdBQVcsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFlBQVksQ0FBQyxDQUFDO0lBRXBFLElBQUksUUFBUSxDQUFDLElBQUksS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUMvQixPQUFPLGFBQWEsQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDMUMsQ0FBQztTQUFNLENBQUM7UUFDTixPQUFPLGVBQWUsQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDNUMsQ0FBQztBQUNILENBQUM7QUFFRDs7O0dBR0c7QUFDSCxLQUFLLFVBQVUsYUFBYSxDQUMxQixPQUE4QixFQUM5QixRQUF3QjtJQUV4QixNQUFNLEVBQUUsY0FBYyxFQUFFLEdBQUcsTUFBTSxNQUFNLENBQUMsY0FBYyxDQUFDLENBQUM7SUFFeEQsZ0ZBQWdGO0lBQ2hGLElBQUksYUFBNkQsQ0FBQztJQUNsRSxJQUFJLHFCQUF1RSxDQUFDO0lBRTVFLGdGQUFnRjtJQUNoRixNQUFNLFlBQVksR0FBRyxrQkFBa0IsQ0FBQztRQUN0QyxZQUFZLEVBQUUsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLGFBQWEsRUFBRSxDQUFDLEtBQUssQ0FBQztRQUMvQyxpQkFBaUIsRUFBRSxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUMscUJBQXFCLEVBQUUsQ0FBQyxRQUFRLENBQUM7S0FDbkUsQ0FBQyxDQUFDO0lBRUgsbUNBQW1DO0lBQ25DLFlBQVksQ0FBQyxxQkFBcUIsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUVuRSwwRUFBMEU7SUFDMUUsTUFBTSxTQUFTLEdBQUcsTUFBTSxjQUFjLENBQUM7UUFDckMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxZQUFZO1FBQ2xDLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVTtRQUM5QixXQUFXLEVBQUUsT0FBTyxDQUFDLFdBQVc7UUFDaEMsSUFBSSxFQUFFLFlBQVk7UUFDbEIsaUJBQWlCLEVBQUUsQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDO1FBQ3hDLEdBQUcsQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxFQUFFLGFBQWEsRUFBRSxPQUFPLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztLQUMzRSxDQUFDLENBQUM7SUFFSCxtREFBbUQ7SUFDbkQsT0FBTyxDQUFDLGlCQUFpQixDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUM7SUFFMUQsK0RBQStEO0lBQy9ELElBQUksU0FBUyxDQUFDLFlBQVksRUFBRSxDQUFDO1FBQzNCLE1BQU0saUJBQWlCLEdBQUcsU0FBUyxDQUFDLFlBQVksQ0FBQztRQUNqRCw2Q0FBNkM7UUFDN0MsYUFBYSxHQUFHLENBQUMsS0FBc0IsRUFBRSxFQUFFO1lBQ3pDLE1BQU0sT0FBTyxHQUFvQjtnQkFDL0IsR0FBRyxLQUFLO2dCQUNSLElBQUksRUFBRSxFQUFFLEdBQUcsS0FBSyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLFNBQVMsRUFBRTthQUN2RCxDQUFDO1lBQ0YsaUJBQWlCLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDN0IsQ0FBQyxDQUFDO0lBQ0osQ0FBQztJQUNELHFCQUFxQixHQUFHLFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQztJQUVwRCxNQUFNLENBQUMsSUFBSSxDQUFDLDJDQUEyQyxDQUFDLENBQUM7SUFFekQsdUNBQXVDO0lBQ3ZDLE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDMUQscUJBQXFCLEVBQUUsQ0FBQztJQUV4QixNQUFNLENBQUMsSUFBSSxDQUFDLGlDQUFpQyxFQUFFO1FBQzdDLFNBQVMsRUFBRSxPQUFPLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsR0FBRyxFQUFFLE9BQU8sQ0FBQyxHQUFHO1FBQ2xFLElBQUksRUFBRSxRQUFRLEVBQUUsWUFBWSxFQUFFLENBQUMsa0JBQWtCLEVBQUUscUJBQXFCLEVBQUUscUJBQXFCLEVBQUUsZUFBZSxDQUFDO0tBQ2xILENBQUMsQ0FBQztJQUVILE9BQU87UUFDTCxJQUFJLEVBQUUsUUFBUTtRQUNkLFFBQVE7UUFDUixJQUFJLEVBQUUsWUFBWTtRQUNsQixPQUFPLEVBQUUsS0FBSyxJQUFJLEVBQUU7WUFDbEIsYUFBYSxFQUFFLENBQUM7UUFDbEIsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsS0FBSyxVQUFVLGVBQWUsQ0FDNUIsT0FBOEIsRUFDOUIsUUFBd0I7SUFFeEIsTUFBTSxTQUFTLEdBQUcsb0JBQW9CLFFBQVEsQ0FBQyxVQUFVLENBQUMsSUFBSSxFQUFFLENBQUM7SUFFakUsaUNBQWlDO0lBQ2pDLE1BQU0sY0FBYyxHQUFHLElBQUksdUJBQXVCLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUNqRixPQUFPLENBQUMsZUFBZSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0lBRXhDLHdDQUF3QztJQUN4QyxNQUFNLGdCQUFnQixHQUFHLElBQUksZ0JBQWdCLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxTQUFTLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ3pGLE1BQU0sZ0JBQWdCLENBQUMsS0FBSyxFQUFFLENBQUM7SUFFL0IsTUFBTSxDQUFDLElBQUksQ0FBQyxtQ0FBbUMsRUFBRTtRQUMvQyxTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVMsRUFBRSxHQUFHLEVBQUUsT0FBTyxDQUFDLEdBQUcsRUFBRSxJQUFJLEVBQUUsVUFBVTtRQUNoRSxhQUFhLEVBQUUsUUFBUSxDQUFDLFVBQVUsQ0FBQyxTQUFTLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQyxVQUFVLENBQUMsR0FBRztRQUNoRixVQUFVLEVBQUUsUUFBUSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsU0FBUztLQUNoRCxDQUFDLENBQUM7SUFFSCxPQUFPO1FBQ0wsSUFBSSxFQUFFLFVBQVU7UUFDaEIsUUFBUTtRQUNSLE9BQU8sRUFBRSxLQUFLLElBQUksRUFBRTtZQUNsQixNQUFNLGdCQUFnQixDQUFDLElBQUksRUFBRSxDQUFDO1lBQzlCLE1BQU0sY0FBYyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQy9CLENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVW5pZmllZCB3ZWIgY29uc29sZSBvcmNoZXN0cmF0b3IuXG4gKlxuICogVGllcyB0b2dldGhlciBsZWFkZXIgZWxlY3Rpb24sIGNvbnNvbGUgc3RhcnR1cCwgZm9sbG93ZXIgd2lyaW5nLFxuICogYW5kIHNlc3Npb24gbGlmZWN5Y2xlIG1hbmFnZW1lbnQuIFRoaXMgaXMgdGhlIG1haW4gZW50cnkgcG9pbnRcbiAqIGNhbGxlZCBieSB0aGUgREkgY29udGFpbmVyIGR1cmluZyBkZWZlcnJlZCBzZXR1cC5cbiAqXG4gKiBGbG93OlxuICogMS4gUnVuIGxlYWRlciBlbGVjdGlvbiAocmVhZCBsb2NrIGZpbGUsIGNsYWltIG9yIGZvbGxvdylcbiAqIDIuIElmIGxlYWRlcjogc3RhcnQgd2ViIHNlcnZlciBvbiBmaXhlZCBwb3J0LCBtb3VudCBpbmdlc3Qgcm91dGVzLCBzdGFydCBoZWFydGJlYXRcbiAqIDMuIElmIGZvbGxvd2VyOiByZWdpc3RlciBmb3J3YXJkaW5nIHNpbmtzIHdpdGggTG9nTWFuYWdlciwgc3RhcnQgc2Vzc2lvbiBoZWFydGJlYXRcbiAqXG4gKiBAc2luY2UgdjIuMS4wIOKAlCBJc3N1ZSAjMTcwMFxuICovXG5cbmltcG9ydCB0eXBlIHsgVW5pZmllZExvZ0VudHJ5IH0gZnJvbSAnLi4vLi4vbG9nZ2luZy90eXBlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IE1ldHJpY1NuYXBzaG90IH0gZnJvbSAnLi4vLi4vbWV0cmljcy90eXBlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IE1lbW9yeUxvZ1NpbmsgfSBmcm9tICcuLi8uLi9sb2dnaW5nL3NpbmtzL01lbW9yeUxvZ1NpbmsuanMnO1xuaW1wb3J0IHR5cGUgeyBNZW1vcnlNZXRyaWNzU2luayB9IGZyb20gJy4uLy4uL21ldHJpY3Mvc2lua3MvTWVtb3J5TWV0cmljc1NpbmsuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7XG4gIGVsZWN0TGVhZGVyLFxuICBzdGFydEhlYXJ0YmVhdCxcbiAgcmVnaXN0ZXJMZWFkZXJDbGVhbnVwLFxuICB0eXBlIEVsZWN0aW9uUmVzdWx0LFxufSBmcm9tICcuL0xlYWRlckVsZWN0aW9uLmpzJztcbmltcG9ydCB7IGNyZWF0ZUluZ2VzdFJvdXRlcyB9IGZyb20gJy4vSW5nZXN0Um91dGVzLmpzJztcbmltcG9ydCB7XG4gIExlYWRlckZvcndhcmRpbmdMb2dTaW5rLFxuICBTZXNzaW9uSGVhcnRiZWF0LFxufSBmcm9tICcuL0xlYWRlckZvcndhcmRpbmdTaW5rLmpzJztcblxuLyoqIEZpeGVkIHBvcnQgZm9yIHRoZSB1bmlmaWVkIGNvbnNvbGUgbGVhZGVyICovXG5jb25zdCBDT05TT0xFX1BPUlQgPSAzOTM5O1xuXG4vKipcbiAqIE9wdGlvbnMgZm9yIHN0YXJ0aW5nIHRoZSB1bmlmaWVkIGNvbnNvbGUuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVW5pZmllZENvbnNvbGVPcHRpb25zIHtcbiAgLyoqIFRoaXMgcHJvY2VzcydzIHVuaXF1ZSBzZXNzaW9uIElEICovXG4gIHNlc3Npb25JZDogc3RyaW5nO1xuICAvKiogUG9ydGZvbGlvIGJhc2UgZGlyZWN0b3J5IChmb3Igc3RhcnRXZWJTZXJ2ZXIpICovXG4gIHBvcnRmb2xpb0Rpcjogc3RyaW5nO1xuICAvKiogTG9nIG1lbW9yeSBzaW5rIChmb3IgY29uc29sZSBoaXN0b3J5KSAqL1xuICBtZW1vcnlTaW5rOiBNZW1vcnlMb2dTaW5rO1xuICAvKiogTWV0cmljcyBtZW1vcnkgc2luayAqL1xuICBtZXRyaWNzU2luaz86IE1lbW9yeU1ldHJpY3NTaW5rO1xuICAvKiogTUNQLUFRTCBoYW5kbGVyIGZvciBwZXJtaXNzaW9uIHJvdXRlcyAodHlwZWQgYXMgYW55IHRvIGF2b2lkIGNpcmN1bGFyIGltcG9ydHMpICovXG4gIG1jcEFxbEhhbmRsZXI/OiBhbnk7XG4gIC8qKiBDYWxsYmFjayB0byByZWdpc3RlciBhIGxvZyBzaW5rIHdpdGggdGhlIExvZ01hbmFnZXIgKi9cbiAgcmVnaXN0ZXJMb2dTaW5rOiAoc2luazogeyB3cml0ZShlbnRyeTogVW5pZmllZExvZ0VudHJ5KTogdm9pZDsgZmx1c2goKTogUHJvbWlzZTx2b2lkPjsgY2xvc2UoKTogUHJvbWlzZTx2b2lkPiB9KSA9PiB2b2lkO1xuICAvKiogQ2FsbGJhY2sgdG8gd2lyZSBTU0UgYnJvYWRjYXN0cyBhZnRlciB3ZWIgc2VydmVyIHN0YXJ0cyAqL1xuICB3aXJlU1NFQnJvYWRjYXN0czogKHdlYlJlc3VsdDogeyBsb2dCcm9hZGNhc3Q/OiAoZW50cnk6IFVuaWZpZWRMb2dFbnRyeSkgPT4gdm9pZDsgbWV0cmljc09uU25hcHNob3Q/OiAoc25hcHNob3Q6IE1ldHJpY1NuYXBzaG90KSA9PiB2b2lkIH0sIG1ldHJpY3NTaW5rPzogTWVtb3J5TWV0cmljc1NpbmspID0+IHZvaWQ7XG59XG5cbi8qKlxuICogUmVzdWx0IG9mIHN0YXJ0aW5nIHRoZSB1bmlmaWVkIGNvbnNvbGUuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVW5pZmllZENvbnNvbGVSZXN1bHQge1xuICByb2xlOiAnbGVhZGVyJyB8ICdmb2xsb3dlcic7XG4gIGVsZWN0aW9uOiBFbGVjdGlvblJlc3VsdDtcbiAgLyoqIFBvcnQgdGhlIGNvbnNvbGUgaXMgcnVubmluZyBvbiAobGVhZGVyIG9ubHkpICovXG4gIHBvcnQ/OiBudW1iZXI7XG4gIC8qKiBDbGVhbnVwIGZ1bmN0aW9uIHRvIGNhbGwgb24gc2h1dGRvd24gKi9cbiAgY2xlYW51cDogKCkgPT4gUHJvbWlzZTx2b2lkPjtcbn1cblxuLyoqXG4gKiBTdGFydCB0aGUgdW5pZmllZCB3ZWIgY29uc29sZS5cbiAqXG4gKiBSdW5zIGxlYWRlciBlbGVjdGlvbiwgdGhlbiBlaXRoZXIgc3RhcnRzIHRoZSBmdWxsIGNvbnNvbGUgKGxlYWRlcilcbiAqIG9yIHNldHMgdXAgZXZlbnQgZm9yd2FyZGluZyAoZm9sbG93ZXIpLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc3RhcnRVbmlmaWVkQ29uc29sZShvcHRpb25zOiBVbmlmaWVkQ29uc29sZU9wdGlvbnMpOiBQcm9taXNlPFVuaWZpZWRDb25zb2xlUmVzdWx0PiB7XG4gIGNvbnN0IGVsZWN0aW9uID0gYXdhaXQgZWxlY3RMZWFkZXIob3B0aW9ucy5zZXNzaW9uSWQsIENPTlNPTEVfUE9SVCk7XG5cbiAgaWYgKGVsZWN0aW9uLnJvbGUgPT09ICdsZWFkZXInKSB7XG4gICAgcmV0dXJuIHN0YXJ0QXNMZWFkZXIob3B0aW9ucywgZWxlY3Rpb24pO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBzdGFydEFzRm9sbG93ZXIob3B0aW9ucywgZWxlY3Rpb24pO1xuICB9XG59XG5cbi8qKlxuICogU3RhcnQgYXMgdGhlIGNvbnNvbGUgbGVhZGVyLlxuICogQmluZHMgcG9ydCAzOTM5LCBtb3VudHMgYWxsIHJvdXRlcyBpbmNsdWRpbmcgaW5nZXN0aW9uLCBzdGFydHMgaGVhcnRiZWF0LlxuICovXG5hc3luYyBmdW5jdGlvbiBzdGFydEFzTGVhZGVyKFxuICBvcHRpb25zOiBVbmlmaWVkQ29uc29sZU9wdGlvbnMsXG4gIGVsZWN0aW9uOiBFbGVjdGlvblJlc3VsdCxcbik6IFByb21pc2U8VW5pZmllZENvbnNvbGVSZXN1bHQ+IHtcbiAgY29uc3QgeyBzdGFydFdlYlNlcnZlciB9ID0gYXdhaXQgaW1wb3J0KCcuLi9zZXJ2ZXIuanMnKTtcblxuICAvLyBQcmUtY3JlYXRlIGEgcGxhY2Vob2xkZXIgYnJvYWRjYXN0IHRoYXQgd2UnbGwgd2lyZSB1cCBhZnRlciB0aGUgc2VydmVyIHN0YXJ0c1xuICBsZXQgbGl2ZUJyb2FkY2FzdDogKChlbnRyeTogVW5pZmllZExvZ0VudHJ5KSA9PiB2b2lkKSB8IHVuZGVmaW5lZDtcbiAgbGV0IGxpdmVNZXRyaWNzT25TbmFwc2hvdDogKChzbmFwc2hvdDogTWV0cmljU25hcHNob3QpID0+IHZvaWQpIHwgdW5kZWZpbmVkO1xuXG4gIC8vIENyZWF0ZSBpbmdlc3Rpb24gcm91dGVzIHdpdGggYSBkZWZlcnJlZCBicm9hZGNhc3QgKHdpcmVkIGFmdGVyIHNlcnZlciBzdGFydHMpXG4gIGNvbnN0IGluZ2VzdFJlc3VsdCA9IGNyZWF0ZUluZ2VzdFJvdXRlcyh7XG4gICAgbG9nQnJvYWRjYXN0OiAoZW50cnkpID0+IGxpdmVCcm9hZGNhc3Q/LihlbnRyeSksXG4gICAgbWV0cmljc09uU25hcHNob3Q6IChzbmFwc2hvdCkgPT4gbGl2ZU1ldHJpY3NPblNuYXBzaG90Py4oc25hcHNob3QpLFxuICB9KTtcblxuICAvLyBSZWdpc3RlciB0aGUgbGVhZGVyIGFzIGEgc2Vzc2lvblxuICBpbmdlc3RSZXN1bHQucmVnaXN0ZXJMZWFkZXJTZXNzaW9uKG9wdGlvbnMuc2Vzc2lvbklkLCBwcm9jZXNzLnBpZCk7XG5cbiAgLy8gU3RhcnQgdGhlIHdlYiBzZXJ2ZXIgd2l0aCBpbmdlc3Qgcm91dGVzIG1vdW50ZWQgYmVmb3JlIHRoZSBTUEEgZmFsbGJhY2tcbiAgY29uc3Qgd2ViUmVzdWx0ID0gYXdhaXQgc3RhcnRXZWJTZXJ2ZXIoe1xuICAgIHBvcnRmb2xpb0Rpcjogb3B0aW9ucy5wb3J0Zm9saW9EaXIsXG4gICAgbWVtb3J5U2luazogb3B0aW9ucy5tZW1vcnlTaW5rLFxuICAgIG1ldHJpY3NTaW5rOiBvcHRpb25zLm1ldHJpY3NTaW5rLFxuICAgIHBvcnQ6IENPTlNPTEVfUE9SVCxcbiAgICBhZGRpdGlvbmFsUm91dGVyczogW2luZ2VzdFJlc3VsdC5yb3V0ZXJdLFxuICAgIC4uLihvcHRpb25zLm1jcEFxbEhhbmRsZXIgPyB7IG1jcEFxbEhhbmRsZXI6IG9wdGlvbnMubWNwQXFsSGFuZGxlciB9IDoge30pLFxuICB9KTtcblxuICAvLyBXaXJlIFNTRSBicm9hZGNhc3RzIGZvciB0aGlzIGxlYWRlcidzIG93biBldmVudHNcbiAgb3B0aW9ucy53aXJlU1NFQnJvYWRjYXN0cyh3ZWJSZXN1bHQsIG9wdGlvbnMubWV0cmljc1NpbmspO1xuXG4gIC8vIE5vdyB3aXJlIHRoZSBsaXZlIGJyb2FkY2FzdCBmdW5jdGlvbnMgaW50byB0aGUgaW5nZXN0IHJvdXRlc1xuICBpZiAod2ViUmVzdWx0LmxvZ0Jyb2FkY2FzdCkge1xuICAgIGNvbnN0IG9yaWdpbmFsQnJvYWRjYXN0ID0gd2ViUmVzdWx0LmxvZ0Jyb2FkY2FzdDtcbiAgICAvLyBTdGFtcCBsZWFkZXIncyBvd24gZW50cmllcyB3aXRoIHNlc3Npb24gSURcbiAgICBsaXZlQnJvYWRjYXN0ID0gKGVudHJ5OiBVbmlmaWVkTG9nRW50cnkpID0+IHtcbiAgICAgIGNvbnN0IHN0YW1wZWQ6IFVuaWZpZWRMb2dFbnRyeSA9IHtcbiAgICAgICAgLi4uZW50cnksXG4gICAgICAgIGRhdGE6IHsgLi4uZW50cnkuZGF0YSwgX3Nlc3Npb25JZDogb3B0aW9ucy5zZXNzaW9uSWQgfSxcbiAgICAgIH07XG4gICAgICBvcmlnaW5hbEJyb2FkY2FzdChzdGFtcGVkKTtcbiAgICB9O1xuICB9XG4gIGxpdmVNZXRyaWNzT25TbmFwc2hvdCA9IHdlYlJlc3VsdC5tZXRyaWNzT25TbmFwc2hvdDtcblxuICBsb2dnZXIuaW5mbygnW1VuaWZpZWRDb25zb2xlXSBJbmdlc3Rpb24gcm91dGVzIG1vdW50ZWQnKTtcblxuICAvLyBTdGFydCBoZWFydGJlYXQgYW5kIHJlZ2lzdGVyIGNsZWFudXBcbiAgY29uc3Qgc3RvcEhlYXJ0YmVhdCA9IHN0YXJ0SGVhcnRiZWF0KGVsZWN0aW9uLmxlYWRlckluZm8pO1xuICByZWdpc3RlckxlYWRlckNsZWFudXAoKTtcblxuICBsb2dnZXIuaW5mbygnW1VuaWZpZWRDb25zb2xlXSBMZWFkZXIgc3RhcnRlZCcsIHtcbiAgICBzZXNzaW9uSWQ6IG9wdGlvbnMuc2Vzc2lvbklkLCBwb3J0OiBDT05TT0xFX1BPUlQsIHBpZDogcHJvY2Vzcy5waWQsXG4gICAgcm9sZTogJ2xlYWRlcicsIGluZ2VzdFJvdXRlczogWycvYXBpL2luZ2VzdC9sb2dzJywgJy9hcGkvaW5nZXN0L21ldHJpY3MnLCAnL2FwaS9pbmdlc3Qvc2Vzc2lvbicsICcvYXBpL3Nlc3Npb25zJ10sXG4gIH0pO1xuXG4gIHJldHVybiB7XG4gICAgcm9sZTogJ2xlYWRlcicsXG4gICAgZWxlY3Rpb24sXG4gICAgcG9ydDogQ09OU09MRV9QT1JULFxuICAgIGNsZWFudXA6IGFzeW5jICgpID0+IHtcbiAgICAgIHN0b3BIZWFydGJlYXQoKTtcbiAgICB9LFxuICB9O1xufVxuXG4vKipcbiAqIFN0YXJ0IGFzIGEgZm9sbG93ZXIuXG4gKiBSZWdpc3RlcnMgZm9yd2FyZGluZyBzaW5rcyB3aXRoIHRoZSBMb2dNYW5hZ2VyLCBzdGFydHMgc2Vzc2lvbiBoZWFydGJlYXQuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHN0YXJ0QXNGb2xsb3dlcihcbiAgb3B0aW9uczogVW5pZmllZENvbnNvbGVPcHRpb25zLFxuICBlbGVjdGlvbjogRWxlY3Rpb25SZXN1bHQsXG4pOiBQcm9taXNlPFVuaWZpZWRDb25zb2xlUmVzdWx0PiB7XG4gIGNvbnN0IGxlYWRlclVybCA9IGBodHRwOi8vMTI3LjAuMC4xOiR7ZWxlY3Rpb24ubGVhZGVySW5mby5wb3J0fWA7XG5cbiAgLy8gUmVnaXN0ZXIgYSBmb3J3YXJkaW5nIGxvZyBzaW5rXG4gIGNvbnN0IGZvcndhcmRpbmdTaW5rID0gbmV3IExlYWRlckZvcndhcmRpbmdMb2dTaW5rKGxlYWRlclVybCwgb3B0aW9ucy5zZXNzaW9uSWQpO1xuICBvcHRpb25zLnJlZ2lzdGVyTG9nU2luayhmb3J3YXJkaW5nU2luayk7XG5cbiAgLy8gU3RhcnQgc2Vzc2lvbiBoZWFydGJlYXQgdG8gdGhlIGxlYWRlclxuICBjb25zdCBzZXNzaW9uSGVhcnRiZWF0ID0gbmV3IFNlc3Npb25IZWFydGJlYXQobGVhZGVyVXJsLCBvcHRpb25zLnNlc3Npb25JZCwgcHJvY2Vzcy5waWQpO1xuICBhd2FpdCBzZXNzaW9uSGVhcnRiZWF0LnN0YXJ0KCk7XG5cbiAgbG9nZ2VyLmluZm8oJ1tVbmlmaWVkQ29uc29sZV0gRm9sbG93ZXIgc3RhcnRlZCcsIHtcbiAgICBzZXNzaW9uSWQ6IG9wdGlvbnMuc2Vzc2lvbklkLCBwaWQ6IHByb2Nlc3MucGlkLCByb2xlOiAnZm9sbG93ZXInLFxuICAgIGxlYWRlclNlc3Npb246IGVsZWN0aW9uLmxlYWRlckluZm8uc2Vzc2lvbklkLCBsZWFkZXJQaWQ6IGVsZWN0aW9uLmxlYWRlckluZm8ucGlkLFxuICAgIGxlYWRlclBvcnQ6IGVsZWN0aW9uLmxlYWRlckluZm8ucG9ydCwgbGVhZGVyVXJsLFxuICB9KTtcblxuICByZXR1cm4ge1xuICAgIHJvbGU6ICdmb2xsb3dlcicsXG4gICAgZWxlY3Rpb24sXG4gICAgY2xlYW51cDogYXN5bmMgKCkgPT4ge1xuICAgICAgYXdhaXQgc2Vzc2lvbkhlYXJ0YmVhdC5zdG9wKCk7XG4gICAgICBhd2FpdCBmb3J3YXJkaW5nU2luay5jbG9zZSgpO1xuICAgIH0sXG4gIH07XG59XG4iXX0=
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Validation Pipeline for Web UI
|
|
3
|
+
*
|
|
4
|
+
* Composes the standalone security validators into a single pipeline
|
|
5
|
+
* for validating portfolio element content before serving to the browser.
|
|
6
|
+
*
|
|
7
|
+
* Uses the same validators as the MCPAQLHandler but without coupling
|
|
8
|
+
* to the handler's operation dispatch. This is the extraction point
|
|
9
|
+
* for the upcoming MCPAQLHandler decomposition.
|
|
10
|
+
*
|
|
11
|
+
* auto-dollhouse#5 / upstream #1679
|
|
12
|
+
* DMCP-SEC-004 compliant: uses UnicodeValidator.normalize() on all inputs
|
|
13
|
+
*/
|
|
14
|
+
/** Known metadata fields extracted from YAML frontmatter for web display */
|
|
15
|
+
export interface ElementDisplayMetadata {
|
|
16
|
+
name?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
version?: string;
|
|
19
|
+
author?: string;
|
|
20
|
+
category?: string;
|
|
21
|
+
created?: string;
|
|
22
|
+
created_date?: string;
|
|
23
|
+
modified?: string;
|
|
24
|
+
tags?: string[];
|
|
25
|
+
license?: string;
|
|
26
|
+
age_rating?: string;
|
|
27
|
+
triggers?: string[];
|
|
28
|
+
instructions?: string;
|
|
29
|
+
coordination_strategy?: string;
|
|
30
|
+
use_cases?: string[];
|
|
31
|
+
proficiency_levels?: Record<string, string>;
|
|
32
|
+
gatekeeper?: Record<string, unknown>;
|
|
33
|
+
goal?: Record<string, unknown>;
|
|
34
|
+
autonomy?: Record<string, unknown>;
|
|
35
|
+
memoryType?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
export interface PipelineResult {
|
|
39
|
+
valid: boolean;
|
|
40
|
+
content: string;
|
|
41
|
+
metadata: ElementDisplayMetadata;
|
|
42
|
+
body: string;
|
|
43
|
+
rejection?: {
|
|
44
|
+
reason: string;
|
|
45
|
+
severity?: string;
|
|
46
|
+
patterns?: string[];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validate element content through the security pipeline.
|
|
51
|
+
*
|
|
52
|
+
* @param filename - The element filename (e.g., "alex-sterling.md")
|
|
53
|
+
* @param rawContent - The raw file content string
|
|
54
|
+
* @param elementType - The plural element type directory (e.g., "personas")
|
|
55
|
+
* @returns Validated content with parsed metadata, or rejection with reason
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateElementContent(filename: string, rawContent: string, elementType: string): PipelineResult;
|
|
58
|
+
//# sourceMappingURL=contentPipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentPipeline.d.ts","sourceRoot":"","sources":["../../src/web/contentPipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAiBH,4EAA4E;AAC5E,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,sBAAsB,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,cAAc,CAgFhB"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Validation Pipeline for Web UI
|
|
3
|
+
*
|
|
4
|
+
* Composes the standalone security validators into a single pipeline
|
|
5
|
+
* for validating portfolio element content before serving to the browser.
|
|
6
|
+
*
|
|
7
|
+
* Uses the same validators as the MCPAQLHandler but without coupling
|
|
8
|
+
* to the handler's operation dispatch. This is the extraction point
|
|
9
|
+
* for the upcoming MCPAQLHandler decomposition.
|
|
10
|
+
*
|
|
11
|
+
* auto-dollhouse#5 / upstream #1679
|
|
12
|
+
* DMCP-SEC-004 compliant: uses UnicodeValidator.normalize() on all inputs
|
|
13
|
+
*/
|
|
14
|
+
import { extname } from 'node:path';
|
|
15
|
+
import { SecureYamlParser } from '../security/secureYamlParser.js';
|
|
16
|
+
import { ContentValidator } from '../security/contentValidator.js';
|
|
17
|
+
import { UnicodeValidator } from '../security/validators/unicodeValidator.js';
|
|
18
|
+
import { logger } from '../utils/logger.js';
|
|
19
|
+
/** Element types that map to content validation contexts */
|
|
20
|
+
const TYPE_TO_CONTEXT = {
|
|
21
|
+
personas: 'persona',
|
|
22
|
+
skills: 'skill',
|
|
23
|
+
templates: 'template',
|
|
24
|
+
agents: 'agent',
|
|
25
|
+
memories: 'memory',
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Validate element content through the security pipeline.
|
|
29
|
+
*
|
|
30
|
+
* @param filename - The element filename (e.g., "alex-sterling.md")
|
|
31
|
+
* @param rawContent - The raw file content string
|
|
32
|
+
* @param elementType - The plural element type directory (e.g., "personas")
|
|
33
|
+
* @returns Validated content with parsed metadata, or rejection with reason
|
|
34
|
+
*/
|
|
35
|
+
export function validateElementContent(filename, rawContent, elementType) {
|
|
36
|
+
// DMCP-SEC-004: Normalize all inputs via UnicodeValidator to prevent
|
|
37
|
+
// homograph attacks, direction override bypasses, and suspicious patterns.
|
|
38
|
+
const filenameValidation = UnicodeValidator.normalize(filename);
|
|
39
|
+
const contentValidation = UnicodeValidator.normalize(rawContent);
|
|
40
|
+
const normalizedFilename = filenameValidation.normalizedContent;
|
|
41
|
+
const normalizedContent = contentValidation.normalizedContent;
|
|
42
|
+
const normalizedType = elementType.normalize('NFC');
|
|
43
|
+
const ext = extname(normalizedFilename);
|
|
44
|
+
const isYaml = ext === '.yaml' || ext === '.yml';
|
|
45
|
+
const contentContext = TYPE_TO_CONTEXT[normalizedType];
|
|
46
|
+
// Step 1: Parse and validate structure (YAML bomb detection, circular refs)
|
|
47
|
+
let metadata = {};
|
|
48
|
+
let body = '';
|
|
49
|
+
try {
|
|
50
|
+
if (isYaml) {
|
|
51
|
+
// Pure YAML file (memories) — use parseRawYaml for structural validation
|
|
52
|
+
// (bomb detection, circular refs, size limits).
|
|
53
|
+
// Skip ContentValidator.validateYamlContent() — it produces false positives
|
|
54
|
+
// on memory content that legitimately contains code patterns, security
|
|
55
|
+
// keywords, and technical documentation. Memories are locally-generated
|
|
56
|
+
// trusted content, not untrusted external submissions.
|
|
57
|
+
const parsed = SecureYamlParser.parseRawYaml(normalizedContent);
|
|
58
|
+
metadata = (typeof parsed === 'object' && parsed !== null) ? parsed : {};
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Markdown with YAML frontmatter
|
|
62
|
+
const parsed = SecureYamlParser.parse(normalizedContent, { contentContext });
|
|
63
|
+
metadata = parsed.data;
|
|
64
|
+
body = parsed.content;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return {
|
|
69
|
+
valid: false,
|
|
70
|
+
content: normalizedContent,
|
|
71
|
+
metadata: {},
|
|
72
|
+
body: '',
|
|
73
|
+
rejection: { reason: `Parse validation failed: ${err.message}`, severity: 'high' },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Step 2: Content injection pattern detection (markdown elements only)
|
|
77
|
+
// YAML memories skip this — they contain legitimate code patterns and
|
|
78
|
+
// technical content that triggers false positives. The structural YAML
|
|
79
|
+
// parsing above (bomb/circular ref detection) is sufficient for local content.
|
|
80
|
+
if (!isYaml) {
|
|
81
|
+
const validation = ContentValidator.validateAndSanitize(normalizedContent, {
|
|
82
|
+
contentContext,
|
|
83
|
+
});
|
|
84
|
+
if (!validation.isValid) {
|
|
85
|
+
logger.warn('[ContentPipeline] Content rejected', {
|
|
86
|
+
filename,
|
|
87
|
+
elementType,
|
|
88
|
+
patterns: validation.detectedPatterns,
|
|
89
|
+
severity: validation.severity,
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
valid: false,
|
|
93
|
+
content: normalizedContent,
|
|
94
|
+
metadata,
|
|
95
|
+
body,
|
|
96
|
+
rejection: {
|
|
97
|
+
reason: 'Content failed security validation',
|
|
98
|
+
severity: validation.severity,
|
|
99
|
+
patterns: validation.detectedPatterns,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Step 3: Return validated content
|
|
105
|
+
return {
|
|
106
|
+
valid: true,
|
|
107
|
+
content: rawContent,
|
|
108
|
+
metadata,
|
|
109
|
+
body,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGVudFBpcGVsaW5lLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dlYi9jb250ZW50UGlwZWxpbmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7OztHQVlHO0FBRUgsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNwQyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNuRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQWdDLE1BQU0saUNBQWlDLENBQUM7QUFDakcsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sNENBQTRDLENBQUM7QUFDOUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRTVDLDREQUE0RDtBQUM1RCxNQUFNLGVBQWUsR0FBMEU7SUFDN0YsUUFBUSxFQUFFLFNBQVM7SUFDbkIsTUFBTSxFQUFFLE9BQU87SUFDZixTQUFTLEVBQUUsVUFBVTtJQUNyQixNQUFNLEVBQUUsT0FBTztJQUNmLFFBQVEsRUFBRSxRQUFRO0NBQ25CLENBQUM7QUF1Q0Y7Ozs7Ozs7R0FPRztBQUNILE1BQU0sVUFBVSxzQkFBc0IsQ0FDcEMsUUFBZ0IsRUFDaEIsVUFBa0IsRUFDbEIsV0FBbUI7SUFFbkIscUVBQXFFO0lBQ3JFLDJFQUEyRTtJQUMzRSxNQUFNLGtCQUFrQixHQUFHLGdCQUFnQixDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNoRSxNQUFNLGlCQUFpQixHQUFHLGdCQUFnQixDQUFDLFNBQVMsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUNqRSxNQUFNLGtCQUFrQixHQUFHLGtCQUFrQixDQUFDLGlCQUFpQixDQUFDO0lBQ2hFLE1BQU0saUJBQWlCLEdBQUcsaUJBQWlCLENBQUMsaUJBQWlCLENBQUM7SUFDOUQsTUFBTSxjQUFjLEdBQUcsV0FBVyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUVwRCxNQUFNLEdBQUcsR0FBRyxPQUFPLENBQUMsa0JBQWtCLENBQUMsQ0FBQztJQUN4QyxNQUFNLE1BQU0sR0FBRyxHQUFHLEtBQUssT0FBTyxJQUFJLEdBQUcsS0FBSyxNQUFNLENBQUM7SUFDakQsTUFBTSxjQUFjLEdBQUcsZUFBZSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0lBRXZELDRFQUE0RTtJQUM1RSxJQUFJLFFBQVEsR0FBMkIsRUFBRSxDQUFDO0lBQzFDLElBQUksSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUVkLElBQUksQ0FBQztRQUNILElBQUksTUFBTSxFQUFFLENBQUM7WUFDWCx5RUFBeUU7WUFDekUsZ0RBQWdEO1lBQ2hELDRFQUE0RTtZQUM1RSx1RUFBdUU7WUFDdkUsd0VBQXdFO1lBQ3hFLHVEQUF1RDtZQUN2RCxNQUFNLE1BQU0sR0FBRyxnQkFBZ0IsQ0FBQyxZQUFZLENBQUMsaUJBQWlCLENBQUMsQ0FBQztZQUNoRSxRQUFRLEdBQUcsQ0FBQyxPQUFPLE1BQU0sS0FBSyxRQUFRLElBQUksTUFBTSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUMzRSxDQUFDO2FBQU0sQ0FBQztZQUNOLGlDQUFpQztZQUNqQyxNQUFNLE1BQU0sR0FBRyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsaUJBQWlCLEVBQUUsRUFBRSxjQUFjLEVBQUUsQ0FBQyxDQUFDO1lBQzdFLFFBQVEsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDO1lBQ3ZCLElBQUksR0FBRyxNQUFNLENBQUMsT0FBTyxDQUFDO1FBQ3hCLENBQUM7SUFDSCxDQUFDO0lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztRQUNiLE9BQU87WUFDTCxLQUFLLEVBQUUsS0FBSztZQUNaLE9BQU8sRUFBRSxpQkFBaUI7WUFDMUIsUUFBUSxFQUFFLEVBQUU7WUFDWixJQUFJLEVBQUUsRUFBRTtZQUNSLFNBQVMsRUFBRSxFQUFFLE1BQU0sRUFBRSw0QkFBNkIsR0FBYSxDQUFDLE9BQU8sRUFBRSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUU7U0FDOUYsQ0FBQztJQUNKLENBQUM7SUFFRCx1RUFBdUU7SUFDdkUsc0VBQXNFO0lBQ3RFLHVFQUF1RTtJQUN2RSwrRUFBK0U7SUFDL0UsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osTUFBTSxVQUFVLEdBQTRCLGdCQUFnQixDQUFDLG1CQUFtQixDQUFDLGlCQUFpQixFQUFFO1lBQ2xHLGNBQWM7U0FDZixDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3hCLE1BQU0sQ0FBQyxJQUFJLENBQUMsb0NBQW9DLEVBQUU7Z0JBQ2hELFFBQVE7Z0JBQ1IsV0FBVztnQkFDWCxRQUFRLEVBQUUsVUFBVSxDQUFDLGdCQUFnQjtnQkFDckMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxRQUFRO2FBQzlCLENBQUMsQ0FBQztZQUNILE9BQU87Z0JBQ0wsS0FBSyxFQUFFLEtBQUs7Z0JBQ1osT0FBTyxFQUFFLGlCQUFpQjtnQkFDMUIsUUFBUTtnQkFDUixJQUFJO2dCQUNKLFNBQVMsRUFBRTtvQkFDVCxNQUFNLEVBQUUsb0NBQW9DO29CQUM1QyxRQUFRLEVBQUUsVUFBVSxDQUFDLFFBQVE7b0JBQzdCLFFBQVEsRUFBRSxVQUFVLENBQUMsZ0JBQWdCO2lCQUN0QzthQUNGLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQztJQUVELG1DQUFtQztJQUNuQyxPQUFPO1FBQ0wsS0FBSyxFQUFFLElBQUk7UUFDWCxPQUFPLEVBQUUsVUFBVTtRQUNuQixRQUFRO1FBQ1IsSUFBSTtLQUNMLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb250ZW50IFZhbGlkYXRpb24gUGlwZWxpbmUgZm9yIFdlYiBVSVxuICpcbiAqIENvbXBvc2VzIHRoZSBzdGFuZGFsb25lIHNlY3VyaXR5IHZhbGlkYXRvcnMgaW50byBhIHNpbmdsZSBwaXBlbGluZVxuICogZm9yIHZhbGlkYXRpbmcgcG9ydGZvbGlvIGVsZW1lbnQgY29udGVudCBiZWZvcmUgc2VydmluZyB0byB0aGUgYnJvd3Nlci5cbiAqXG4gKiBVc2VzIHRoZSBzYW1lIHZhbGlkYXRvcnMgYXMgdGhlIE1DUEFRTEhhbmRsZXIgYnV0IHdpdGhvdXQgY291cGxpbmdcbiAqIHRvIHRoZSBoYW5kbGVyJ3Mgb3BlcmF0aW9uIGRpc3BhdGNoLiBUaGlzIGlzIHRoZSBleHRyYWN0aW9uIHBvaW50XG4gKiBmb3IgdGhlIHVwY29taW5nIE1DUEFRTEhhbmRsZXIgZGVjb21wb3NpdGlvbi5cbiAqXG4gKiBhdXRvLWRvbGxob3VzZSM1IC8gdXBzdHJlYW0gIzE2NzlcbiAqIERNQ1AtU0VDLTAwNCBjb21wbGlhbnQ6IHVzZXMgVW5pY29kZVZhbGlkYXRvci5ub3JtYWxpemUoKSBvbiBhbGwgaW5wdXRzXG4gKi9cblxuaW1wb3J0IHsgZXh0bmFtZSB9IGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBTZWN1cmVZYW1sUGFyc2VyIH0gZnJvbSAnLi4vc2VjdXJpdHkvc2VjdXJlWWFtbFBhcnNlci5qcyc7XG5pbXBvcnQgeyBDb250ZW50VmFsaWRhdG9yLCB0eXBlIENvbnRlbnRWYWxpZGF0aW9uUmVzdWx0IH0gZnJvbSAnLi4vc2VjdXJpdHkvY29udGVudFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbi8qKiBFbGVtZW50IHR5cGVzIHRoYXQgbWFwIHRvIGNvbnRlbnQgdmFsaWRhdGlvbiBjb250ZXh0cyAqL1xuY29uc3QgVFlQRV9UT19DT05URVhUOiBSZWNvcmQ8c3RyaW5nLCAncGVyc29uYScgfCAnc2tpbGwnIHwgJ3RlbXBsYXRlJyB8ICdhZ2VudCcgfCAnbWVtb3J5Jz4gPSB7XG4gIHBlcnNvbmFzOiAncGVyc29uYScsXG4gIHNraWxsczogJ3NraWxsJyxcbiAgdGVtcGxhdGVzOiAndGVtcGxhdGUnLFxuICBhZ2VudHM6ICdhZ2VudCcsXG4gIG1lbW9yaWVzOiAnbWVtb3J5Jyxcbn07XG5cbi8qKiBLbm93biBtZXRhZGF0YSBmaWVsZHMgZXh0cmFjdGVkIGZyb20gWUFNTCBmcm9udG1hdHRlciBmb3Igd2ViIGRpc3BsYXkgKi9cbmV4cG9ydCBpbnRlcmZhY2UgRWxlbWVudERpc3BsYXlNZXRhZGF0YSB7XG4gIG5hbWU/OiBzdHJpbmc7XG4gIGRlc2NyaXB0aW9uPzogc3RyaW5nO1xuICB2ZXJzaW9uPzogc3RyaW5nO1xuICBhdXRob3I/OiBzdHJpbmc7XG4gIGNhdGVnb3J5Pzogc3RyaW5nO1xuICBjcmVhdGVkPzogc3RyaW5nO1xuICBjcmVhdGVkX2RhdGU/OiBzdHJpbmc7XG4gIG1vZGlmaWVkPzogc3RyaW5nO1xuICB0YWdzPzogc3RyaW5nW107XG4gIGxpY2Vuc2U/OiBzdHJpbmc7XG4gIGFnZV9yYXRpbmc/OiBzdHJpbmc7XG4gIHRyaWdnZXJzPzogc3RyaW5nW107XG4gIGluc3RydWN0aW9ucz86IHN0cmluZztcbiAgY29vcmRpbmF0aW9uX3N0cmF0ZWd5Pzogc3RyaW5nO1xuICB1c2VfY2FzZXM/OiBzdHJpbmdbXTtcbiAgcHJvZmljaWVuY3lfbGV2ZWxzPzogUmVjb3JkPHN0cmluZywgc3RyaW5nPjtcbiAgZ2F0ZWtlZXBlcj86IFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICBnb2FsPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGF1dG9ub215PzogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIG1lbW9yeVR5cGU/OiBzdHJpbmc7XG4gIFtrZXk6IHN0cmluZ106IHVua25vd247XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGlwZWxpbmVSZXN1bHQge1xuICB2YWxpZDogYm9vbGVhbjtcbiAgY29udGVudDogc3RyaW5nO1xuICBtZXRhZGF0YTogRWxlbWVudERpc3BsYXlNZXRhZGF0YTtcbiAgYm9keTogc3RyaW5nO1xuICByZWplY3Rpb24/OiB7XG4gICAgcmVhc29uOiBzdHJpbmc7XG4gICAgc2V2ZXJpdHk/OiBzdHJpbmc7XG4gICAgcGF0dGVybnM/OiBzdHJpbmdbXTtcbiAgfTtcbn1cblxuLyoqXG4gKiBWYWxpZGF0ZSBlbGVtZW50IGNvbnRlbnQgdGhyb3VnaCB0aGUgc2VjdXJpdHkgcGlwZWxpbmUuXG4gKlxuICogQHBhcmFtIGZpbGVuYW1lIC0gVGhlIGVsZW1lbnQgZmlsZW5hbWUgKGUuZy4sIFwiYWxleC1zdGVybGluZy5tZFwiKVxuICogQHBhcmFtIHJhd0NvbnRlbnQgLSBUaGUgcmF3IGZpbGUgY29udGVudCBzdHJpbmdcbiAqIEBwYXJhbSBlbGVtZW50VHlwZSAtIFRoZSBwbHVyYWwgZWxlbWVudCB0eXBlIGRpcmVjdG9yeSAoZS5nLiwgXCJwZXJzb25hc1wiKVxuICogQHJldHVybnMgVmFsaWRhdGVkIGNvbnRlbnQgd2l0aCBwYXJzZWQgbWV0YWRhdGEsIG9yIHJlamVjdGlvbiB3aXRoIHJlYXNvblxuICovXG5leHBvcnQgZnVuY3Rpb24gdmFsaWRhdGVFbGVtZW50Q29udGVudChcbiAgZmlsZW5hbWU6IHN0cmluZyxcbiAgcmF3Q29udGVudDogc3RyaW5nLFxuICBlbGVtZW50VHlwZTogc3RyaW5nLFxuKTogUGlwZWxpbmVSZXN1bHQge1xuICAvLyBETUNQLVNFQy0wMDQ6IE5vcm1hbGl6ZSBhbGwgaW5wdXRzIHZpYSBVbmljb2RlVmFsaWRhdG9yIHRvIHByZXZlbnRcbiAgLy8gaG9tb2dyYXBoIGF0dGFja3MsIGRpcmVjdGlvbiBvdmVycmlkZSBieXBhc3NlcywgYW5kIHN1c3BpY2lvdXMgcGF0dGVybnMuXG4gIGNvbnN0IGZpbGVuYW1lVmFsaWRhdGlvbiA9IFVuaWNvZGVWYWxpZGF0b3Iubm9ybWFsaXplKGZpbGVuYW1lKTtcbiAgY29uc3QgY29udGVudFZhbGlkYXRpb24gPSBVbmljb2RlVmFsaWRhdG9yLm5vcm1hbGl6ZShyYXdDb250ZW50KTtcbiAgY29uc3Qgbm9ybWFsaXplZEZpbGVuYW1lID0gZmlsZW5hbWVWYWxpZGF0aW9uLm5vcm1hbGl6ZWRDb250ZW50O1xuICBjb25zdCBub3JtYWxpemVkQ29udGVudCA9IGNvbnRlbnRWYWxpZGF0aW9uLm5vcm1hbGl6ZWRDb250ZW50O1xuICBjb25zdCBub3JtYWxpemVkVHlwZSA9IGVsZW1lbnRUeXBlLm5vcm1hbGl6ZSgnTkZDJyk7XG5cbiAgY29uc3QgZXh0ID0gZXh0bmFtZShub3JtYWxpemVkRmlsZW5hbWUpO1xuICBjb25zdCBpc1lhbWwgPSBleHQgPT09ICcueWFtbCcgfHwgZXh0ID09PSAnLnltbCc7XG4gIGNvbnN0IGNvbnRlbnRDb250ZXh0ID0gVFlQRV9UT19DT05URVhUW25vcm1hbGl6ZWRUeXBlXTtcblxuICAvLyBTdGVwIDE6IFBhcnNlIGFuZCB2YWxpZGF0ZSBzdHJ1Y3R1cmUgKFlBTUwgYm9tYiBkZXRlY3Rpb24sIGNpcmN1bGFyIHJlZnMpXG4gIGxldCBtZXRhZGF0YTogRWxlbWVudERpc3BsYXlNZXRhZGF0YSA9IHt9O1xuICBsZXQgYm9keSA9ICcnO1xuXG4gIHRyeSB7XG4gICAgaWYgKGlzWWFtbCkge1xuICAgICAgLy8gUHVyZSBZQU1MIGZpbGUgKG1lbW9yaWVzKSDigJQgdXNlIHBhcnNlUmF3WWFtbCBmb3Igc3RydWN0dXJhbCB2YWxpZGF0aW9uXG4gICAgICAvLyAoYm9tYiBkZXRlY3Rpb24sIGNpcmN1bGFyIHJlZnMsIHNpemUgbGltaXRzKS5cbiAgICAgIC8vIFNraXAgQ29udGVudFZhbGlkYXRvci52YWxpZGF0ZVlhbWxDb250ZW50KCkg4oCUIGl0IHByb2R1Y2VzIGZhbHNlIHBvc2l0aXZlc1xuICAgICAgLy8gb24gbWVtb3J5IGNvbnRlbnQgdGhhdCBsZWdpdGltYXRlbHkgY29udGFpbnMgY29kZSBwYXR0ZXJucywgc2VjdXJpdHlcbiAgICAgIC8vIGtleXdvcmRzLCBhbmQgdGVjaG5pY2FsIGRvY3VtZW50YXRpb24uIE1lbW9yaWVzIGFyZSBsb2NhbGx5LWdlbmVyYXRlZFxuICAgICAgLy8gdHJ1c3RlZCBjb250ZW50LCBub3QgdW50cnVzdGVkIGV4dGVybmFsIHN1Ym1pc3Npb25zLlxuICAgICAgY29uc3QgcGFyc2VkID0gU2VjdXJlWWFtbFBhcnNlci5wYXJzZVJhd1lhbWwobm9ybWFsaXplZENvbnRlbnQpO1xuICAgICAgbWV0YWRhdGEgPSAodHlwZW9mIHBhcnNlZCA9PT0gJ29iamVjdCcgJiYgcGFyc2VkICE9PSBudWxsKSA/IHBhcnNlZCA6IHt9O1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyBNYXJrZG93biB3aXRoIFlBTUwgZnJvbnRtYXR0ZXJcbiAgICAgIGNvbnN0IHBhcnNlZCA9IFNlY3VyZVlhbWxQYXJzZXIucGFyc2Uobm9ybWFsaXplZENvbnRlbnQsIHsgY29udGVudENvbnRleHQgfSk7XG4gICAgICBtZXRhZGF0YSA9IHBhcnNlZC5kYXRhO1xuICAgICAgYm9keSA9IHBhcnNlZC5jb250ZW50O1xuICAgIH1cbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHZhbGlkOiBmYWxzZSxcbiAgICAgIGNvbnRlbnQ6IG5vcm1hbGl6ZWRDb250ZW50LFxuICAgICAgbWV0YWRhdGE6IHt9LFxuICAgICAgYm9keTogJycsXG4gICAgICByZWplY3Rpb246IHsgcmVhc29uOiBgUGFyc2UgdmFsaWRhdGlvbiBmYWlsZWQ6ICR7KGVyciBhcyBFcnJvcikubWVzc2FnZX1gLCBzZXZlcml0eTogJ2hpZ2gnIH0sXG4gICAgfTtcbiAgfVxuXG4gIC8vIFN0ZXAgMjogQ29udGVudCBpbmplY3Rpb24gcGF0dGVybiBkZXRlY3Rpb24gKG1hcmtkb3duIGVsZW1lbnRzIG9ubHkpXG4gIC8vIFlBTUwgbWVtb3JpZXMgc2tpcCB0aGlzIOKAlCB0aGV5IGNvbnRhaW4gbGVnaXRpbWF0ZSBjb2RlIHBhdHRlcm5zIGFuZFxuICAvLyB0ZWNobmljYWwgY29udGVudCB0aGF0IHRyaWdnZXJzIGZhbHNlIHBvc2l0aXZlcy4gVGhlIHN0cnVjdHVyYWwgWUFNTFxuICAvLyBwYXJzaW5nIGFib3ZlIChib21iL2NpcmN1bGFyIHJlZiBkZXRlY3Rpb24pIGlzIHN1ZmZpY2llbnQgZm9yIGxvY2FsIGNvbnRlbnQuXG4gIGlmICghaXNZYW1sKSB7XG4gICAgY29uc3QgdmFsaWRhdGlvbjogQ29udGVudFZhbGlkYXRpb25SZXN1bHQgPSBDb250ZW50VmFsaWRhdG9yLnZhbGlkYXRlQW5kU2FuaXRpemUobm9ybWFsaXplZENvbnRlbnQsIHtcbiAgICAgIGNvbnRlbnRDb250ZXh0LFxuICAgIH0pO1xuXG4gICAgaWYgKCF2YWxpZGF0aW9uLmlzVmFsaWQpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdbQ29udGVudFBpcGVsaW5lXSBDb250ZW50IHJlamVjdGVkJywge1xuICAgICAgICBmaWxlbmFtZSxcbiAgICAgICAgZWxlbWVudFR5cGUsXG4gICAgICAgIHBhdHRlcm5zOiB2YWxpZGF0aW9uLmRldGVjdGVkUGF0dGVybnMsXG4gICAgICAgIHNldmVyaXR5OiB2YWxpZGF0aW9uLnNldmVyaXR5LFxuICAgICAgfSk7XG4gICAgICByZXR1cm4ge1xuICAgICAgICB2YWxpZDogZmFsc2UsXG4gICAgICAgIGNvbnRlbnQ6IG5vcm1hbGl6ZWRDb250ZW50LFxuICAgICAgICBtZXRhZGF0YSxcbiAgICAgICAgYm9keSxcbiAgICAgICAgcmVqZWN0aW9uOiB7XG4gICAgICAgICAgcmVhc29uOiAnQ29udGVudCBmYWlsZWQgc2VjdXJpdHkgdmFsaWRhdGlvbicsXG4gICAgICAgICAgc2V2ZXJpdHk6IHZhbGlkYXRpb24uc2V2ZXJpdHksXG4gICAgICAgICAgcGF0dGVybnM6IHZhbGlkYXRpb24uZGV0ZWN0ZWRQYXR0ZXJucyxcbiAgICAgICAgfSxcbiAgICAgIH07XG4gICAgfVxuICB9XG5cbiAgLy8gU3RlcCAzOiBSZXR1cm4gdmFsaWRhdGVkIGNvbnRlbnRcbiAgcmV0dXJuIHtcbiAgICB2YWxpZDogdHJ1ZSxcbiAgICBjb250ZW50OiByYXdDb250ZW50LFxuICAgIG1ldGFkYXRhLFxuICAgIGJvZHksXG4gIH07XG59XG4iXX0=
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic port allocation and port file discovery for the web console.
|
|
3
|
+
*
|
|
4
|
+
* When multiple DollhouseMCP sessions run simultaneously (e.g., multiple
|
|
5
|
+
* Claude Code windows, IDE instances, or agent sessions), each needs its
|
|
6
|
+
* own web console port. This module handles:
|
|
7
|
+
*
|
|
8
|
+
* 1. Finding an available port starting from the default (3939)
|
|
9
|
+
* 2. Writing port discovery files so external tools know which port to use
|
|
10
|
+
* 3. Cleaning up port files on process exit
|
|
11
|
+
*
|
|
12
|
+
* Port files are written to ~/.dollhouse/run/:
|
|
13
|
+
* - permission-server-{pid}.port — per-process file (cleaned on exit)
|
|
14
|
+
* - permission-server.port — latest port (convenience for scripts)
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Find an available port starting from the given port.
|
|
18
|
+
*
|
|
19
|
+
* Tries sequential ports, logging each attempt. Returns both the port number
|
|
20
|
+
* and a held server to prevent TOCTOU race conditions. The caller should
|
|
21
|
+
* close the held server after binding their own Express app to the port.
|
|
22
|
+
*
|
|
23
|
+
* @param startPort - Port to try first (default: 3939)
|
|
24
|
+
* @param maxAttempts - Maximum ports to try (default: 10, configurable via DOLLHOUSE_MAX_PORT_ATTEMPTS)
|
|
25
|
+
* @returns Object with port number and held server, or throws if all attempts fail
|
|
26
|
+
*/
|
|
27
|
+
export declare function findAvailablePort(startPort: number, maxAttempts?: number): Promise<number>;
|
|
28
|
+
/**
|
|
29
|
+
* Write the active server port to a discoverable file.
|
|
30
|
+
*
|
|
31
|
+
* Creates two files:
|
|
32
|
+
* - PID-keyed file for per-process cleanup
|
|
33
|
+
* - Latest file for convenience (scripts can read this without knowing the PID)
|
|
34
|
+
*
|
|
35
|
+
* @param port - The port the web server is listening on
|
|
36
|
+
* @returns Path to the PID-keyed port file
|
|
37
|
+
*/
|
|
38
|
+
export declare function writePortFile(port: number): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Clean up port file on shutdown.
|
|
41
|
+
*/
|
|
42
|
+
export declare function cleanupPortFile(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Register process exit handlers to clean up port files.
|
|
45
|
+
* Should be called once after the web server successfully starts.
|
|
46
|
+
*/
|
|
47
|
+
export declare function registerPortCleanup(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Discover an available port, write discovery files, and register cleanup.
|
|
50
|
+
*
|
|
51
|
+
* This is the recommended entry point for Container startup. Combines
|
|
52
|
+
* port discovery, file writing, and cleanup registration in one call.
|
|
53
|
+
*
|
|
54
|
+
* @param defaultPort - Port to try first (default: 3939)
|
|
55
|
+
* @returns The port the server should bind to, or undefined if discovery failed
|
|
56
|
+
*/
|
|
57
|
+
export declare function discoverAndBindPort(defaultPort?: number): Promise<number | undefined>;
|
|
58
|
+
//# sourceMappingURL=portDiscovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portDiscovery.d.ts","sourceRoot":"","sources":["../../src/web/portDiscovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAwCH;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,WAAW,GAAE,MAAqF,GACjG,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASjE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAIrD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,GAAE,MAAa,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAYjG"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic port allocation and port file discovery for the web console.
|
|
3
|
+
*
|
|
4
|
+
* When multiple DollhouseMCP sessions run simultaneously (e.g., multiple
|
|
5
|
+
* Claude Code windows, IDE instances, or agent sessions), each needs its
|
|
6
|
+
* own web console port. This module handles:
|
|
7
|
+
*
|
|
8
|
+
* 1. Finding an available port starting from the default (3939)
|
|
9
|
+
* 2. Writing port discovery files so external tools know which port to use
|
|
10
|
+
* 3. Cleaning up port files on process exit
|
|
11
|
+
*
|
|
12
|
+
* Port files are written to ~/.dollhouse/run/:
|
|
13
|
+
* - permission-server-{pid}.port — per-process file (cleaned on exit)
|
|
14
|
+
* - permission-server.port — latest port (convenience for scripts)
|
|
15
|
+
*/
|
|
16
|
+
import { createServer } from 'node:net';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { mkdir, writeFile, unlink } from 'node:fs/promises';
|
|
20
|
+
import { logger } from '../utils/logger.js';
|
|
21
|
+
/** Default maximum port attempts before giving up */
|
|
22
|
+
const DEFAULT_MAX_PORT_ATTEMPTS = 10;
|
|
23
|
+
/** Directory for runtime state files (port discovery, PID files) */
|
|
24
|
+
const RUN_DIR = join(homedir(), '.dollhouse', 'run');
|
|
25
|
+
/** Track port file path for cleanup */
|
|
26
|
+
let portFilePath = null;
|
|
27
|
+
/**
|
|
28
|
+
* Attempt to bind to a single port. Returns the port if successful, null if in use.
|
|
29
|
+
* Keeps the server listening to prevent TOCTOU race conditions — caller must
|
|
30
|
+
* close the returned server after their own server binds.
|
|
31
|
+
*/
|
|
32
|
+
function tryBindPort(port) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const server = createServer();
|
|
35
|
+
server.once('error', (err) => {
|
|
36
|
+
if (err.code === 'EADDRINUSE') {
|
|
37
|
+
resolve(null);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
resolve(null);
|
|
41
|
+
logger.debug(`[PortDiscovery] Unexpected error on port ${port}: ${err.code}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
server.once('listening', () => {
|
|
45
|
+
resolve({ port, server });
|
|
46
|
+
});
|
|
47
|
+
server.listen(port, '127.0.0.1');
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Find an available port starting from the given port.
|
|
52
|
+
*
|
|
53
|
+
* Tries sequential ports, logging each attempt. Returns both the port number
|
|
54
|
+
* and a held server to prevent TOCTOU race conditions. The caller should
|
|
55
|
+
* close the held server after binding their own Express app to the port.
|
|
56
|
+
*
|
|
57
|
+
* @param startPort - Port to try first (default: 3939)
|
|
58
|
+
* @param maxAttempts - Maximum ports to try (default: 10, configurable via DOLLHOUSE_MAX_PORT_ATTEMPTS)
|
|
59
|
+
* @returns Object with port number and held server, or throws if all attempts fail
|
|
60
|
+
*/
|
|
61
|
+
export async function findAvailablePort(startPort, maxAttempts = Number(process.env.DOLLHOUSE_MAX_PORT_ATTEMPTS) || DEFAULT_MAX_PORT_ATTEMPTS) {
|
|
62
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
63
|
+
const port = startPort + attempt;
|
|
64
|
+
logger.debug(`[PortDiscovery] Trying port ${port} (attempt ${attempt + 1}/${maxAttempts})`);
|
|
65
|
+
const result = await tryBindPort(port);
|
|
66
|
+
if (result) {
|
|
67
|
+
// Close the probe server — there's a brief TOCTOU window here between
|
|
68
|
+
// closing and the caller binding, but it's negligible on localhost.
|
|
69
|
+
// A future improvement could return the server for the caller to adopt.
|
|
70
|
+
result.server.close();
|
|
71
|
+
if (attempt > 0) {
|
|
72
|
+
logger.info(`[PortDiscovery] Port ${startPort} in use, using ${port} (after ${attempt} skip${attempt > 1 ? 's' : ''})`);
|
|
73
|
+
}
|
|
74
|
+
return port;
|
|
75
|
+
}
|
|
76
|
+
logger.debug(`[PortDiscovery] Port ${port} in use, trying next`);
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1} ` +
|
|
79
|
+
`after ${maxAttempts} attempts. Set DOLLHOUSE_MAX_PORT_ATTEMPTS to increase the range.`);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Write the active server port to a discoverable file.
|
|
83
|
+
*
|
|
84
|
+
* Creates two files:
|
|
85
|
+
* - PID-keyed file for per-process cleanup
|
|
86
|
+
* - Latest file for convenience (scripts can read this without knowing the PID)
|
|
87
|
+
*
|
|
88
|
+
* @param port - The port the web server is listening on
|
|
89
|
+
* @returns Path to the PID-keyed port file
|
|
90
|
+
*/
|
|
91
|
+
export async function writePortFile(port) {
|
|
92
|
+
await mkdir(RUN_DIR, { recursive: true });
|
|
93
|
+
const pidFile = join(RUN_DIR, `permission-server-${process.pid}.port`);
|
|
94
|
+
const latestFile = join(RUN_DIR, 'permission-server.port');
|
|
95
|
+
await writeFile(pidFile, String(port), 'utf-8');
|
|
96
|
+
await writeFile(latestFile, String(port), 'utf-8');
|
|
97
|
+
portFilePath = pidFile;
|
|
98
|
+
logger.debug(`[PortDiscovery] Port file written: ${pidFile}`);
|
|
99
|
+
return pidFile;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Clean up port file on shutdown.
|
|
103
|
+
*/
|
|
104
|
+
export async function cleanupPortFile() {
|
|
105
|
+
if (portFilePath) {
|
|
106
|
+
try {
|
|
107
|
+
await unlink(portFilePath);
|
|
108
|
+
}
|
|
109
|
+
catch { /* already gone */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Register process exit handlers to clean up port files.
|
|
114
|
+
* Should be called once after the web server successfully starts.
|
|
115
|
+
*/
|
|
116
|
+
export function registerPortCleanup() {
|
|
117
|
+
const exitCleanup = () => { cleanupPortFile().catch(() => { }); };
|
|
118
|
+
process.once('exit', exitCleanup);
|
|
119
|
+
process.once('SIGTERM', exitCleanup);
|
|
120
|
+
process.once('SIGINT', exitCleanup);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Discover an available port, write discovery files, and register cleanup.
|
|
124
|
+
*
|
|
125
|
+
* This is the recommended entry point for Container startup. Combines
|
|
126
|
+
* port discovery, file writing, and cleanup registration in one call.
|
|
127
|
+
*
|
|
128
|
+
* @param defaultPort - Port to try first (default: 3939)
|
|
129
|
+
* @returns The port the server should bind to, or undefined if discovery failed
|
|
130
|
+
*/
|
|
131
|
+
export async function discoverAndBindPort(defaultPort = 3939) {
|
|
132
|
+
try {
|
|
133
|
+
const port = await findAvailablePort(defaultPort);
|
|
134
|
+
await writePortFile(port);
|
|
135
|
+
registerPortCleanup();
|
|
136
|
+
return port;
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
logger.warn('[PortDiscovery] Port discovery failed:', err);
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9ydERpc2NvdmVyeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93ZWIvcG9ydERpc2NvdmVyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUVILE9BQU8sRUFBRSxZQUFZLEVBQWUsTUFBTSxVQUFVLENBQUM7QUFDckQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNsQyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2pDLE9BQU8sRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0sRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzVELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUU1QyxxREFBcUQ7QUFDckQsTUFBTSx5QkFBeUIsR0FBRyxFQUFFLENBQUM7QUFFckMsb0VBQW9FO0FBQ3BFLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLEVBQUUsRUFBRSxZQUFZLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFFckQsdUNBQXVDO0FBQ3ZDLElBQUksWUFBWSxHQUFrQixJQUFJLENBQUM7QUFFdkM7Ozs7R0FJRztBQUNILFNBQVMsV0FBVyxDQUFDLElBQVk7SUFDL0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFO1FBQzdCLE1BQU0sTUFBTSxHQUFHLFlBQVksRUFBRSxDQUFDO1FBQzlCLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsR0FBMEIsRUFBRSxFQUFFO1lBQ2xELElBQUksR0FBRyxDQUFDLElBQUksS0FBSyxZQUFZLEVBQUUsQ0FBQztnQkFDOUIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ2hCLENBQUM7aUJBQU0sQ0FBQztnQkFDTixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7Z0JBQ2QsTUFBTSxDQUFDLEtBQUssQ0FBQyw0Q0FBNEMsSUFBSSxLQUFL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a0JBQWtCLENBQUMsQ0FBQztJQUNsRSxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sVUFBVSxtQkFBbUI7SUFDakMsTUFBTSxXQUFXLEdBQUcsR0FBRyxFQUFFLEdBQUcsZUFBZSxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pFLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLFdBQVcsQ0FBQyxDQUFDO0lBQ2xDLE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLFdBQVcsQ0FBQyxDQUFDO0lBQ3JDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFDO0FBQ3RDLENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsbUJBQW1CLENBQUMsY0FBc0IsSUFBSTtJQUNsRSxJQUFJLENBQUM7UUFDSCxNQUFNLElBQUksR0FBRyxNQUFNLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBRWxELE1BQU0sYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzFCLG1CQUFtQixFQUFFLENBQUM7UUFFdEIsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztRQUNiLE1BQU0sQ0FBQyxJQUFJLENBQUMsd0NBQXdDLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDM0QsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIER5bmFtaWMgcG9ydCBhbGxvY2F0aW9uIGFuZCBwb3J0IGZpbGUgZGlzY292ZXJ5IGZvciB0aGUgd2ViIGNvbnNvbGUuXG4gKlxuICogV2hlbiBtdWx0aXBsZSBEb2xsaG91c2VNQ1Agc2Vzc2lvbnMgcnVuIHNpbXVsdGFuZW91c2x5IChlLmcuLCBtdWx0aXBsZVxuICogQ2xhdWRlIENvZGUgd2luZG93cywgSURFIGluc3RhbmNlcywgb3IgYWdlbnQgc2Vzc2lvbnMpLCBlYWNoIG5lZWRzIGl0c1xuICogb3duIHdlYiBjb25zb2xlIHBvcnQuIFRoaXMgbW9kdWxlIGhhbmRsZXM6XG4gKlxuICogMS4gRmluZGluZyBhbiBhdmFpbGFibGUgcG9ydCBzdGFydGluZyBmcm9tIHRoZSBkZWZhdWx0ICgzOTM5KVxuICogMi4gV3JpdGluZyBwb3J0IGRpc2NvdmVyeSBmaWxlcyBzbyBleHRlcm5hbCB0b29scyBrbm93IHdoaWNoIHBvcnQgdG8gdXNlXG4gKiAzLiBDbGVhbmluZyB1cCBwb3J0IGZpbGVzIG9uIHByb2Nlc3MgZXhpdFxuICpcbiAqIFBvcnQgZmlsZXMgYXJlIHdyaXR0ZW4gdG8gfi8uZG9sbGhvdXNlL3J1bi86XG4gKiAtIHBlcm1pc3Npb24tc2VydmVyLXtwaWR9LnBvcnQg4oCUIHBlci1wcm9jZXNzIGZpbGUgKGNsZWFuZWQgb24gZXhpdClcbiAqIC0gcGVybWlzc2lvbi1zZXJ2ZXIucG9ydCDigJQgbGF0ZXN0IHBvcnQgKGNvbnZlbmllbmNlIGZvciBzY3JpcHRzKVxuICovXG5cbmltcG9ydCB7IGNyZWF0ZVNlcnZlciwgdHlwZSBTZXJ2ZXIgfSBmcm9tICdub2RlOm5ldCc7XG5pbXBvcnQgeyBob21lZGlyIH0gZnJvbSAnbm9kZTpvcyc7XG5pbXBvcnQgeyBqb2luIH0gZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IG1rZGlyLCB3cml0ZUZpbGUsIHVubGluayB9IGZyb20gJ25vZGU6ZnMvcHJvbWlzZXMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuLyoqIERlZmF1bHQgbWF4aW11bSBwb3J0IGF0dGVtcHRzIGJlZm9yZSBnaXZpbmcgdXAgKi9cbmNvbnN0IERFRkFVTFRfTUFYX1BPUlRfQVRURU1QVFMgPSAxMDtcblxuLyoqIERpcmVjdG9yeSBmb3IgcnVudGltZSBzdGF0ZSBmaWxlcyAocG9ydCBkaXNjb3ZlcnksIFBJRCBmaWxlcykgKi9cbmNvbnN0IFJVTl9ESVIgPSBqb2luKGhvbWVkaXIoKSwgJy5kb2xsaG91c2UnLCAncnVuJyk7XG5cbi8qKiBUcmFjayBwb3J0IGZpbGUgcGF0aCBmb3IgY2xlYW51cCAqL1xubGV0IHBvcnRGaWxlUGF0aDogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG5cbi8qKlxuICogQXR0ZW1wdCB0byBiaW5kIHRvIGEgc2luZ2xlIHBvcnQuIFJldHVybnMgdGhlIHBvcnQgaWYgc3VjY2Vzc2Z1bCwgbnVsbCBpZiBpbiB1c2UuXG4gKiBLZWVwcyB0aGUgc2VydmVyIGxpc3RlbmluZyB0byBwcmV2ZW50IFRPQ1RPVSByYWNlIGNvbmRpdGlvbnMg4oCUIGNhbGxlciBtdXN0XG4gKiBjbG9zZSB0aGUgcmV0dXJuZWQgc2VydmVyIGFmdGVyIHRoZWlyIG93biBzZXJ2ZXIgYmluZHMuXG4gKi9cbmZ1bmN0aW9uIHRyeUJpbmRQb3J0KHBvcnQ6IG51bWJlcik6IFByb21pc2U8eyBwb3J0OiBudW1iZXI7IHNlcnZlcjogU2VydmVyIH0gfCBudWxsPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4ge1xuICAgIGNvbnN0IHNlcnZlciA9IGNyZWF0ZVNlcnZlcigpO1xuICAgIHNlcnZlci5vbmNlKCdlcnJvcicsIChlcnI6IE5vZGVKUy5FcnJub0V4Y2VwdGlvbikgPT4ge1xuICAgICAgaWYgKGVyci5jb2RlID09PSAnRUFERFJJTlVTRScpIHtcbiAgICAgICAgcmVzb2x2ZShudWxsKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJlc29sdmUobnVsbCk7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhgW1BvcnREaXNjb3ZlcnldIFVuZXhwZWN0ZWQgZXJyb3Igb24gcG9ydCAke3BvcnR9OiAke2Vyci5jb2RlfWApO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHNlcnZlci5vbmNlKCdsaXN0ZW5pbmcnLCAoKSA9PiB7XG4gICAgICByZXNvbHZlKHsgcG9ydCwgc2VydmVyIH0pO1xuICAgIH0pO1xuICAgIHNlcnZlci5saXN0ZW4ocG9ydCwgJzEyNy4wLjAuMScpO1xuICB9KTtcbn1cblxuLyoqXG4gKiBGaW5kIGFuIGF2YWlsYWJsZSBwb3J0IHN0YXJ0aW5nIGZyb20gdGhlIGdpdmVuIHBvcnQuXG4gKlxuICogVHJpZXMgc2VxdWVudGlhbCBwb3J0cywgbG9nZ2luZyBlYWNoIGF0dGVtcHQuIFJldHVybnMgYm90aCB0aGUgcG9ydCBudW1iZXJcbiAqIGFuZCBhIGhlbGQgc2VydmVyIHRvIHByZXZlbnQgVE9DVE9VIHJhY2UgY29uZGl0aW9ucy4gVGhlIGNhbGxlciBzaG91bGRcbiAqIGNsb3NlIHRoZSBoZWxkIHNlcnZlciBhZnRlciBiaW5kaW5nIHRoZWlyIG93biBFeHByZXNzIGFwcCB0byB0aGUgcG9ydC5cbiAqXG4gKiBAcGFyYW0gc3RhcnRQb3J0IC0gUG9ydCB0byB0cnkgZmlyc3QgKGRlZmF1bHQ6IDM5MzkpXG4gKiBAcGFyYW0gbWF4QXR0ZW1wdHMgLSBNYXhpbXVtIHBvcnRzIHRvIHRyeSAoZGVmYXVsdDogMTAsIGNvbmZpZ3VyYWJsZSB2aWEgRE9MTEhPVVNFX01BWF9QT1JUX0FUVEVNUFRTKVxuICogQHJldHVybnMgT2JqZWN0IHdpdGggcG9ydCBudW1iZXIgYW5kIGhlbGQgc2VydmVyLCBvciB0aHJvd3MgaWYgYWxsIGF0dGVtcHRzIGZhaWxcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmRBdmFpbGFibGVQb3J0KFxuICBzdGFydFBvcnQ6IG51bWJlcixcbiAgbWF4QXR0ZW1wdHM6IG51bWJlciA9IE51bWJlcihwcm9jZXNzLmVudi5ET0xMSE9VU0VfTUFYX1BPUlRfQVRURU1QVFMpIHx8IERFRkFVTFRfTUFYX1BPUlRfQVRURU1QVFMsXG4pOiBQcm9taXNlPG51bWJlcj4ge1xuICBmb3IgKGxldCBhdHRlbXB0ID0gMDsgYXR0ZW1wdCA8IG1heEF0dGVtcHRzOyBhdHRlbXB0KyspIHtcbiAgICBjb25zdCBwb3J0ID0gc3RhcnRQb3J0ICsgYXR0ZW1wdDtcbiAgICBsb2dnZXIuZGVidWcoYFtQb3J0RGlzY292ZXJ5XSBUcnlpbmcgcG9ydCAke3BvcnR9IChhdHRlbXB0ICR7YXR0ZW1wdCArIDF9LyR7bWF4QXR0ZW1wdHN9KWApO1xuXG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdHJ5QmluZFBvcnQocG9ydCk7XG4gICAgaWYgKHJlc3VsdCkge1xuICAgICAgLy8gQ2xvc2UgdGhlIHByb2JlIHNlcnZlciDigJQgdGhlcmUncyBhIGJyaWVmIFRPQ1RPVSB3aW5kb3cgaGVyZSBiZXR3ZWVuXG4gICAgICAvLyBjbG9zaW5nIGFuZCB0aGUgY2FsbGVyIGJpbmRpbmcsIGJ1dCBpdCdzIG5lZ2xpZ2libGUgb24gbG9jYWxob3N0LlxuICAgICAgLy8gQSBmdXR1cmUgaW1wcm92ZW1lbnQgY291bGQgcmV0dXJuIHRoZSBzZXJ2ZXIgZm9yIHRoZSBjYWxsZXIgdG8gYWRvcHQuXG4gICAgICByZXN1bHQuc2VydmVyLmNsb3NlKCk7XG4gICAgICBpZiAoYXR0ZW1wdCA+IDApIHtcbiAgICAgICAgbG9nZ2VyLmluZm8oYFtQb3J0RGlzY292ZXJ5XSBQb3J0ICR7c3RhcnRQb3J0fSBpbiB1c2UsIHVzaW5nICR7cG9ydH0gKGFmdGVyICR7YXR0ZW1wdH0gc2tpcCR7YXR0ZW1wdCA+IDEgPyAncycgOiAnJ30pYCk7XG4gICAgICB9XG4gICAgICByZXR1cm4gcG9ydDtcbiAgICB9XG4gICAgbG9nZ2VyLmRlYnVnKGBbUG9ydERpc2NvdmVyeV0gUG9ydCAke3BvcnR9IGluIHVzZSwgdHJ5aW5nIG5leHRgKTtcbiAgfVxuXG4gIHRocm93IG5ldyBFcnJvcihcbiAgICBgTm8gYXZhaWxhYmxlIHBvcnQgZm91bmQgaW4gcmFuZ2UgJHtzdGFydFBvcnR9LSR7c3RhcnRQb3J0ICsgbWF4QXR0ZW1wdHMgLSAxfSBgICtcbiAgICBgYWZ0ZXIgJHttYXhBdHRlbXB0c30gYXR0ZW1wdHMuIFNldCBET0xMSE9VU0VfTUFYX1BPUlRfQVRURU1QVFMgdG8gaW5jcmVhc2UgdGhlIHJhbmdlLmBcbiAgKTtcbn1cblxuLyoqXG4gKiBXcml0ZSB0aGUgYWN0aXZlIHNlcnZlciBwb3J0IHRvIGEgZGlzY292ZXJhYmxlIGZpbGUuXG4gKlxuICogQ3JlYXRlcyB0d28gZmlsZXM6XG4gKiAtIFBJRC1rZXllZCBmaWxlIGZvciBwZXItcHJvY2VzcyBjbGVhbnVwXG4gKiAtIExhdGVzdCBmaWxlIGZvciBjb252ZW5pZW5jZSAoc2NyaXB0cyBjYW4gcmVhZCB0aGlzIHdpdGhvdXQga25vd2luZyB0aGUgUElEKVxuICpcbiAqIEBwYXJhbSBwb3J0IC0gVGhlIHBvcnQgdGhlIHdlYiBzZXJ2ZXIgaXMgbGlzdGVuaW5nIG9uXG4gKiBAcmV0dXJucyBQYXRoIHRvIHRoZSBQSUQta2V5ZWQgcG9ydCBmaWxlXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3cml0ZVBvcnRGaWxlKHBvcnQ6IG51bWJlcik6IFByb21pc2U8c3RyaW5nPiB7XG4gIGF3YWl0IG1rZGlyKFJVTl9ESVIsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICBjb25zdCBwaWRGaWxlID0gam9pbihSVU5fRElSLCBgcGVybWlzc2lvbi1zZXJ2ZXItJHtwcm9jZXNzLnBpZH0ucG9ydGApO1xuICBjb25zdCBsYXRlc3RGaWxlID0gam9pbihSVU5fRElSLCAncGVybWlzc2lvbi1zZXJ2ZXIucG9ydCcpO1xuICBhd2FpdCB3cml0ZUZpbGUocGlkRmlsZSwgU3RyaW5nKHBvcnQpLCAndXRmLTgnKTtcbiAgYXdhaXQgd3JpdGVGaWxlKGxhdGVzdEZpbGUsIFN0cmluZyhwb3J0KSwgJ3V0Zi04Jyk7XG4gIHBvcnRGaWxlUGF0aCA9IHBpZEZpbGU7XG4gIGxvZ2dlci5kZWJ1ZyhgW1BvcnREaXNjb3ZlcnldIFBvcnQgZmlsZSB3cml0dGVuOiAke3BpZEZpbGV9YCk7XG4gIHJldHVybiBwaWRGaWxlO1xufVxuXG4vKipcbiAqIENsZWFuIHVwIHBvcnQgZmlsZSBvbiBzaHV0ZG93bi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNsZWFudXBQb3J0RmlsZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgaWYgKHBvcnRGaWxlUGF0aCkge1xuICAgIHRyeSB7IGF3YWl0IHVubGluayhwb3J0RmlsZVBhdGgpOyB9IGNhdGNoIHsgLyogYWxyZWFkeSBnb25lICovIH1cbiAgfVxufVxuXG4vKipcbiAqIFJlZ2lzdGVyIHByb2Nlc3MgZXhpdCBoYW5kbGVycyB0byBjbGVhbiB1cCBwb3J0IGZpbGVzLlxuICogU2hvdWxkIGJlIGNhbGxlZCBvbmNlIGFmdGVyIHRoZSB3ZWIgc2VydmVyIHN1Y2Nlc3NmdWxseSBzdGFydHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWdpc3RlclBvcnRDbGVhbnVwKCk6IHZvaWQge1xuICBjb25zdCBleGl0Q2xlYW51cCA9ICgpID0+IHsgY2xlYW51cFBvcnRGaWxlKCkuY2F0Y2goKCkgPT4ge30pOyB9O1xuICBwcm9jZXNzLm9uY2UoJ2V4aXQnLCBleGl0Q2xlYW51cCk7XG4gIHByb2Nlc3Mub25jZSgnU0lHVEVSTScsIGV4aXRDbGVhbnVwKTtcbiAgcHJvY2Vzcy5vbmNlKCdTSUdJTlQnLCBleGl0Q2xlYW51cCk7XG59XG5cbi8qKlxuICogRGlzY292ZXIgYW4gYXZhaWxhYmxlIHBvcnQsIHdyaXRlIGRpc2NvdmVyeSBmaWxlcywgYW5kIHJlZ2lzdGVyIGNsZWFudXAuXG4gKlxuICogVGhpcyBpcyB0aGUgcmVjb21tZW5kZWQgZW50cnkgcG9pbnQgZm9yIENvbnRhaW5lciBzdGFydHVwLiBDb21iaW5lc1xuICogcG9ydCBkaXNjb3ZlcnksIGZpbGUgd3JpdGluZywgYW5kIGNsZWFudXAgcmVnaXN0cmF0aW9uIGluIG9uZSBjYWxsLlxuICpcbiAqIEBwYXJhbSBkZWZhdWx0UG9ydCAtIFBvcnQgdG8gdHJ5IGZpcnN0IChkZWZhdWx0OiAzOTM5KVxuICogQHJldHVybnMgVGhlIHBvcnQgdGhlIHNlcnZlciBzaG91bGQgYmluZCB0bywgb3IgdW5kZWZpbmVkIGlmIGRpc2NvdmVyeSBmYWlsZWRcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGRpc2NvdmVyQW5kQmluZFBvcnQoZGVmYXVsdFBvcnQ6IG51bWJlciA9IDM5MzkpOiBQcm9taXNlPG51bWJlciB8IHVuZGVmaW5lZD4ge1xuICB0cnkge1xuICAgIGNvbnN0IHBvcnQgPSBhd2FpdCBmaW5kQXZhaWxhYmxlUG9ydChkZWZhdWx0UG9ydCk7XG5cbiAgICBhd2FpdCB3cml0ZVBvcnRGaWxlKHBvcnQpO1xuICAgIHJlZ2lzdGVyUG9ydENsZWFudXAoKTtcblxuICAgIHJldHVybiBwb3J0O1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICBsb2dnZXIud2FybignW1BvcnREaXNjb3ZlcnldIFBvcnQgZGlzY292ZXJ5IGZhaWxlZDonLCBlcnIpO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cbiJdfQ==
|