@darkiceinteractive/mcp-conductor 1.1.0 → 3.0.0-beta.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/README.md +35 -5
- package/dist/bin/cli.d.ts +20 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +260 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/bridge/http-server.d.ts +65 -1
- package/dist/bridge/http-server.d.ts.map +1 -1
- package/dist/bridge/http-server.js +192 -7
- package/dist/bridge/http-server.js.map +1 -1
- package/dist/bridge/index.d.ts +1 -0
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +1 -0
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/pool.d.ts +95 -0
- package/dist/bridge/pool.d.ts.map +1 -0
- package/dist/bridge/pool.js +384 -0
- package/dist/bridge/pool.js.map +1 -0
- package/dist/bridge/session-registry.d.ts +64 -0
- package/dist/bridge/session-registry.d.ts.map +1 -0
- package/dist/bridge/session-registry.js +124 -0
- package/dist/bridge/session-registry.js.map +1 -0
- package/dist/cache/cache.d.ts +43 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +167 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cache/delta.d.ts +32 -0
- package/dist/cache/delta.d.ts.map +1 -0
- package/dist/cache/delta.js +131 -0
- package/dist/cache/delta.js.map +1 -0
- package/dist/cache/disk.d.ts +65 -0
- package/dist/cache/disk.d.ts.map +1 -0
- package/dist/cache/disk.js +238 -0
- package/dist/cache/disk.js.map +1 -0
- package/dist/cache/index.d.ts +53 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +12 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/key.d.ts +44 -0
- package/dist/cache/key.d.ts.map +1 -0
- package/dist/cache/key.js +83 -0
- package/dist/cache/key.js.map +1 -0
- package/dist/cache/lru.d.ts +57 -0
- package/dist/cache/lru.d.ts.map +1 -0
- package/dist/cache/lru.js +112 -0
- package/dist/cache/lru.js.map +1 -0
- package/dist/cache/policy.d.ts +34 -0
- package/dist/cache/policy.d.ts.map +1 -0
- package/dist/cache/policy.js +95 -0
- package/dist/cache/policy.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +33 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +135 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/export-servers.d.ts +22 -0
- package/dist/cli/commands/export-servers.d.ts.map +1 -0
- package/dist/cli/commands/export-servers.js +45 -0
- package/dist/cli/commands/export-servers.js.map +1 -0
- package/dist/cli/commands/import-servers.d.ts +57 -0
- package/dist/cli/commands/import-servers.d.ts.map +1 -0
- package/dist/cli/commands/import-servers.js +137 -0
- package/dist/cli/commands/import-servers.js.map +1 -0
- package/dist/cli/commands/routing.d.ts +34 -0
- package/dist/cli/commands/routing.d.ts.map +1 -0
- package/dist/cli/commands/routing.js +60 -0
- package/dist/cli/commands/routing.js.map +1 -0
- package/dist/cli/commands/test-server.d.ts +34 -0
- package/dist/cli/commands/test-server.d.ts.map +1 -0
- package/dist/cli/commands/test-server.js +86 -0
- package/dist/cli/commands/test-server.js.map +1 -0
- package/dist/cli/daemon.d.ts +60 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +244 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/replay.d.ts +16 -0
- package/dist/cli/replay.d.ts.map +1 -0
- package/dist/cli/replay.js +89 -0
- package/dist/cli/replay.js.map +1 -0
- package/dist/cli/wizard/setup.d.ts +12 -0
- package/dist/cli/wizard/setup.d.ts.map +1 -0
- package/dist/cli/wizard/setup.js +71 -0
- package/dist/cli/wizard/setup.js.map +1 -0
- package/dist/config/defaults.d.ts +10 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +14 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/daemon/client.d.ts +97 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +279 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/discovery.d.ts +50 -0
- package/dist/daemon/discovery.d.ts.map +1 -0
- package/dist/daemon/discovery.js +104 -0
- package/dist/daemon/discovery.js.map +1 -0
- package/dist/daemon/index.d.ts +16 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +11 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/sandbox-api.d.ts +45 -0
- package/dist/daemon/sandbox-api.d.ts.map +1 -0
- package/dist/daemon/sandbox-api.js +74 -0
- package/dist/daemon/sandbox-api.js.map +1 -0
- package/dist/daemon/server.d.ts +65 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +351 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/shared-kv.d.ts +81 -0
- package/dist/daemon/shared-kv.d.ts.map +1 -0
- package/dist/daemon/shared-kv.js +215 -0
- package/dist/daemon/shared-kv.js.map +1 -0
- package/dist/daemon/shared-lock.d.ts +71 -0
- package/dist/daemon/shared-lock.d.ts.map +1 -0
- package/dist/daemon/shared-lock.js +119 -0
- package/dist/daemon/shared-lock.js.map +1 -0
- package/dist/hub/mcp-hub.d.ts +23 -0
- package/dist/hub/mcp-hub.d.ts.map +1 -1
- package/dist/hub/mcp-hub.js +34 -1
- package/dist/hub/mcp-hub.js.map +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/observability/anomaly.d.ts +67 -0
- package/dist/observability/anomaly.d.ts.map +1 -0
- package/dist/observability/anomaly.js +141 -0
- package/dist/observability/anomaly.js.map +1 -0
- package/dist/observability/cost-predictor.d.ts +49 -0
- package/dist/observability/cost-predictor.d.ts.map +1 -0
- package/dist/observability/cost-predictor.js +145 -0
- package/dist/observability/cost-predictor.js.map +1 -0
- package/dist/observability/hot-path.d.ts +49 -0
- package/dist/observability/hot-path.d.ts.map +1 -0
- package/dist/observability/hot-path.js +125 -0
- package/dist/observability/hot-path.js.map +1 -0
- package/dist/observability/index.d.ts +10 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +10 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/replay.d.ts +104 -0
- package/dist/observability/replay.d.ts.map +1 -0
- package/dist/observability/replay.js +239 -0
- package/dist/observability/replay.js.map +1 -0
- package/dist/registry/built-in-recommendations.d.ts +54 -0
- package/dist/registry/built-in-recommendations.d.ts.map +1 -0
- package/dist/registry/built-in-recommendations.js +65 -0
- package/dist/registry/built-in-recommendations.js.map +1 -0
- package/dist/registry/events.d.ts +26 -0
- package/dist/registry/events.d.ts.map +1 -0
- package/dist/registry/events.js +22 -0
- package/dist/registry/events.js.map +1 -0
- package/dist/registry/index.d.ts +159 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +12 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/registry.d.ts +87 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +294 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/registry/snapshot.d.ts +42 -0
- package/dist/registry/snapshot.d.ts.map +1 -0
- package/dist/registry/snapshot.js +71 -0
- package/dist/registry/snapshot.js.map +1 -0
- package/dist/registry/typegen.d.ts +48 -0
- package/dist/registry/typegen.d.ts.map +1 -0
- package/dist/registry/typegen.js +200 -0
- package/dist/registry/typegen.js.map +1 -0
- package/dist/registry/validator.d.ts +23 -0
- package/dist/registry/validator.d.ts.map +1 -0
- package/dist/registry/validator.js +50 -0
- package/dist/registry/validator.js.map +1 -0
- package/dist/reliability/breaker.d.ts +57 -0
- package/dist/reliability/breaker.d.ts.map +1 -0
- package/dist/reliability/breaker.js +130 -0
- package/dist/reliability/breaker.js.map +1 -0
- package/dist/reliability/errors.d.ts +78 -0
- package/dist/reliability/errors.d.ts.map +1 -0
- package/dist/reliability/errors.js +160 -0
- package/dist/reliability/errors.js.map +1 -0
- package/dist/reliability/gateway.d.ts +88 -0
- package/dist/reliability/gateway.d.ts.map +1 -0
- package/dist/reliability/gateway.js +180 -0
- package/dist/reliability/gateway.js.map +1 -0
- package/dist/reliability/index.d.ts +20 -0
- package/dist/reliability/index.d.ts.map +1 -0
- package/dist/reliability/index.js +16 -0
- package/dist/reliability/index.js.map +1 -0
- package/dist/reliability/profile.d.ts +49 -0
- package/dist/reliability/profile.d.ts.map +1 -0
- package/dist/reliability/profile.js +58 -0
- package/dist/reliability/profile.js.map +1 -0
- package/dist/reliability/retry.d.ts +39 -0
- package/dist/reliability/retry.d.ts.map +1 -0
- package/dist/reliability/retry.js +51 -0
- package/dist/reliability/retry.js.map +1 -0
- package/dist/reliability/timeout.d.ts +34 -0
- package/dist/reliability/timeout.d.ts.map +1 -0
- package/dist/reliability/timeout.js +53 -0
- package/dist/reliability/timeout.js.map +1 -0
- package/dist/runtime/executor.d.ts +12 -0
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +148 -16
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/findtool/embed.d.ts +28 -0
- package/dist/runtime/findtool/embed.d.ts.map +1 -0
- package/dist/runtime/findtool/embed.js +85 -0
- package/dist/runtime/findtool/embed.js.map +1 -0
- package/dist/runtime/findtool/index.d.ts +52 -0
- package/dist/runtime/findtool/index.d.ts.map +1 -0
- package/dist/runtime/findtool/index.js +78 -0
- package/dist/runtime/findtool/index.js.map +1 -0
- package/dist/runtime/findtool/vector-index.d.ts +53 -0
- package/dist/runtime/findtool/vector-index.d.ts.map +1 -0
- package/dist/runtime/findtool/vector-index.js +71 -0
- package/dist/runtime/findtool/vector-index.js.map +1 -0
- package/dist/runtime/helpers/budget.d.ts +27 -0
- package/dist/runtime/helpers/budget.d.ts.map +1 -0
- package/dist/runtime/helpers/budget.js +103 -0
- package/dist/runtime/helpers/budget.js.map +1 -0
- package/dist/runtime/helpers/compact.d.ts +32 -0
- package/dist/runtime/helpers/compact.d.ts.map +1 -0
- package/dist/runtime/helpers/compact.js +93 -0
- package/dist/runtime/helpers/compact.js.map +1 -0
- package/dist/runtime/helpers/delta.d.ts +45 -0
- package/dist/runtime/helpers/delta.d.ts.map +1 -0
- package/dist/runtime/helpers/delta.js +116 -0
- package/dist/runtime/helpers/delta.js.map +1 -0
- package/dist/runtime/helpers/index.d.ts +16 -0
- package/dist/runtime/helpers/index.d.ts.map +1 -0
- package/dist/runtime/helpers/index.js +13 -0
- package/dist/runtime/helpers/index.js.map +1 -0
- package/dist/runtime/helpers/summarize.d.ts +24 -0
- package/dist/runtime/helpers/summarize.d.ts.map +1 -0
- package/dist/runtime/helpers/summarize.js +124 -0
- package/dist/runtime/helpers/summarize.js.map +1 -0
- package/dist/runtime/helpers/worker-preload.d.ts +25 -0
- package/dist/runtime/helpers/worker-preload.d.ts.map +1 -0
- package/dist/runtime/helpers/worker-preload.js +223 -0
- package/dist/runtime/helpers/worker-preload.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/pool/index.d.ts +11 -0
- package/dist/runtime/pool/index.d.ts.map +1 -0
- package/dist/runtime/pool/index.js +8 -0
- package/dist/runtime/pool/index.js.map +1 -0
- package/dist/runtime/pool/recycle.d.ts +44 -0
- package/dist/runtime/pool/recycle.d.ts.map +1 -0
- package/dist/runtime/pool/recycle.js +50 -0
- package/dist/runtime/pool/recycle.js.map +1 -0
- package/dist/runtime/pool/worker-pool.d.ts +77 -0
- package/dist/runtime/pool/worker-pool.d.ts.map +1 -0
- package/dist/runtime/pool/worker-pool.js +216 -0
- package/dist/runtime/pool/worker-pool.js.map +1 -0
- package/dist/runtime/pool/worker.d.ts +80 -0
- package/dist/runtime/pool/worker.d.ts.map +1 -0
- package/dist/runtime/pool/worker.js +324 -0
- package/dist/runtime/pool/worker.js.map +1 -0
- package/dist/server/mcp-server.d.ts +6 -0
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +610 -45
- package/dist/server/mcp-server.js.map +1 -1
- package/dist/server/passthrough-registrar.d.ts +73 -0
- package/dist/server/passthrough-registrar.d.ts.map +1 -0
- package/dist/server/passthrough-registrar.js +110 -0
- package/dist/server/passthrough-registrar.js.map +1 -0
- package/dist/skills/skills-engine.d.ts +9 -1
- package/dist/skills/skills-engine.d.ts.map +1 -1
- package/dist/skills/skills-engine.js +20 -3
- package/dist/skills/skills-engine.js.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +5 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/orphan-watch.d.ts +34 -0
- package/dist/utils/orphan-watch.d.ts.map +1 -0
- package/dist/utils/orphan-watch.js +54 -0
- package/dist/utils/orphan-watch.js.map +1 -0
- package/dist/utils/redact.d.ts +15 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +48 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/utils/tokenize.d.ts +55 -0
- package/dist/utils/tokenize.d.ts.map +1 -0
- package/dist/utils/tokenize.js +205 -0
- package/dist/utils/tokenize.js.map +1 -0
- package/dist/version.d.ts +3 -3
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +3 -3
- package/dist/version.js.map +1 -1
- package/package.json +13 -3
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend Connection Pool
|
|
3
|
+
*
|
|
4
|
+
* Maintains persistent stdio connections to MCP backend servers, multiplexes
|
|
5
|
+
* JSON-RPC requests over the same channel, and respawns crashed backends
|
|
6
|
+
* within 1 second. Eliminates per-request spawn overhead.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Each server gets [min, max] connections whose lifecycle is managed here
|
|
10
|
+
* - JSON-RPC multiplexing: requests tagged with a unique id; responses routed
|
|
11
|
+
* back to the correct in-flight caller via a pending-map
|
|
12
|
+
* - Idle timer: connections unused for `idleTimeoutMs` are gracefully closed
|
|
13
|
+
* - Crash recovery: `close` event triggers automatic respawn
|
|
14
|
+
*
|
|
15
|
+
* @module bridge/pool
|
|
16
|
+
*/
|
|
17
|
+
import { EventEmitter } from 'node:events';
|
|
18
|
+
import { logger } from '../utils/index.js';
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
// ConnectionPool
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
const DEFAULT_OPTIONS = {
|
|
23
|
+
minConnectionsPerServer: 1,
|
|
24
|
+
maxConnectionsPerServer: 4,
|
|
25
|
+
idleTimeoutMs: 300_000,
|
|
26
|
+
acquireTimeoutMs: 5_000,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A connection pool for MCP backend servers.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* ```ts
|
|
33
|
+
* pool.registerServer('my-server', factory);
|
|
34
|
+
* const conn = await pool.acquire('my-server');
|
|
35
|
+
* const result = await conn.call('tools/list');
|
|
36
|
+
* pool.release(conn);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class ConnectionPool extends EventEmitter {
|
|
40
|
+
opts;
|
|
41
|
+
servers = new Map();
|
|
42
|
+
isShuttingDown = false;
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
super();
|
|
45
|
+
this.opts = { ...DEFAULT_OPTIONS, ...options };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Register a backend server with its connection factory.
|
|
49
|
+
* Call this before `acquire()`. Pre-warms `minConnectionsPerServer`
|
|
50
|
+
* connections immediately.
|
|
51
|
+
*/
|
|
52
|
+
registerServer(serverKey, factory) {
|
|
53
|
+
if (this.servers.has(serverKey))
|
|
54
|
+
return;
|
|
55
|
+
const entry = {
|
|
56
|
+
key: serverKey,
|
|
57
|
+
connections: new Map(),
|
|
58
|
+
waiting: [],
|
|
59
|
+
factory,
|
|
60
|
+
spawning: 0,
|
|
61
|
+
};
|
|
62
|
+
this.servers.set(serverKey, entry);
|
|
63
|
+
// Pre-warm minimum connections
|
|
64
|
+
for (let i = 0; i < this.opts.minConnectionsPerServer; i++) {
|
|
65
|
+
this._spawnConnection(entry).catch((err) => {
|
|
66
|
+
logger.warn('ConnectionPool: pre-warm spawn failed', { serverKey, err: String(err) });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Acquire an idle connection for `serverKey`.
|
|
72
|
+
*
|
|
73
|
+
* If all connections are busy and count < max, spawns a new one.
|
|
74
|
+
* If at max, waits up to `acquireTimeoutMs` for a release.
|
|
75
|
+
*/
|
|
76
|
+
async acquire(serverKey) {
|
|
77
|
+
if (this.isShuttingDown) {
|
|
78
|
+
throw new Error(`ConnectionPool is shutting down`);
|
|
79
|
+
}
|
|
80
|
+
const entry = this.servers.get(serverKey);
|
|
81
|
+
if (!entry) {
|
|
82
|
+
throw new Error(`ConnectionPool: unknown server "${serverKey}". Call registerServer() first.`);
|
|
83
|
+
}
|
|
84
|
+
// Try to find an idle connection
|
|
85
|
+
const idle = this._findIdle(entry);
|
|
86
|
+
if (idle) {
|
|
87
|
+
return this._markBusy(idle);
|
|
88
|
+
}
|
|
89
|
+
// Spawn a new connection if under max
|
|
90
|
+
const totalActive = entry.connections.size + entry.spawning;
|
|
91
|
+
if (totalActive < this.opts.maxConnectionsPerServer) {
|
|
92
|
+
const conn = await this._spawnConnection(entry);
|
|
93
|
+
return this._markBusy(conn);
|
|
94
|
+
}
|
|
95
|
+
// All at max — wait for a release
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
const idx = entry.waiting.indexOf(waiter);
|
|
99
|
+
if (idx !== -1)
|
|
100
|
+
entry.waiting.splice(idx, 1);
|
|
101
|
+
reject(new Error(`ConnectionPool: acquire timeout after ${this.opts.acquireTimeoutMs}ms for "${serverKey}"`));
|
|
102
|
+
}, this.opts.acquireTimeoutMs);
|
|
103
|
+
// Allow the timer to be GC'd without blocking Node exit
|
|
104
|
+
if (timer.unref)
|
|
105
|
+
timer.unref();
|
|
106
|
+
const waiter = { resolve, reject, timer };
|
|
107
|
+
entry.waiting.push(waiter);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Return a connection to the pool.
|
|
112
|
+
* Idle timer is (re)started; if waiters are queued, hands off immediately.
|
|
113
|
+
*/
|
|
114
|
+
release(connection) {
|
|
115
|
+
const entry = this.servers.get(connection.serverKey);
|
|
116
|
+
if (!entry)
|
|
117
|
+
return;
|
|
118
|
+
const internal = entry.connections.get(connection.id);
|
|
119
|
+
if (!internal || internal.closed)
|
|
120
|
+
return;
|
|
121
|
+
internal.busy = false;
|
|
122
|
+
// Drain waiting queue first
|
|
123
|
+
if (entry.waiting.length > 0) {
|
|
124
|
+
const waiter = entry.waiting.shift();
|
|
125
|
+
clearTimeout(waiter.timer);
|
|
126
|
+
// Mark busy synchronously before handing off
|
|
127
|
+
internal.busy = true;
|
|
128
|
+
waiter.resolve(this._toPublic(internal));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Start idle timer
|
|
132
|
+
this._startIdleTimer(entry, internal);
|
|
133
|
+
}
|
|
134
|
+
/** Gracefully drain all connections and stop accepting new ones. */
|
|
135
|
+
async shutdown() {
|
|
136
|
+
this.isShuttingDown = true;
|
|
137
|
+
for (const entry of this.servers.values()) {
|
|
138
|
+
// Reject all waiting acquires
|
|
139
|
+
for (const waiter of entry.waiting) {
|
|
140
|
+
clearTimeout(waiter.timer);
|
|
141
|
+
waiter.reject(new Error('ConnectionPool shut down'));
|
|
142
|
+
}
|
|
143
|
+
entry.waiting.length = 0;
|
|
144
|
+
// Close all connections
|
|
145
|
+
for (const conn of entry.connections.values()) {
|
|
146
|
+
this._closeInternal(conn);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
this.servers.clear();
|
|
150
|
+
logger.info('ConnectionPool: shut down complete');
|
|
151
|
+
}
|
|
152
|
+
/** Snapshot of pool health metrics. */
|
|
153
|
+
stats() {
|
|
154
|
+
let total = 0;
|
|
155
|
+
let idle = 0;
|
|
156
|
+
let busy = 0;
|
|
157
|
+
let pending = 0;
|
|
158
|
+
for (const entry of this.servers.values()) {
|
|
159
|
+
for (const conn of entry.connections.values()) {
|
|
160
|
+
if (conn.closed)
|
|
161
|
+
continue;
|
|
162
|
+
total++;
|
|
163
|
+
if (conn.busy)
|
|
164
|
+
busy++;
|
|
165
|
+
else
|
|
166
|
+
idle++;
|
|
167
|
+
}
|
|
168
|
+
pending += entry.waiting.length;
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
totalConnections: total,
|
|
172
|
+
idleConnections: idle,
|
|
173
|
+
busyConnections: busy,
|
|
174
|
+
serversTracked: this.servers.size,
|
|
175
|
+
pendingAcquires: pending,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
179
|
+
// Private helpers
|
|
180
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
181
|
+
_findIdle(entry) {
|
|
182
|
+
for (const conn of entry.connections.values()) {
|
|
183
|
+
if (!conn.busy && !conn.closed)
|
|
184
|
+
return conn;
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
_markBusy(conn) {
|
|
189
|
+
conn.busy = true;
|
|
190
|
+
// Cancel idle timer if active
|
|
191
|
+
if (conn.idleTimer) {
|
|
192
|
+
clearTimeout(conn.idleTimer);
|
|
193
|
+
conn.idleTimer = null;
|
|
194
|
+
}
|
|
195
|
+
return this._toPublic(conn);
|
|
196
|
+
}
|
|
197
|
+
_toPublic(internal) {
|
|
198
|
+
return {
|
|
199
|
+
id: internal.id,
|
|
200
|
+
serverKey: internal.serverKey,
|
|
201
|
+
call: (method, params) => this._rpcCall(internal, method, params),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
_rpcCall(conn, method, params) {
|
|
205
|
+
if (conn.closed || !conn.writeLine) {
|
|
206
|
+
return Promise.reject(new Error(`Connection ${conn.id} is closed`));
|
|
207
|
+
}
|
|
208
|
+
const id = conn.nextRequestId++;
|
|
209
|
+
const message = JSON.stringify({ jsonrpc: '2.0', id, method, params: params ?? {} });
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
conn.pending.set(id, { resolve, reject });
|
|
212
|
+
const ok = conn.writeLine(message + '\n');
|
|
213
|
+
if (!ok) {
|
|
214
|
+
conn.pending.delete(id);
|
|
215
|
+
reject(new Error(`Write failed on connection ${conn.id}`));
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async _spawnConnection(entry) {
|
|
220
|
+
entry.spawning++;
|
|
221
|
+
const id = `${entry.key}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
222
|
+
const internal = {
|
|
223
|
+
id,
|
|
224
|
+
serverKey: entry.key,
|
|
225
|
+
busy: false,
|
|
226
|
+
idleTimer: null,
|
|
227
|
+
pending: new Map(),
|
|
228
|
+
nextRequestId: 1,
|
|
229
|
+
lineBuffer: '',
|
|
230
|
+
writeLine: null,
|
|
231
|
+
close: () => { },
|
|
232
|
+
closed: false,
|
|
233
|
+
createdAt: Date.now(),
|
|
234
|
+
};
|
|
235
|
+
return new Promise((resolve, reject) => {
|
|
236
|
+
let resolved = false;
|
|
237
|
+
const transport = entry.factory({
|
|
238
|
+
onLine: (line) => this._handleLine(internal, line),
|
|
239
|
+
onClose: () => this._handleClose(entry, internal),
|
|
240
|
+
onError: (err) => {
|
|
241
|
+
if (!resolved) {
|
|
242
|
+
resolved = true;
|
|
243
|
+
entry.spawning--;
|
|
244
|
+
entry.connections.delete(id);
|
|
245
|
+
reject(err);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
this._handleError(entry, internal, err);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
internal.writeLine = transport.write;
|
|
253
|
+
internal.close = transport.close;
|
|
254
|
+
entry.connections.set(id, internal);
|
|
255
|
+
entry.spawning--;
|
|
256
|
+
resolved = true;
|
|
257
|
+
logger.debug('ConnectionPool: spawned connection', { id, server: entry.key });
|
|
258
|
+
resolve(internal);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
_handleLine(conn, raw) {
|
|
262
|
+
// Accumulate partial lines
|
|
263
|
+
conn.lineBuffer += raw;
|
|
264
|
+
// Process all complete newline-terminated JSON objects
|
|
265
|
+
let nl;
|
|
266
|
+
while ((nl = conn.lineBuffer.indexOf('\n')) !== -1) {
|
|
267
|
+
const line = conn.lineBuffer.slice(0, nl).trim();
|
|
268
|
+
conn.lineBuffer = conn.lineBuffer.slice(nl + 1);
|
|
269
|
+
if (!line)
|
|
270
|
+
continue;
|
|
271
|
+
let msg;
|
|
272
|
+
try {
|
|
273
|
+
msg = JSON.parse(line);
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
logger.warn('ConnectionPool: unparseable line from backend', { conn: conn.id, line: line.slice(0, 200) });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (msg.id !== undefined) {
|
|
280
|
+
const pending = conn.pending.get(msg.id);
|
|
281
|
+
if (pending) {
|
|
282
|
+
conn.pending.delete(msg.id);
|
|
283
|
+
if (msg.error) {
|
|
284
|
+
pending.reject(new Error(msg.error.message ?? 'RPC error'));
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
pending.resolve(msg.result);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
_handleClose(entry, conn) {
|
|
294
|
+
if (conn.closed)
|
|
295
|
+
return;
|
|
296
|
+
conn.closed = true;
|
|
297
|
+
conn.writeLine = null;
|
|
298
|
+
// Reject all in-flight calls
|
|
299
|
+
for (const [, pending] of conn.pending) {
|
|
300
|
+
pending.reject(new Error(`Connection ${conn.id} to "${entry.key}" closed unexpectedly`));
|
|
301
|
+
}
|
|
302
|
+
conn.pending.clear();
|
|
303
|
+
if (conn.idleTimer) {
|
|
304
|
+
clearTimeout(conn.idleTimer);
|
|
305
|
+
conn.idleTimer = null;
|
|
306
|
+
}
|
|
307
|
+
entry.connections.delete(conn.id);
|
|
308
|
+
logger.warn('ConnectionPool: connection closed', { id: conn.id, server: entry.key });
|
|
309
|
+
if (this.isShuttingDown)
|
|
310
|
+
return;
|
|
311
|
+
// Respawn to maintain minimum floor
|
|
312
|
+
const activeCount = entry.connections.size + entry.spawning;
|
|
313
|
+
if (activeCount < this.opts.minConnectionsPerServer) {
|
|
314
|
+
setTimeout(() => {
|
|
315
|
+
if (!this.isShuttingDown) {
|
|
316
|
+
this._spawnConnection(entry)
|
|
317
|
+
.then((newConn) => {
|
|
318
|
+
logger.info('ConnectionPool: respawned connection after crash', {
|
|
319
|
+
newId: newConn.id,
|
|
320
|
+
server: entry.key,
|
|
321
|
+
});
|
|
322
|
+
// Drain any waiters that accumulated during the crash
|
|
323
|
+
this._drainWaiters(entry);
|
|
324
|
+
})
|
|
325
|
+
.catch((err) => {
|
|
326
|
+
logger.error('ConnectionPool: respawn failed', { server: entry.key, err: String(err) });
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}, 0).unref?.();
|
|
330
|
+
}
|
|
331
|
+
this.emit('connectionClosed', entry.key, conn.id);
|
|
332
|
+
}
|
|
333
|
+
_handleError(entry, conn, err) {
|
|
334
|
+
logger.error('ConnectionPool: connection error', { id: conn.id, server: entry.key, err: err.message });
|
|
335
|
+
this._handleClose(entry, conn);
|
|
336
|
+
}
|
|
337
|
+
_startIdleTimer(entry, conn) {
|
|
338
|
+
if (conn.idleTimer)
|
|
339
|
+
clearTimeout(conn.idleTimer);
|
|
340
|
+
conn.idleTimer = setTimeout(() => {
|
|
341
|
+
const currentCount = entry.connections.size;
|
|
342
|
+
if (!conn.busy && currentCount > this.opts.minConnectionsPerServer) {
|
|
343
|
+
logger.debug('ConnectionPool: closing idle connection', { id: conn.id, server: entry.key });
|
|
344
|
+
this._closeInternal(conn);
|
|
345
|
+
entry.connections.delete(conn.id);
|
|
346
|
+
}
|
|
347
|
+
}, this.opts.idleTimeoutMs);
|
|
348
|
+
// Do not block Node.js exit for idle timers
|
|
349
|
+
if (conn.idleTimer.unref)
|
|
350
|
+
conn.idleTimer.unref();
|
|
351
|
+
}
|
|
352
|
+
_closeInternal(conn) {
|
|
353
|
+
if (conn.closed)
|
|
354
|
+
return;
|
|
355
|
+
conn.closed = true;
|
|
356
|
+
conn.writeLine = null;
|
|
357
|
+
for (const [, pending] of conn.pending) {
|
|
358
|
+
pending.reject(new Error(`Connection ${conn.id} closed`));
|
|
359
|
+
}
|
|
360
|
+
conn.pending.clear();
|
|
361
|
+
if (conn.idleTimer) {
|
|
362
|
+
clearTimeout(conn.idleTimer);
|
|
363
|
+
conn.idleTimer = null;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
conn.close();
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
// Ignore close errors
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
_drainWaiters(entry) {
|
|
373
|
+
while (entry.waiting.length > 0) {
|
|
374
|
+
const idle = this._findIdle(entry);
|
|
375
|
+
if (!idle)
|
|
376
|
+
break;
|
|
377
|
+
const waiter = entry.waiting.shift();
|
|
378
|
+
clearTimeout(waiter.timer);
|
|
379
|
+
idle.busy = true;
|
|
380
|
+
waiter.resolve(this._toPublic(idle));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/bridge/pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge Session Registry
|
|
3
|
+
*
|
|
4
|
+
* Tracks `Mcp-Session-Id` values issued by the HTTP bridge. Sessions are
|
|
5
|
+
* generated on first request (or when a client presents an unknown id) and
|
|
6
|
+
* expire after a period of inactivity. Bounded to prevent unbounded growth
|
|
7
|
+
* if a misbehaving client rotates IDs.
|
|
8
|
+
*
|
|
9
|
+
* Per MCP spec 2025-03-26 Streamable HTTP transport:
|
|
10
|
+
* - session IDs MUST be globally unique and cryptographically secure
|
|
11
|
+
* - requests carrying a terminated / unknown id should return 404
|
|
12
|
+
* - servers MAY assign a new id on any request
|
|
13
|
+
*/
|
|
14
|
+
interface SessionRecord {
|
|
15
|
+
id: string;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
lastSeenAt: number;
|
|
18
|
+
}
|
|
19
|
+
export declare class SessionRegistry {
|
|
20
|
+
private readonly sessions;
|
|
21
|
+
private cleanupTimer;
|
|
22
|
+
private readonly ttlMs;
|
|
23
|
+
private readonly cleanupIntervalMs;
|
|
24
|
+
private readonly maxSessions;
|
|
25
|
+
constructor(opts?: {
|
|
26
|
+
ttlMs?: number;
|
|
27
|
+
cleanupIntervalMs?: number;
|
|
28
|
+
maxSessions?: number;
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Begin periodic cleanup. Called by HttpBridge.start().
|
|
32
|
+
*/
|
|
33
|
+
start(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Stop the cleanup timer and drop all sessions. Called by HttpBridge.stop().
|
|
36
|
+
*/
|
|
37
|
+
stop(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Create a new session and return its id.
|
|
40
|
+
*/
|
|
41
|
+
create(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Look up a session. Returns undefined if the id is unknown or expired.
|
|
44
|
+
* Expired sessions are purged lazily here even if the sweep hasn't run yet.
|
|
45
|
+
*/
|
|
46
|
+
touch(id: string): SessionRecord | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Explicit termination (e.g. DELETE /session).
|
|
49
|
+
* Returns true if a session was removed.
|
|
50
|
+
*/
|
|
51
|
+
terminate(id: string): boolean;
|
|
52
|
+
size(): number;
|
|
53
|
+
/**
|
|
54
|
+
* Purge sessions older than ttlMs. Exposed for tests.
|
|
55
|
+
*/
|
|
56
|
+
sweep(): number;
|
|
57
|
+
/**
|
|
58
|
+
* If we're at capacity, drop the oldest (by lastSeenAt) record to make room.
|
|
59
|
+
* Prevents unbounded growth under ID rotation.
|
|
60
|
+
*/
|
|
61
|
+
private evictIfFull;
|
|
62
|
+
}
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=session-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../../src/bridge/session-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,IAAI,CAAC,EAAE;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB;IAOD;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACH,MAAM,IAAI,MAAM;IAQhB;;;OAGG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAY5C;;;OAGG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI9B,IAAI,IAAI,MAAM;IAId;;OAEG;IACH,KAAK,IAAI,MAAM;IAef;;;OAGG;IACH,OAAO,CAAC,WAAW;CAepB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge Session Registry
|
|
3
|
+
*
|
|
4
|
+
* Tracks `Mcp-Session-Id` values issued by the HTTP bridge. Sessions are
|
|
5
|
+
* generated on first request (or when a client presents an unknown id) and
|
|
6
|
+
* expire after a period of inactivity. Bounded to prevent unbounded growth
|
|
7
|
+
* if a misbehaving client rotates IDs.
|
|
8
|
+
*
|
|
9
|
+
* Per MCP spec 2025-03-26 Streamable HTTP transport:
|
|
10
|
+
* - session IDs MUST be globally unique and cryptographically secure
|
|
11
|
+
* - requests carrying a terminated / unknown id should return 404
|
|
12
|
+
* - servers MAY assign a new id on any request
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
15
|
+
import { logger } from '../utils/index.js';
|
|
16
|
+
import { LIFECYCLE_TIMEOUTS, MAX_BRIDGE_SESSIONS } from '../config/defaults.js';
|
|
17
|
+
export class SessionRegistry {
|
|
18
|
+
sessions = new Map();
|
|
19
|
+
cleanupTimer = null;
|
|
20
|
+
ttlMs;
|
|
21
|
+
cleanupIntervalMs;
|
|
22
|
+
maxSessions;
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
this.ttlMs = opts?.ttlMs ?? LIFECYCLE_TIMEOUTS.BRIDGE_SESSION_TTL_MS;
|
|
25
|
+
this.cleanupIntervalMs =
|
|
26
|
+
opts?.cleanupIntervalMs ?? LIFECYCLE_TIMEOUTS.BRIDGE_SESSION_CLEANUP_INTERVAL_MS;
|
|
27
|
+
this.maxSessions = opts?.maxSessions ?? MAX_BRIDGE_SESSIONS;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Begin periodic cleanup. Called by HttpBridge.start().
|
|
31
|
+
*/
|
|
32
|
+
start() {
|
|
33
|
+
if (this.cleanupTimer)
|
|
34
|
+
return;
|
|
35
|
+
this.cleanupTimer = setInterval(() => {
|
|
36
|
+
this.sweep();
|
|
37
|
+
}, this.cleanupIntervalMs);
|
|
38
|
+
this.cleanupTimer.unref?.();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Stop the cleanup timer and drop all sessions. Called by HttpBridge.stop().
|
|
42
|
+
*/
|
|
43
|
+
stop() {
|
|
44
|
+
if (this.cleanupTimer) {
|
|
45
|
+
clearInterval(this.cleanupTimer);
|
|
46
|
+
this.cleanupTimer = null;
|
|
47
|
+
}
|
|
48
|
+
this.sessions.clear();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create a new session and return its id.
|
|
52
|
+
*/
|
|
53
|
+
create() {
|
|
54
|
+
this.evictIfFull();
|
|
55
|
+
const id = randomUUID();
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
this.sessions.set(id, { id, createdAt: now, lastSeenAt: now });
|
|
58
|
+
return id;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Look up a session. Returns undefined if the id is unknown or expired.
|
|
62
|
+
* Expired sessions are purged lazily here even if the sweep hasn't run yet.
|
|
63
|
+
*/
|
|
64
|
+
touch(id) {
|
|
65
|
+
const record = this.sessions.get(id);
|
|
66
|
+
if (!record)
|
|
67
|
+
return undefined;
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
if (now - record.lastSeenAt > this.ttlMs) {
|
|
70
|
+
this.sessions.delete(id);
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
record.lastSeenAt = now;
|
|
74
|
+
return record;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Explicit termination (e.g. DELETE /session).
|
|
78
|
+
* Returns true if a session was removed.
|
|
79
|
+
*/
|
|
80
|
+
terminate(id) {
|
|
81
|
+
return this.sessions.delete(id);
|
|
82
|
+
}
|
|
83
|
+
size() {
|
|
84
|
+
return this.sessions.size;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Purge sessions older than ttlMs. Exposed for tests.
|
|
88
|
+
*/
|
|
89
|
+
sweep() {
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
let removed = 0;
|
|
92
|
+
for (const [id, record] of this.sessions) {
|
|
93
|
+
if (now - record.lastSeenAt > this.ttlMs) {
|
|
94
|
+
this.sessions.delete(id);
|
|
95
|
+
removed++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (removed > 0) {
|
|
99
|
+
logger.debug('Bridge session sweep', { removed, remaining: this.sessions.size });
|
|
100
|
+
}
|
|
101
|
+
return removed;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* If we're at capacity, drop the oldest (by lastSeenAt) record to make room.
|
|
105
|
+
* Prevents unbounded growth under ID rotation.
|
|
106
|
+
*/
|
|
107
|
+
evictIfFull() {
|
|
108
|
+
if (this.sessions.size < this.maxSessions)
|
|
109
|
+
return;
|
|
110
|
+
let oldestId;
|
|
111
|
+
let oldestSeen = Number.POSITIVE_INFINITY;
|
|
112
|
+
for (const [id, record] of this.sessions) {
|
|
113
|
+
if (record.lastSeenAt < oldestSeen) {
|
|
114
|
+
oldestSeen = record.lastSeenAt;
|
|
115
|
+
oldestId = id;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (oldestId) {
|
|
119
|
+
this.sessions.delete(oldestId);
|
|
120
|
+
logger.warn('Bridge session registry full, evicted oldest', { maxSessions: this.maxSessions });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=session-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../src/bridge/session-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CacheLayer — three-tier cache composition (memory LRU + disk CBOR + delta).
|
|
3
|
+
*
|
|
4
|
+
* Call flow:
|
|
5
|
+
* get(server, tool, args)
|
|
6
|
+
* → memory LRU hit? → return CacheHit (source: 'memory')
|
|
7
|
+
* → disk hit? → promote to memory, return CacheHit (source: 'disk')
|
|
8
|
+
* → miss → return null (caller must fetch from backend)
|
|
9
|
+
*
|
|
10
|
+
* Stale-while-revalidate (SWR):
|
|
11
|
+
* All staleness decisions are made here, not in DiskCache, so that
|
|
12
|
+
* vi.useFakeTimers() in tests correctly affects TTL checks at all tiers.
|
|
13
|
+
*
|
|
14
|
+
* Bridge wiring order (per spec):
|
|
15
|
+
* cache check → cache miss → reliability gateway (Agent C) → backend
|
|
16
|
+
*
|
|
17
|
+
* @module cache/cache
|
|
18
|
+
*/
|
|
19
|
+
import type { CacheHit, CacheStats, CacheLayerOptions, DeltaResult } from './index.js';
|
|
20
|
+
export interface ExtendedCacheHit extends CacheHit {
|
|
21
|
+
needsRevalidation?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare class CacheLayer {
|
|
24
|
+
private lru;
|
|
25
|
+
private disk;
|
|
26
|
+
private registry;
|
|
27
|
+
private options;
|
|
28
|
+
private unsubscribe;
|
|
29
|
+
private diskMisses;
|
|
30
|
+
private ttlMap;
|
|
31
|
+
constructor(options: CacheLayerOptions);
|
|
32
|
+
get(server: string, tool: string, args: unknown): Promise<ExtendedCacheHit | null>;
|
|
33
|
+
set(server: string, tool: string, args: unknown, result: unknown, options?: {
|
|
34
|
+
ttl?: number;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
invalidate(server: string, pattern?: string): Promise<number>;
|
|
37
|
+
delta(server: string, tool: string, args: unknown, current: unknown): Promise<DeltaResult>;
|
|
38
|
+
stats(): CacheStats;
|
|
39
|
+
clear(): Promise<void>;
|
|
40
|
+
wouldCache(server: string, tool: string): boolean;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAUH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEvF,MAAM,WAAW,gBAAiB,SAAQ,QAAQ;IAChD,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,MAAM,CAA6B;gBAE/B,OAAO,EAAE,iBAAiB;IAgChC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAsDlF,GAAG,CACP,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GACzB,OAAO,CAAC,IAAI,CAAC;IAkBV,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO7D,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAMhG,KAAK,IAAI,UAAU;IAab,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAKjD,OAAO,IAAI,IAAI;CAMhB"}
|