@intutic/mcp-governance-proxy 1.0.3
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/.turbo/turbo-build.log +1 -0
- package/.turbo/turbo-test.log +51 -0
- package/.turbo/turbo-typecheck.log +1 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +100 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/healthMonitor.d.ts +18 -0
- package/dist/daemon/healthMonitor.d.ts.map +1 -0
- package/dist/daemon/healthMonitor.js +185 -0
- package/dist/daemon/healthMonitor.js.map +1 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +77 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/policyCache.d.ts +20 -0
- package/dist/daemon/policyCache.d.ts.map +1 -0
- package/dist/daemon/policyCache.js +124 -0
- package/dist/daemon/policyCache.js.map +1 -0
- package/dist/daemon/socketServer.d.ts +13 -0
- package/dist/daemon/socketServer.d.ts.map +1 -0
- package/dist/daemon/socketServer.js +90 -0
- package/dist/daemon/socketServer.js.map +1 -0
- package/dist/daemon/telemetryBatcher.d.ts +15 -0
- package/dist/daemon/telemetryBatcher.d.ts.map +1 -0
- package/dist/daemon/telemetryBatcher.js +143 -0
- package/dist/daemon/telemetryBatcher.js.map +1 -0
- package/dist/daemonClient.d.ts +5 -0
- package/dist/daemonClient.d.ts.map +1 -0
- package/dist/daemonClient.js +67 -0
- package/dist/daemonClient.js.map +1 -0
- package/dist/dlp.d.ts +29 -0
- package/dist/dlp.d.ts.map +1 -0
- package/dist/dlp.js +58 -0
- package/dist/dlp.js.map +1 -0
- package/dist/emitter.d.ts +34 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +121 -0
- package/dist/emitter.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptor.d.ts +38 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +93 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/policy.d.ts +41 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +130 -0
- package/dist/policy.js.map +1 -0
- package/dist/proxy.d.ts +43 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +238 -0
- package/dist/proxy.js.map +1 -0
- package/dist/stderrLog.d.ts +32 -0
- package/dist/stderrLog.d.ts.map +1 -0
- package/dist/stderrLog.js +45 -0
- package/dist/stderrLog.js.map +1 -0
- package/dummy-path.jsonl +2 -0
- package/package.json +32 -0
- package/src/__tests__/config.test.ts +82 -0
- package/src/__tests__/dlp.test.ts +103 -0
- package/src/__tests__/integration/daemon-shim.test.ts +125 -0
- package/src/__tests__/interceptor.test.ts +229 -0
- package/src/__tests__/unit/policyCache.test.ts +95 -0
- package/src/__tests__/unit/socketServer.test.ts +220 -0
- package/src/__tests__/unit/telemetryBatcher.test.ts +78 -0
- package/src/config.ts +145 -0
- package/src/daemon/healthMonitor.ts +210 -0
- package/src/daemon/index.ts +76 -0
- package/src/daemon/policyCache.ts +143 -0
- package/src/daemon/socketServer.ts +111 -0
- package/src/daemon/telemetryBatcher.ts +147 -0
- package/src/daemonClient.ts +71 -0
- package/src/dlp.ts +72 -0
- package/src/emitter.ts +144 -0
- package/src/index.ts +53 -0
- package/src/interceptor.ts +103 -0
- package/src/policy.ts +152 -0
- package/src/proxy.ts +327 -0
- package/src/stderrLog.ts +56 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ResolvedPolicy {
|
|
2
|
+
workspaceId: string;
|
|
3
|
+
sopRules: Record<string, unknown>[];
|
|
4
|
+
dlpPatterns: string[];
|
|
5
|
+
interventionMode: string;
|
|
6
|
+
cachedAt: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolves policy for a workspace.
|
|
10
|
+
* Order: LRU (fresh) → LRU (stale, triggers async refresh) → Valkey → HTTP fetch
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolvePolicy(workspaceId: string): Promise<ResolvedPolicy | null>;
|
|
13
|
+
/** Invalidates policy cache for a workspace (called on SOP update). */
|
|
14
|
+
export declare function invalidatePolicy(workspaceId: string): void;
|
|
15
|
+
/** Returns cache statistics. */
|
|
16
|
+
export declare function getCacheStats(): {
|
|
17
|
+
entries: number;
|
|
18
|
+
hitRate: number;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=policyCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policyCache.d.ts","sourceRoot":"","sources":["../../src/daemon/policyCache.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAI,MAAM,CAAA;IACrB,QAAQ,EAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IACxC,WAAW,EAAI,MAAM,EAAE,CAAA;IACvB,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAO,MAAM,CAAA;CACtB;AA0CD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA+CvF;AAED,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAI1D;AAED,gCAAgC;AAChC,wBAAgB,aAAa,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAEpE"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy Cache — LRU in-memory + Valkey write-through
|
|
3
|
+
*
|
|
4
|
+
* Resolves PCAS permissions + active SOP rules for MCP daemon.
|
|
5
|
+
* Cache hit: < 0.1ms (LRU). Valkey hit: < 2ms. Miss: HTTP to control-plane.
|
|
6
|
+
*
|
|
7
|
+
* LLD #28: MCP Daemon Mode, WS-5MCP
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import https from 'node:https';
|
|
11
|
+
import http from 'node:http';
|
|
12
|
+
import { Redis } from 'ioredis';
|
|
13
|
+
import { createLogger } from '@intutic/logger';
|
|
14
|
+
const logger = createLogger('mcp-proxy.policyCache');
|
|
15
|
+
const getPolicyTtlMs = () => parseInt(process.env['MCP_DAEMON_POLICY_TTL_MS'] ?? '300000', 10);
|
|
16
|
+
const getMaxEntries = () => parseInt(process.env['MCP_DAEMON_MAX_CACHE_ENTRIES'] ?? '500', 10);
|
|
17
|
+
const getCpUrl = () => process.env['CONTROL_PLANE_URL'] ?? 'http://localhost:3001';
|
|
18
|
+
const getDaemonApiKey = () => process.env['INTUTIC_API_KEY'] ?? '';
|
|
19
|
+
const VALKEY_URL = process.env['VALKEY_URL'] ?? process.env['REDIS_URL'] ?? 'redis://localhost:6379';
|
|
20
|
+
const valkey = new Redis(VALKEY_URL, {
|
|
21
|
+
lazyConnect: true,
|
|
22
|
+
maxRetriesPerRequest: 3,
|
|
23
|
+
});
|
|
24
|
+
valkey.on('error', (err) => {
|
|
25
|
+
logger.warn({ err: err.message }, 'policyCache Valkey connection error');
|
|
26
|
+
});
|
|
27
|
+
// Simple LRU map (insertion-order eviction)
|
|
28
|
+
const lru = new Map();
|
|
29
|
+
function evictIfFull() {
|
|
30
|
+
if (lru.size >= getMaxEntries()) {
|
|
31
|
+
const firstKey = lru.keys().next().value;
|
|
32
|
+
lru.delete(firstKey);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function isStale(entry) {
|
|
36
|
+
return Date.now() - entry.cachedAt > getPolicyTtlMs();
|
|
37
|
+
}
|
|
38
|
+
async function fetchFromControlPlane(workspaceId) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const path = `/api/v1/policy/resolve?workspaceId=${encodeURIComponent(workspaceId)}`;
|
|
41
|
+
const url = new URL(path, getCpUrl());
|
|
42
|
+
const lib = url.protocol === 'https:' ? https : http;
|
|
43
|
+
const req = lib.request({ hostname: url.hostname, port: url.port, path: url.pathname + url.search, method: 'GET',
|
|
44
|
+
headers: { 'Authorization': `Bearer ${getDaemonApiKey()}`, 'Accept': 'application/json' } }, (res) => {
|
|
45
|
+
let data = '';
|
|
46
|
+
res.on('data', (c) => { data += c; });
|
|
47
|
+
res.on('end', () => {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(data);
|
|
50
|
+
resolve({ workspaceId, sopRules: parsed.sopRules ?? [], dlpPatterns: parsed.dlpPatterns ?? [],
|
|
51
|
+
interventionMode: parsed.interventionMode ?? 'BYPASS', cachedAt: Date.now() });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
resolve(null);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
req.on('error', () => resolve(null));
|
|
59
|
+
req.setTimeout(5000, () => { req.destroy(); resolve(null); });
|
|
60
|
+
req.end();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolves policy for a workspace.
|
|
65
|
+
* Order: LRU (fresh) → LRU (stale, triggers async refresh) → Valkey → HTTP fetch
|
|
66
|
+
*/
|
|
67
|
+
export async function resolvePolicy(workspaceId) {
|
|
68
|
+
const cached = lru.get(workspaceId);
|
|
69
|
+
if (cached && !isStale(cached)) {
|
|
70
|
+
// Touch for LRU recency
|
|
71
|
+
lru.delete(workspaceId);
|
|
72
|
+
lru.set(workspaceId, cached);
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
if (cached && isStale(cached)) {
|
|
76
|
+
// Return stale, trigger async refresh
|
|
77
|
+
void fetchFromControlPlane(workspaceId).then(async (fresh) => {
|
|
78
|
+
if (fresh) {
|
|
79
|
+
lru.delete(workspaceId);
|
|
80
|
+
evictIfFull();
|
|
81
|
+
lru.set(workspaceId, fresh);
|
|
82
|
+
try {
|
|
83
|
+
await valkey.set(`mcp_daemon:policy:${workspaceId}`, JSON.stringify(fresh), 'PX', getPolicyTtlMs());
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return cached;
|
|
89
|
+
}
|
|
90
|
+
// Cache miss in LRU — check Valkey
|
|
91
|
+
try {
|
|
92
|
+
const valkeyCached = await valkey.get(`mcp_daemon:policy:${workspaceId}`);
|
|
93
|
+
if (valkeyCached) {
|
|
94
|
+
const parsed = JSON.parse(valkeyCached);
|
|
95
|
+
evictIfFull();
|
|
96
|
+
lru.set(workspaceId, parsed);
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
// Cache miss in Valkey too — fetch synchronously
|
|
102
|
+
logger.debug({ workspaceId }, 'policy_cache.miss');
|
|
103
|
+
const fresh = await fetchFromControlPlane(workspaceId);
|
|
104
|
+
if (fresh) {
|
|
105
|
+
evictIfFull();
|
|
106
|
+
lru.set(workspaceId, fresh);
|
|
107
|
+
try {
|
|
108
|
+
await valkey.set(`mcp_daemon:policy:${workspaceId}`, JSON.stringify(fresh), 'PX', getPolicyTtlMs());
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
}
|
|
112
|
+
return fresh;
|
|
113
|
+
}
|
|
114
|
+
/** Invalidates policy cache for a workspace (called on SOP update). */
|
|
115
|
+
export function invalidatePolicy(workspaceId) {
|
|
116
|
+
lru.delete(workspaceId);
|
|
117
|
+
valkey.del(`mcp_daemon:policy:${workspaceId}`, `mcp_daemon:sop_rules:${workspaceId}`).catch(() => { });
|
|
118
|
+
logger.info({ workspaceId }, 'policy_cache.invalidated');
|
|
119
|
+
}
|
|
120
|
+
/** Returns cache statistics. */
|
|
121
|
+
export function getCacheStats() {
|
|
122
|
+
return { entries: lru.size, hitRate: 0 }; // hit rate tracked by metrics in production
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=policyCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policyCache.js","sourceRoot":"","sources":["../../src/daemon/policyCache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC over Unix Domain Socket server
|
|
3
|
+
*
|
|
4
|
+
* Accepts newline-delimited JSON requests from MCP shims.
|
|
5
|
+
* Methods: proxy.tool_call, proxy.health_check, policy.get, telemetry.flush
|
|
6
|
+
*
|
|
7
|
+
* LLD #28: MCP Daemon Mode, WS-5MCP
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import net from 'node:net';
|
|
11
|
+
export declare function getSocketPath(): string;
|
|
12
|
+
export declare function createSocketServer(): net.Server;
|
|
13
|
+
//# sourceMappingURL=socketServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socketServer.d.ts","sourceRoot":"","sources":["../../src/daemon/socketServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,GAAG,MAAO,UAAU,CAAA;AAW3B,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AA4DD,wBAAgB,kBAAkB,IAAI,GAAG,CAAC,MAAM,CA2B/C"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC over Unix Domain Socket server
|
|
3
|
+
*
|
|
4
|
+
* Accepts newline-delimited JSON requests from MCP shims.
|
|
5
|
+
* Methods: proxy.tool_call, proxy.health_check, policy.get, telemetry.flush
|
|
6
|
+
*
|
|
7
|
+
* LLD #28: MCP Daemon Mode, WS-5MCP
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import net from 'node:net';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import { createLogger } from '@intutic/logger';
|
|
14
|
+
import { resolvePolicy, getCacheStats, invalidatePolicy } from './policyCache.js';
|
|
15
|
+
import { enqueueEvent } from './telemetryBatcher.js';
|
|
16
|
+
import { getHealthSnapshot } from './healthMonitor.js';
|
|
17
|
+
const logger = createLogger('mcp-proxy.socketServer');
|
|
18
|
+
export function getSocketPath() {
|
|
19
|
+
return process.env['MCP_DAEMON_SOCKET'] ??
|
|
20
|
+
path.join(os.homedir(), '.intutic', 'mcp-proxy.sock');
|
|
21
|
+
}
|
|
22
|
+
function respond(socket, id, result) {
|
|
23
|
+
socket.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\n');
|
|
24
|
+
}
|
|
25
|
+
function respondError(socket, id, code, message) {
|
|
26
|
+
socket.write(JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } }) + '\n');
|
|
27
|
+
}
|
|
28
|
+
async function handleRequest(socket, req) {
|
|
29
|
+
const { id, method, params } = req;
|
|
30
|
+
switch (method) {
|
|
31
|
+
case 'proxy.health_check':
|
|
32
|
+
respond(socket, id, { status: 'ok', version: process.env['npm_package_version'] ?? '0.0.0',
|
|
33
|
+
cacheStats: getCacheStats(), mcpServers: getHealthSnapshot() });
|
|
34
|
+
break;
|
|
35
|
+
case 'proxy.tool_call': {
|
|
36
|
+
const workspaceId = params['workspaceId'];
|
|
37
|
+
const policy = await resolvePolicy(workspaceId);
|
|
38
|
+
// Simplified enforcement: if policy loaded, apply it
|
|
39
|
+
const allowed = policy !== null;
|
|
40
|
+
respond(socket, id, { allowed, policy: policy ?? null });
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'policy.get': {
|
|
44
|
+
const workspaceId = params['workspaceId'];
|
|
45
|
+
const policy = await resolvePolicy(workspaceId);
|
|
46
|
+
respond(socket, id, policy);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case 'policy.invalidate': {
|
|
50
|
+
const workspaceId = params['workspaceId'];
|
|
51
|
+
invalidatePolicy(workspaceId);
|
|
52
|
+
respond(socket, id, { invalidated: true });
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'telemetry.enqueue': {
|
|
56
|
+
enqueueEvent(params);
|
|
57
|
+
respond(socket, id, { queued: true });
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
respondError(socket, id, -32601, `Method not found: ${method}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function createSocketServer() {
|
|
65
|
+
const server = net.createServer((socket) => {
|
|
66
|
+
socket.setEncoding('utf8');
|
|
67
|
+
let buffer = '';
|
|
68
|
+
socket.on('data', (chunk) => {
|
|
69
|
+
buffer += chunk;
|
|
70
|
+
const lines = buffer.split('\n');
|
|
71
|
+
buffer = lines.pop() ?? '';
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (!trimmed)
|
|
75
|
+
continue;
|
|
76
|
+
try {
|
|
77
|
+
const req = JSON.parse(trimmed);
|
|
78
|
+
void handleRequest(socket, req).catch(err => respondError(socket, req.id, -32603, String(err)));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
socket.write(JSON.stringify({ jsonrpc: '2.0', id: null,
|
|
82
|
+
error: { code: -32700, message: 'Parse error' } }) + '\n');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
socket.on('error', (err) => logger.warn({ err }, 'socket.client_error'));
|
|
87
|
+
});
|
|
88
|
+
return server;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=socketServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socketServer.js","sourceRoot":"","sources":["../../src/daemon/socketServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface HookEvent {
|
|
2
|
+
event: string;
|
|
3
|
+
toolName: string;
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
harnessType: string;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** Enqueues a hook event. Evicts oldest on overflow. */
|
|
10
|
+
export declare function enqueueEvent(event: HookEvent): void;
|
|
11
|
+
/** Drains any Valkey or disk buffer from a previous crash, then starts the flush timer. */
|
|
12
|
+
export declare function startBatcher(): void;
|
|
13
|
+
/** Flushes remaining events and stops the timer. */
|
|
14
|
+
export declare function stopBatcher(): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=telemetryBatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetryBatcher.d.ts","sourceRoot":"","sources":["../../src/daemon/telemetryBatcher.ts"],"names":[],"mappings":"AAqCA,MAAM,WAAW,SAAS;IACxB,KAAK,EAAQ,MAAM,CAAA;IACnB,QAAQ,EAAK,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAI,MAAM,CAAA;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD,wDAAwD;AACxD,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAGnD;AA0DD,2FAA2F;AAC3F,wBAAgB,YAAY,IAAI,IAAI,CA2BnC;AAED,oDAAoD;AACpD,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAGjD"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry Batcher — ring buffer + 1s flush to control-plane
|
|
3
|
+
*
|
|
4
|
+
* Accumulates hook events and batches them for upload.
|
|
5
|
+
* Fallback: Valkey event buffer, followed by disk buffer at ~/.intutic/telemetry-buffer.ndjson on flush failure.
|
|
6
|
+
*
|
|
7
|
+
* LLD #28: MCP Daemon Mode, WS-5MCP
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import https from 'node:https';
|
|
11
|
+
import http from 'node:http';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import { Redis } from 'ioredis';
|
|
16
|
+
import { createLogger } from '@intutic/logger';
|
|
17
|
+
const logger = createLogger('mcp-proxy.telemetryBatcher');
|
|
18
|
+
const CP_URL = process.env['CONTROL_PLANE_URL'] ?? 'http://localhost:3001';
|
|
19
|
+
const DAEMON_API_KEY = process.env['INTUTIC_API_KEY'] ?? '';
|
|
20
|
+
const FLUSH_MS = parseInt(process.env['MCP_DAEMON_TELEMETRY_FLUSH_MS'] ?? '1000', 10);
|
|
21
|
+
const RING_CAPACITY = 200;
|
|
22
|
+
const DISK_BUFFER = path.join(os.homedir(), '.intutic', 'telemetry-buffer.ndjson');
|
|
23
|
+
const VALKEY_URL = process.env['VALKEY_URL'] ?? process.env['REDIS_URL'] ?? 'redis://localhost:6379';
|
|
24
|
+
const valkey = new Redis(VALKEY_URL, {
|
|
25
|
+
lazyConnect: true,
|
|
26
|
+
maxRetriesPerRequest: 3,
|
|
27
|
+
});
|
|
28
|
+
valkey.on('error', (err) => {
|
|
29
|
+
logger.warn({ err: err.message }, 'telemetryBatcher Valkey connection error');
|
|
30
|
+
});
|
|
31
|
+
const daemonId = process.pid.toString();
|
|
32
|
+
const ring = [];
|
|
33
|
+
let flushTimer = null;
|
|
34
|
+
let isFlushing = false;
|
|
35
|
+
/** Enqueues a hook event. Evicts oldest on overflow. */
|
|
36
|
+
export function enqueueEvent(event) {
|
|
37
|
+
if (ring.length >= RING_CAPACITY)
|
|
38
|
+
ring.shift();
|
|
39
|
+
ring.push(event);
|
|
40
|
+
}
|
|
41
|
+
async function flush() {
|
|
42
|
+
if (isFlushing || ring.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
isFlushing = true;
|
|
45
|
+
const batch = ring.splice(0, ring.length);
|
|
46
|
+
const body = JSON.stringify({ events: batch });
|
|
47
|
+
const url = new URL('/api/v1/hook-events', CP_URL);
|
|
48
|
+
const isHttps = url.protocol === 'https:';
|
|
49
|
+
const lib = isHttps ? https : http;
|
|
50
|
+
const tryUpload = (attempt) => new Promise((resolve, reject) => {
|
|
51
|
+
const req = lib.request({ hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body),
|
|
53
|
+
'Authorization': `Bearer ${DAEMON_API_KEY}` } }, (res) => {
|
|
54
|
+
res.resume();
|
|
55
|
+
if (res.statusCode && res.statusCode < 400)
|
|
56
|
+
resolve();
|
|
57
|
+
else
|
|
58
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
59
|
+
});
|
|
60
|
+
req.on('error', reject);
|
|
61
|
+
req.setTimeout(5000, () => { req.destroy(); reject(new Error('timeout')); });
|
|
62
|
+
req.write(body);
|
|
63
|
+
req.end();
|
|
64
|
+
});
|
|
65
|
+
let success = false;
|
|
66
|
+
for (let i = 0; i < 3; i++) {
|
|
67
|
+
try {
|
|
68
|
+
await tryUpload(i);
|
|
69
|
+
success = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
await new Promise(r => setTimeout(r, 500 * (i + 1)));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!success) {
|
|
77
|
+
// Persist to Valkey first
|
|
78
|
+
try {
|
|
79
|
+
const key = `mcp_daemon:event_buffer:${daemonId}`;
|
|
80
|
+
await valkey.rpush(key, ...batch.map(e => JSON.stringify(e)));
|
|
81
|
+
await valkey.expire(key, 600); // 10 min TTL
|
|
82
|
+
logger.warn({ count: batch.length }, 'telemetry.flush_failed_buffered_to_valkey');
|
|
83
|
+
}
|
|
84
|
+
catch (valkeyErr) {
|
|
85
|
+
// Fallback: local disk buffer
|
|
86
|
+
try {
|
|
87
|
+
fs.mkdirSync(path.dirname(DISK_BUFFER), { recursive: true, mode: 0o700 });
|
|
88
|
+
fs.appendFileSync(DISK_BUFFER, batch.map(e => JSON.stringify(e)).join('\n') + '\n', { mode: 0o600 });
|
|
89
|
+
logger.warn({ count: batch.length }, 'telemetry.flush_failed_buffered_to_disk');
|
|
90
|
+
}
|
|
91
|
+
catch (diskErr) {
|
|
92
|
+
logger.error({ diskErr, count: batch.length }, 'telemetry.disk_buffer_failed');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
logger.debug({ count: batch.length }, 'telemetry.flushed');
|
|
98
|
+
}
|
|
99
|
+
isFlushing = false;
|
|
100
|
+
}
|
|
101
|
+
/** Drains any Valkey or disk buffer from a previous crash, then starts the flush timer. */
|
|
102
|
+
export function startBatcher() {
|
|
103
|
+
// Drain Valkey buffer
|
|
104
|
+
const key = `mcp_daemon:event_buffer:${daemonId}`;
|
|
105
|
+
valkey.lrange(key, 0, -1).then(async (lines) => {
|
|
106
|
+
if (lines && lines.length > 0) {
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
try {
|
|
109
|
+
enqueueEvent(JSON.parse(line));
|
|
110
|
+
}
|
|
111
|
+
catch { }
|
|
112
|
+
}
|
|
113
|
+
await valkey.del(key);
|
|
114
|
+
logger.info({ count: lines.length }, 'telemetry.valkey_buffer_drained');
|
|
115
|
+
}
|
|
116
|
+
}).catch(() => { });
|
|
117
|
+
// Drain disk buffer
|
|
118
|
+
try {
|
|
119
|
+
if (fs.existsSync(DISK_BUFFER)) {
|
|
120
|
+
const lines = fs.readFileSync(DISK_BUFFER, 'utf8').trim().split('\n').filter(Boolean);
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
try {
|
|
123
|
+
enqueueEvent(JSON.parse(line));
|
|
124
|
+
}
|
|
125
|
+
catch { }
|
|
126
|
+
}
|
|
127
|
+
fs.unlinkSync(DISK_BUFFER);
|
|
128
|
+
logger.info({ count: lines.length }, 'telemetry.disk_buffer_drained');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch { }
|
|
132
|
+
flushTimer = setInterval(() => { void flush(); }, FLUSH_MS);
|
|
133
|
+
flushTimer.unref();
|
|
134
|
+
}
|
|
135
|
+
/** Flushes remaining events and stops the timer. */
|
|
136
|
+
export async function stopBatcher() {
|
|
137
|
+
if (flushTimer) {
|
|
138
|
+
clearInterval(flushTimer);
|
|
139
|
+
flushTimer = null;
|
|
140
|
+
}
|
|
141
|
+
await flush();
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=telemetryBatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetryBatcher.js","sourceRoot":"","sources":["../../src/daemon/telemetryBatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemonClient.d.ts","sourceRoot":"","sources":["../src/daemonClient.ts"],"names":[],"mappings":"AAkBA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAiD9F"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* daemonClient.ts — IPC socket client helper for communicating with mcp-daemon.
|
|
3
|
+
* LLD #28: MCP Daemon Mode, WS-5MCP
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import * as net from 'node:net';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import * as os from 'node:os';
|
|
10
|
+
import { createStderrLogger as createLogger } from './stderrLog.js';
|
|
11
|
+
const log = createLogger('mcp-proxy-daemon-client');
|
|
12
|
+
function getSocketPath() {
|
|
13
|
+
return process.env['MCP_DAEMON_SOCKET'] ??
|
|
14
|
+
path.join(os.homedir(), '.intutic', 'mcp-proxy.sock');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Sends a JSON-RPC request to the mcp-daemon Unix domain socket.
|
|
18
|
+
*/
|
|
19
|
+
export function callDaemonSocket(method, params) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const socket = net.createConnection(getSocketPath());
|
|
22
|
+
socket.setEncoding('utf8');
|
|
23
|
+
let buffer = '';
|
|
24
|
+
const req = {
|
|
25
|
+
jsonrpc: '2.0',
|
|
26
|
+
id: Date.now().toString() + Math.random().toString(36).substring(2, 7),
|
|
27
|
+
method,
|
|
28
|
+
params,
|
|
29
|
+
};
|
|
30
|
+
socket.on('connect', () => {
|
|
31
|
+
socket.write(JSON.stringify(req) + '\n');
|
|
32
|
+
});
|
|
33
|
+
socket.on('data', (chunk) => {
|
|
34
|
+
buffer += chunk;
|
|
35
|
+
const lines = buffer.split('\n');
|
|
36
|
+
if (lines.length > 1) {
|
|
37
|
+
const line = lines[0].trim();
|
|
38
|
+
if (line) {
|
|
39
|
+
try {
|
|
40
|
+
const res = JSON.parse(line);
|
|
41
|
+
if (res.error) {
|
|
42
|
+
reject(new Error(res.error.message));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
resolve(res.result);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
reject(err);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
socket.end();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
socket.on('error', (err) => {
|
|
58
|
+
reject(err);
|
|
59
|
+
});
|
|
60
|
+
// 2s timeout
|
|
61
|
+
socket.setTimeout(2000, () => {
|
|
62
|
+
socket.destroy();
|
|
63
|
+
reject(new Error('Daemon socket timeout'));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=daemonClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemonClient.js","sourceRoot":"","sources":["../src/daemonClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,kBAAkB,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEnE,MAAM,GAAG,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAA;AAEnD,SAAS,aAAa;IACpB,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAA;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,MAA+B;IAC9E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAA;QACpD,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAE1B,IAAI,MAAM,GAAG,EAAE,CAAA;QACf,MAAM,GAAG,GAAG;YACV,OAAO,EAAE,KAAK;YACd,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;YACtE,MAAM;YACN,MAAM;SACP,CAAA;QAED,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACxB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;QAEF,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAClC,MAAM,IAAI,KAAK,CAAA;YACf,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAChC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;gBAC7B,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,CAAC;wBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;wBAC5B,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;4BACd,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;wBACtC,CAAC;6BAAM,CAAC;4BACN,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;wBACrB,CAAC;oBACH,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,CAAC,GAAG,CAAC,CAAA;oBACb,CAAC;4BAAS,CAAC;wBACT,MAAM,CAAC,GAAG,EAAE,CAAA;oBACd,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC,CAAC,CAAA;QAEF,aAAa;QACb,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE;YAC3B,MAAM,CAAC,OAAO,EAAE,CAAA;YAChB,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAA;QAC5C,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/dist/dlp.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dlp.ts — DLP (Data Loss Prevention) argument scanner.
|
|
3
|
+
*
|
|
4
|
+
* Scans tool arguments (as a JSON string) for patterns that indicate
|
|
5
|
+
* credentials, secrets, PII, or other sensitive data before allowing
|
|
6
|
+
* the tool call to proceed.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
export interface DlpFinding {
|
|
11
|
+
pattern: string;
|
|
12
|
+
description: string;
|
|
13
|
+
}
|
|
14
|
+
export interface DlpScanResult {
|
|
15
|
+
hasFinding: boolean;
|
|
16
|
+
findings: DlpFinding[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Scan tool arguments for DLP findings.
|
|
20
|
+
*
|
|
21
|
+
* @param toolInput - The tool_input object from the MCP tools/call request
|
|
22
|
+
* @returns DLP scan result with any findings
|
|
23
|
+
*/
|
|
24
|
+
export declare function scanToolInput(toolInput: unknown): DlpScanResult;
|
|
25
|
+
/**
|
|
26
|
+
* Format DLP findings into a human-readable block reason.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatDlpBlockReason(findings: DlpFinding[]): string;
|
|
29
|
+
//# sourceMappingURL=dlp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dlp.d.ts","sourceRoot":"","sources":["../src/dlp.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,UAAU,EAAE,CAAA;CACvB;AA2BD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,OAAO,GAAG,aAAa,CAY/D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM,CAGnE"}
|
package/dist/dlp.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dlp.ts — DLP (Data Loss Prevention) argument scanner.
|
|
3
|
+
*
|
|
4
|
+
* Scans tool arguments (as a JSON string) for patterns that indicate
|
|
5
|
+
* credentials, secrets, PII, or other sensitive data before allowing
|
|
6
|
+
* the tool call to proceed.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Compiled DLP patterns. Each entry has a regex and a human-readable description.
|
|
12
|
+
*/
|
|
13
|
+
const DLP_PATTERNS = [
|
|
14
|
+
// API keys / tokens
|
|
15
|
+
{ regex: /sk-[A-Za-z0-9]{20,}/, description: 'OpenAI API key pattern' },
|
|
16
|
+
{ regex: /sk-ant-[A-Za-z0-9\-_]{20,}/, description: 'Anthropic API key pattern' },
|
|
17
|
+
{ regex: /AIza[A-Za-z0-9\-_]{35}/, description: 'Google API key pattern' },
|
|
18
|
+
{ regex: /ghp_[A-Za-z0-9]{36}/, description: 'GitHub personal access token' },
|
|
19
|
+
{ regex: /ghs_[A-Za-z0-9]{36}/, description: 'GitHub server token' },
|
|
20
|
+
{ regex: /xoxb-[A-Za-z0-9\-]{50,}/, description: 'Slack bot token' },
|
|
21
|
+
{ regex: /xoxp-[A-Za-z0-9\-]{50,}/, description: 'Slack user token' },
|
|
22
|
+
{ regex: /AKIA[A-Z0-9]{16}/, description: 'AWS Access Key ID' },
|
|
23
|
+
// High-entropy strings that look like secrets (≥40 chars of hex or base64)
|
|
24
|
+
{ regex: /[0-9a-f]{40,}/, description: 'High-entropy hex string (possible secret)' },
|
|
25
|
+
// Destructive shell patterns in Bash/command arguments
|
|
26
|
+
{ regex: /rm\s+-rf?\s+\//, description: 'Destructive rm -rf / command' },
|
|
27
|
+
{ regex: /DROP\s+TABLE/i, description: 'SQL DROP TABLE statement' },
|
|
28
|
+
{ regex: /DROP\s+DATABASE/i, description: 'SQL DROP DATABASE statement' },
|
|
29
|
+
{ regex: /TRUNCATE\s+TABLE/i, description: 'SQL TRUNCATE TABLE statement' },
|
|
30
|
+
// Private key material
|
|
31
|
+
{ regex: /-----BEGIN\s+(RSA\s+)?PRIVATE KEY-----/, description: 'PEM private key material' },
|
|
32
|
+
{ regex: /-----BEGIN\s+EC\s+PRIVATE KEY-----/, description: 'EC private key material' },
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Scan tool arguments for DLP findings.
|
|
36
|
+
*
|
|
37
|
+
* @param toolInput - The tool_input object from the MCP tools/call request
|
|
38
|
+
* @returns DLP scan result with any findings
|
|
39
|
+
*/
|
|
40
|
+
export function scanToolInput(toolInput) {
|
|
41
|
+
// Serialize to string for pattern matching
|
|
42
|
+
const serialized = JSON.stringify(toolInput ?? {});
|
|
43
|
+
const findings = [];
|
|
44
|
+
for (const { regex, description } of DLP_PATTERNS) {
|
|
45
|
+
if (regex.test(serialized)) {
|
|
46
|
+
findings.push({ pattern: regex.source, description });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { hasFinding: findings.length > 0, findings };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Format DLP findings into a human-readable block reason.
|
|
53
|
+
*/
|
|
54
|
+
export function formatDlpBlockReason(findings) {
|
|
55
|
+
const list = findings.map((f) => `• ${f.description}`).join('\n');
|
|
56
|
+
return `Tool call blocked by Intutic DLP scanner. Sensitive data detected:\n${list}`;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=dlp.js.map
|
package/dist/dlp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dlp.js","sourceRoot":"","sources":["../src/dlp.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH;;GAEG;AACH,MAAM,YAAY,GAAkD;IAClE,oBAAoB;IACpB,EAAE,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,wBAAwB,EAAE;IACvE,EAAE,KAAK,EAAE,4BAA4B,EAAE,WAAW,EAAE,2BAA2B,EAAE;IACjF,EAAE,KAAK,EAAE,wBAAwB,EAAE,WAAW,EAAE,wBAAwB,EAAE;IAC1E,EAAE,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,8BAA8B,EAAE;IAC7E,EAAE,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,qBAAqB,EAAE;IACpE,EAAE,KAAK,EAAE,yBAAyB,EAAE,WAAW,EAAE,iBAAiB,EAAE;IACpE,EAAE,KAAK,EAAE,yBAAyB,EAAE,WAAW,EAAE,kBAAkB,EAAE;IACrE,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,mBAAmB,EAAE;IAC/D,2EAA2E;IAC3E,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,2CAA2C,EAAE;IACpF,uDAAuD;IACvD,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,8BAA8B,EAAE;IACxE,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,0BAA0B,EAAE;IACnE,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,6BAA6B,EAAE;IACzE,EAAE,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,8BAA8B,EAAE;IAC3E,uBAAuB;IACvB,EAAE,KAAK,EAAE,wCAAwC,EAAE,WAAW,EAAE,0BAA0B,EAAE;IAC5F,EAAE,KAAK,EAAE,oCAAoC,EAAE,WAAW,EAAE,yBAAyB,EAAE;CACxF,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,SAAkB;IAC9C,2CAA2C;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;IAClD,MAAM,QAAQ,GAAiB,EAAE,CAAA;IAEjC,KAAK,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,YAAY,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;QACvD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAA;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAsB;IACzD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjE,OAAO,uEAAuE,IAAI,EAAE,CAAA;AACtF,CAAC"}
|