@wolpertingerlabs/drawlatch 1.0.0-alpha.9.4 → 1.0.0-alpha.9.5
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.
|
@@ -43,7 +43,14 @@ export declare class IngestorManager {
|
|
|
43
43
|
private readonly config;
|
|
44
44
|
/** Active ingestor instances, keyed by `callerAlias:connectionAlias:instanceId`. */
|
|
45
45
|
private ingestors;
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Optional config loader for hot-reload support. When provided, `startOne()`
|
|
48
|
+
* uses it to get fresh config from disk instead of the constructor snapshot.
|
|
49
|
+
*/
|
|
50
|
+
private configLoader;
|
|
51
|
+
constructor(config: RemoteServerConfig, configLoader?: () => RemoteServerConfig);
|
|
52
|
+
/** Return fresh config if a loader is available, otherwise the constructor snapshot. */
|
|
53
|
+
private getConfig;
|
|
47
54
|
/**
|
|
48
55
|
* Start ingestors for all callers whose connections have an `ingestor` config.
|
|
49
56
|
* Called once when the remote server starts listening.
|
|
@@ -48,8 +48,18 @@ export class IngestorManager {
|
|
|
48
48
|
config;
|
|
49
49
|
/** Active ingestor instances, keyed by `callerAlias:connectionAlias:instanceId`. */
|
|
50
50
|
ingestors = new Map();
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Optional config loader for hot-reload support. When provided, `startOne()`
|
|
53
|
+
* uses it to get fresh config from disk instead of the constructor snapshot.
|
|
54
|
+
*/
|
|
55
|
+
configLoader;
|
|
56
|
+
constructor(config, configLoader) {
|
|
52
57
|
this.config = config;
|
|
58
|
+
this.configLoader = configLoader;
|
|
59
|
+
}
|
|
60
|
+
/** Return fresh config if a loader is available, otherwise the constructor snapshot. */
|
|
61
|
+
getConfig() {
|
|
62
|
+
return this.configLoader ? this.configLoader() : this.config;
|
|
53
63
|
}
|
|
54
64
|
/**
|
|
55
65
|
* Start ingestors for all callers whose connections have an `ingestor` config.
|
|
@@ -228,7 +238,11 @@ export class IngestorManager {
|
|
|
228
238
|
* When omitted, starts the default instance (or all instances if listenerInstances is defined).
|
|
229
239
|
*/
|
|
230
240
|
async startOne(callerAlias, connectionAlias, instanceId) {
|
|
231
|
-
|
|
241
|
+
// Get fresh config (from disk in production, or constructor snapshot in tests)
|
|
242
|
+
// so we pick up changes made by tool handlers (e.g. set_connection_enabled,
|
|
243
|
+
// set_listener_params, set_secrets) without requiring a server restart.
|
|
244
|
+
const config = this.getConfig();
|
|
245
|
+
const callerConfig = config.callers[callerAlias];
|
|
232
246
|
if (!callerConfig) {
|
|
233
247
|
return {
|
|
234
248
|
success: false,
|
|
@@ -244,7 +258,7 @@ export class IngestorManager {
|
|
|
244
258
|
error: `Caller does not have connection: ${connectionAlias}`,
|
|
245
259
|
};
|
|
246
260
|
}
|
|
247
|
-
const rawRoutes = resolveCallerRoutes(
|
|
261
|
+
const rawRoutes = resolveCallerRoutes(config, callerAlias);
|
|
248
262
|
const callerEnvResolved = resolveSecrets(callerConfig.env ?? {});
|
|
249
263
|
const resolvedRoutes = resolveRoutes(rawRoutes, callerEnvResolved, callerAlias);
|
|
250
264
|
const rawRoute = rawRoutes[connectionIndex];
|
|
@@ -258,7 +272,7 @@ export class IngestorManager {
|
|
|
258
272
|
}
|
|
259
273
|
// If a specific instanceId is given, start just that one
|
|
260
274
|
if (instanceId) {
|
|
261
|
-
return this.startOneInstance(callerAlias, connectionAlias, instanceId, rawRoute, resolvedRoute);
|
|
275
|
+
return this.startOneInstance(callerAlias, connectionAlias, instanceId, rawRoute, resolvedRoute, config);
|
|
262
276
|
}
|
|
263
277
|
// If listenerInstances is defined, start all instances
|
|
264
278
|
const instances = callerConfig.listenerInstances?.[connectionAlias];
|
|
@@ -267,15 +281,15 @@ export class IngestorManager {
|
|
|
267
281
|
for (const [instId, instOverrides] of Object.entries(instances)) {
|
|
268
282
|
if (instOverrides.disabled)
|
|
269
283
|
continue;
|
|
270
|
-
results.push(await this.startOneInstance(callerAlias, connectionAlias, instId, rawRoute, resolvedRoute));
|
|
284
|
+
results.push(await this.startOneInstance(callerAlias, connectionAlias, instId, rawRoute, resolvedRoute, config));
|
|
271
285
|
}
|
|
272
286
|
return results;
|
|
273
287
|
}
|
|
274
288
|
// Single default instance
|
|
275
|
-
return this.startOneInstance(callerAlias, connectionAlias, undefined, rawRoute, resolvedRoute);
|
|
289
|
+
return this.startOneInstance(callerAlias, connectionAlias, undefined, rawRoute, resolvedRoute, config);
|
|
276
290
|
}
|
|
277
291
|
/** Internal: start a single specific instance. */
|
|
278
|
-
async startOneInstance(callerAlias, connectionAlias, instanceId, rawRoute, resolvedRoute) {
|
|
292
|
+
async startOneInstance(callerAlias, connectionAlias, instanceId, rawRoute, resolvedRoute, config) {
|
|
279
293
|
const key = makeKey(callerAlias, connectionAlias, instanceId ?? DEFAULT_INSTANCE_ID);
|
|
280
294
|
// If already running, return current status
|
|
281
295
|
const existing = this.ingestors.get(key);
|
|
@@ -289,10 +303,10 @@ export class IngestorManager {
|
|
|
289
303
|
// Remove stopped instance to recreate
|
|
290
304
|
this.ingestors.delete(key);
|
|
291
305
|
}
|
|
292
|
-
const
|
|
306
|
+
const callerCfg = config.callers[callerAlias];
|
|
293
307
|
const overrides = instanceId
|
|
294
|
-
?
|
|
295
|
-
:
|
|
308
|
+
? callerCfg.listenerInstances?.[connectionAlias]?.[instanceId]
|
|
309
|
+
: callerCfg.ingestorOverrides?.[connectionAlias];
|
|
296
310
|
const effectiveConfig = IngestorManager.mergeIngestorConfig(rawRoute.ingestor, overrides);
|
|
297
311
|
// Apply instance params
|
|
298
312
|
const instanceSecrets = { ...resolvedRoute.secrets };
|
package/dist/remote/server.js
CHANGED
|
@@ -1030,8 +1030,14 @@ export function createApp(options = {}) {
|
|
|
1030
1030
|
const ownKeys = options.ownKeys ?? loadKeyBundle(getServerKeysDir());
|
|
1031
1031
|
const authorizedPeers = options.authorizedPeers ?? loadCallerPeers(config.callers);
|
|
1032
1032
|
rateLimitPerMinute = config.rateLimitPerMinute;
|
|
1033
|
-
// Create or use the provided ingestor manager
|
|
1034
|
-
|
|
1033
|
+
// Create or use the provided ingestor manager.
|
|
1034
|
+
// When config is loaded from disk (production), pass loadRemoteConfig as the
|
|
1035
|
+
// config loader so startOne()/restartOne() read fresh config, picking up
|
|
1036
|
+
// changes made by tool handlers without requiring a server restart.
|
|
1037
|
+
// When config is injected via options (tests), omit the loader so the
|
|
1038
|
+
// IngestorManager uses the injected config snapshot.
|
|
1039
|
+
const configLoader = options.config ? undefined : loadRemoteConfig;
|
|
1040
|
+
const ingestorManager = options.ingestorManager ?? new IngestorManager(config, configLoader);
|
|
1035
1041
|
app.locals.ingestorManager = ingestorManager;
|
|
1036
1042
|
// Log connector and caller summary
|
|
1037
1043
|
const connectorCount = config.connectors?.length ?? 0;
|
|
@@ -1076,9 +1082,12 @@ export function createApp(options = {}) {
|
|
|
1076
1082
|
// Look up the caller alias by matching the returned PublicKeyBundle
|
|
1077
1083
|
const matchedPeer = authorizedPeers.find((p) => p.keys === initiatorPubKey);
|
|
1078
1084
|
const callerAlias = matchedPeer?.alias ?? 'unknown';
|
|
1085
|
+
// Reload config from disk so new sessions pick up changes made by tool
|
|
1086
|
+
// handlers (e.g. set_connection_enabled, set_secrets) without a restart.
|
|
1087
|
+
const freshConfig = options.config ?? loadRemoteConfig();
|
|
1079
1088
|
// Resolve per-caller routes (with optional env overrides)
|
|
1080
|
-
const callerRoutes = resolveCallerRoutes(
|
|
1081
|
-
const caller =
|
|
1089
|
+
const callerRoutes = resolveCallerRoutes(freshConfig, callerAlias);
|
|
1090
|
+
const caller = freshConfig.callers[callerAlias];
|
|
1082
1091
|
const callerEnvResolved = resolveSecrets(caller.env ?? {});
|
|
1083
1092
|
const callerResolvedRoutes = resolveRoutes(callerRoutes, callerEnvResolved, callerAlias);
|
|
1084
1093
|
// Store pending handshake for the finish step
|
|
@@ -1316,19 +1325,21 @@ export function createApp(options = {}) {
|
|
|
1316
1325
|
res.status(400).json({ error: 'INVALID_PAYLOAD', detail: `Invalid public keys: ${msg}` });
|
|
1317
1326
|
return;
|
|
1318
1327
|
}
|
|
1328
|
+
// Reload config from disk so we don't clobber changes made since startup
|
|
1329
|
+
const freshConfig = options.config ?? loadRemoteConfig();
|
|
1319
1330
|
// Register caller in config if not already present
|
|
1320
|
-
if (!(callerAlias in
|
|
1321
|
-
|
|
1331
|
+
if (!(callerAlias in freshConfig.callers)) {
|
|
1332
|
+
freshConfig.callers[callerAlias] = {
|
|
1322
1333
|
connections: [],
|
|
1323
1334
|
};
|
|
1324
|
-
saveRemoteConfig(
|
|
1335
|
+
saveRemoteConfig(freshConfig);
|
|
1325
1336
|
console.log(`[sync] Registered new caller "${callerAlias}" (0 connections — configure manually)`);
|
|
1326
1337
|
}
|
|
1327
1338
|
else {
|
|
1328
1339
|
console.log(`[sync] Caller "${callerAlias}" already exists, updated peer keys`);
|
|
1329
1340
|
}
|
|
1330
1341
|
// Reload authorized peers so the new caller can connect immediately
|
|
1331
|
-
const newPeer = loadCallerPeers({ [callerAlias]:
|
|
1342
|
+
const newPeer = loadCallerPeers({ [callerAlias]: freshConfig.callers[callerAlias] });
|
|
1332
1343
|
for (const p of newPeer) {
|
|
1333
1344
|
if (!authorizedPeers.find((existing) => existing.alias === p.alias)) {
|
|
1334
1345
|
authorizedPeers.push(p);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wolpertingerlabs/drawlatch",
|
|
3
|
-
"version": "1.0.0-alpha.9.
|
|
3
|
+
"version": "1.0.0-alpha.9.5",
|
|
4
4
|
"description": "Encrypted MCP proxy with mutual authentication. Local MCP server forwards requests through an encrypted channel to a remote secrets-holding server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/mcp/server.js",
|