@mindexec/cli 0.2.133 → 0.2.135
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/package.json +3 -2
- package/scripts/remote-fast-mdm-browser-smoke.mjs +13 -17
- package/scripts/remote-frame-ws-smoke.mjs +11 -9
- package/scripts/remote-registry-follower-smoke.mjs +322 -0
- package/server.js +693 -10
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +50 -4
- package/wwwroot/service-worker-assets.js +2 -2
- package/wwwroot/service-worker.js +1 -1
package/server.js
CHANGED
|
@@ -41,7 +41,7 @@ const VERBOSE_CODEX_TRACE = /^(1|true|yes|on)$/i.test(String(process.env.BRIDGE_
|
|
|
41
41
|
const COLOR_LOGS_ENABLED = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
42
42
|
const DEFAULT_WEB_APP_ROOT = path.join(BRIDGE_ROOT, 'wwwroot');
|
|
43
43
|
|
|
44
|
-
const ANSI = {
|
|
44
|
+
const ANSI = {
|
|
45
45
|
reset: '\x1b[0m',
|
|
46
46
|
bold: '\x1b[1m',
|
|
47
47
|
dim: '\x1b[2m',
|
|
@@ -52,8 +52,10 @@ const ANSI = {
|
|
|
52
52
|
blue: '\x1b[34m',
|
|
53
53
|
magenta: '\x1b[35m',
|
|
54
54
|
cyan: '\x1b[36m',
|
|
55
|
-
white: '\x1b[37m'
|
|
56
|
-
};
|
|
55
|
+
white: '\x1b[37m'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let isShuttingDown = false;
|
|
57
59
|
|
|
58
60
|
function paint(text, ...styles) {
|
|
59
61
|
const value = String(text ?? '');
|
|
@@ -2042,6 +2044,7 @@ app.post('/api/auth/session', async (req, res) => {
|
|
|
2042
2044
|
try {
|
|
2043
2045
|
const content = req.body?.content;
|
|
2044
2046
|
await writeStableAuthSessionPayload(content);
|
|
2047
|
+
await wakeRemoteRegistryFollower('auth-session-saved');
|
|
2045
2048
|
res.json({
|
|
2046
2049
|
success: true
|
|
2047
2050
|
});
|
|
@@ -2058,6 +2061,7 @@ app.post('/api/auth/session', async (req, res) => {
|
|
|
2058
2061
|
app.delete('/api/auth/session', async (req, res) => {
|
|
2059
2062
|
try {
|
|
2060
2063
|
await deleteStableAuthSessionPayload();
|
|
2064
|
+
await wakeRemoteRegistryFollower('auth-session-cleared');
|
|
2061
2065
|
res.json({
|
|
2062
2066
|
success: true
|
|
2063
2067
|
});
|
|
@@ -2124,6 +2128,11 @@ const remoteFrameWss = new WebSocketServer({ noServer: true });
|
|
|
2124
2128
|
const wsClients = new Set();
|
|
2125
2129
|
const remoteFrameClients = new Set();
|
|
2126
2130
|
const shellJobs = new Map();
|
|
2131
|
+
const REMOTE_FRAME_WS_AUTO_START_LIMIT = 120;
|
|
2132
|
+
const REMOTE_FRAME_WS_DEFAULT_FPS = 12;
|
|
2133
|
+
const REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH = 960;
|
|
2134
|
+
const REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT = 540;
|
|
2135
|
+
const REMOTE_FRAME_WS_DEFAULT_QUALITY = 60;
|
|
2127
2136
|
|
|
2128
2137
|
httpServer.on('upgrade', (req, socket, head) => {
|
|
2129
2138
|
try {
|
|
@@ -2213,12 +2222,101 @@ function updateRemoteFrameClientSubscription(ws, payload = {}) {
|
|
|
2213
2222
|
const deviceIds = normalizeRemoteFrameWsDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
|
|
2214
2223
|
ws.remoteFrameDeviceIds = new Set(deviceIds);
|
|
2215
2224
|
ws.remoteFrameSubscriptionUpdatedAt = new Date().toISOString();
|
|
2225
|
+
ws.remoteFrameAutoStartLive = /^(1|true|yes|on|live)$/i.test(String(payload.autoStartLive ?? payload.startLive ?? ''));
|
|
2226
|
+
ws.remoteFrameAutoStartOptions = normalizeRemoteFrameWsLiveOptions(payload);
|
|
2216
2227
|
sendRemoteFrameClientJson(ws, {
|
|
2217
2228
|
type: 'RemoteFrameSubscription',
|
|
2218
2229
|
timestamp: ws.remoteFrameSubscriptionUpdatedAt,
|
|
2219
2230
|
deviceIds,
|
|
2220
|
-
mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices'
|
|
2231
|
+
mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices',
|
|
2232
|
+
autoStartLive: ws.remoteFrameAutoStartLive === true
|
|
2221
2233
|
});
|
|
2234
|
+
maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds);
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
function clampRemoteFrameWsNumber(value, min, max, fallback) {
|
|
2238
|
+
const number = Number(value);
|
|
2239
|
+
if (!Number.isFinite(number)) {
|
|
2240
|
+
return fallback;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
return Math.max(min, Math.min(max, Math.round(number)));
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
function normalizeRemoteFrameWsLiveOptions(payload = {}) {
|
|
2247
|
+
return {
|
|
2248
|
+
fps: clampRemoteFrameWsNumber(payload.fps, 1, 24, REMOTE_FRAME_WS_DEFAULT_FPS),
|
|
2249
|
+
maxWidth: clampRemoteFrameWsNumber(payload.maxWidth, 320, 2560, REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH),
|
|
2250
|
+
maxHeight: clampRemoteFrameWsNumber(payload.maxHeight, 180, 1440, REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT),
|
|
2251
|
+
quality: clampRemoteFrameWsNumber(payload.quality, 20, 95, REMOTE_FRAME_WS_DEFAULT_QUALITY),
|
|
2252
|
+
mode: String(payload.mode || 'remote-fast').trim() || 'remote-fast'
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
function getRemoteFrameWsDeviceLookup() {
|
|
2257
|
+
const devices = remoteHub.listDevices({ includeDataUrl: false });
|
|
2258
|
+
return new Map(devices.map(device => [String(device?.deviceId || '').trim(), device]).filter(([id]) => id));
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
function maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds = []) {
|
|
2262
|
+
if (!ws || ws.remoteFrameAutoStartLive !== true || !Array.isArray(deviceIds) || deviceIds.length === 0) {
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
const options = ws.remoteFrameAutoStartOptions || normalizeRemoteFrameWsLiveOptions();
|
|
2267
|
+
const lookup = getRemoteFrameWsDeviceLookup();
|
|
2268
|
+
const started = [];
|
|
2269
|
+
const skipped = [];
|
|
2270
|
+
for (const deviceId of deviceIds.slice(0, REMOTE_FRAME_WS_AUTO_START_LIMIT)) {
|
|
2271
|
+
const device = lookup.get(String(deviceId || '').trim());
|
|
2272
|
+
if (!device?.connected) {
|
|
2273
|
+
skipped.push({ deviceId, reason: 'not-connected' });
|
|
2274
|
+
continue;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
if (device.liveStreamActive === true) {
|
|
2278
|
+
skipped.push({ deviceId, reason: 'already-live' });
|
|
2279
|
+
continue;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
const liveCapable = device.liveStreamEnabled === true
|
|
2283
|
+
|| device.liveStreamCapable === true
|
|
2284
|
+
|| device.capabilities?.liveStream === true
|
|
2285
|
+
|| device.capabilities?.stream === true
|
|
2286
|
+
|| device.capabilities?.screenStream === true;
|
|
2287
|
+
if (!liveCapable) {
|
|
2288
|
+
skipped.push({ deviceId, reason: 'live-unavailable' });
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
const result = remoteHub.startLiveStream(deviceId, {
|
|
2293
|
+
...options,
|
|
2294
|
+
streamId: `mdm-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
|
|
2295
|
+
});
|
|
2296
|
+
if (result?.ok === true) {
|
|
2297
|
+
started.push({
|
|
2298
|
+
deviceId,
|
|
2299
|
+
streamId: result.streamId || '',
|
|
2300
|
+
fps: result.fps || options.fps
|
|
2301
|
+
});
|
|
2302
|
+
} else {
|
|
2303
|
+
skipped.push({
|
|
2304
|
+
deviceId,
|
|
2305
|
+
reason: result?.error || 'start-failed'
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
if (started.length > 0 || skipped.length > 0) {
|
|
2311
|
+
sendRemoteFrameClientJson(ws, {
|
|
2312
|
+
type: 'RemoteFrameLiveAutoStart',
|
|
2313
|
+
timestamp: new Date().toISOString(),
|
|
2314
|
+
requested: Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT),
|
|
2315
|
+
started,
|
|
2316
|
+
skipped: skipped.slice(0, 24),
|
|
2317
|
+
options
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2222
2320
|
}
|
|
2223
2321
|
|
|
2224
2322
|
function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
@@ -2912,6 +3010,10 @@ const REMOTE_AGENT_MAX_PARALLEL_CANDIDATES = 6;
|
|
|
2912
3010
|
const REMOTE_AGENT_RECENT_MANAGER_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
2913
3011
|
const REMOTE_AGENT_RECENT_MANAGER_CACHE_LIMIT = 64;
|
|
2914
3012
|
const REMOTE_AGENT_RECENT_MANAGER_DEFAULT_KEY = 'default';
|
|
3013
|
+
const REMOTE_REGISTRY_FOLLOWER_ENABLED = !/^(0|false|no|off)$/i.test(String(process.env.MINDEXEC_REMOTE_REGISTRY_FOLLOWER || 'true'));
|
|
3014
|
+
const REMOTE_REGISTRY_FOLLOWER_POLL_MS = Math.max(1500, Number(process.env.MINDEXEC_REMOTE_REGISTRY_POLL_MS || 5000) || 5000);
|
|
3015
|
+
const REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS = Math.max(500, Number(process.env.MINDEXEC_REMOTE_REGISTRY_FAST_RETRY_MS || 1200) || 1200);
|
|
3016
|
+
const REMOTE_REGISTRY_FOLLOWER_MISSING_SESSION_STOP_COUNT = 3;
|
|
2915
3017
|
let remoteAgentState = createRemoteAgentIdleState();
|
|
2916
3018
|
let remoteAgentSyncReportState = null;
|
|
2917
3019
|
let remoteAgentSyncReportLogKey = '';
|
|
@@ -2920,6 +3022,23 @@ let remoteAgentConnectPromise = null;
|
|
|
2920
3022
|
const remoteAgentRecentSuccessfulManagers = new Map();
|
|
2921
3023
|
let remoteAgentRecentSuccessfulManagersLoaded = false;
|
|
2922
3024
|
let remoteAgentRecentSuccessfulManagersSavePromise = null;
|
|
3025
|
+
let remoteRegistryFollowerTimer = null;
|
|
3026
|
+
let remoteRegistryFollowerInFlight = false;
|
|
3027
|
+
let remoteRegistryFollowerStarted = false;
|
|
3028
|
+
let remoteRegistryFollowerConsecutiveMissingSession = 0;
|
|
3029
|
+
let remoteRegistryFollowerState = {
|
|
3030
|
+
enabled: REMOTE_REGISTRY_FOLLOWER_ENABLED,
|
|
3031
|
+
status: REMOTE_REGISTRY_FOLLOWER_ENABLED ? 'idle' : 'disabled',
|
|
3032
|
+
reason: '',
|
|
3033
|
+
lastAttemptAt: '',
|
|
3034
|
+
lastSuccessAt: '',
|
|
3035
|
+
lastError: '',
|
|
3036
|
+
targetEndpoint: '',
|
|
3037
|
+
targetEndpointCandidates: [],
|
|
3038
|
+
targetLeaseId: '',
|
|
3039
|
+
targetNodeId: '',
|
|
3040
|
+
authenticated: false
|
|
3041
|
+
};
|
|
2923
3042
|
|
|
2924
3043
|
function createRemoteAgentIdleState(overrides = {}) {
|
|
2925
3044
|
return {
|
|
@@ -4154,11 +4273,569 @@ async function startRemoteAgentConnectionAttempt(options = {}) {
|
|
|
4154
4273
|
return { ok: true, alreadyRunning: false, agent: serializeRemoteAgentState() };
|
|
4155
4274
|
}
|
|
4156
4275
|
|
|
4276
|
+
function serializeRemoteRegistryFollowerState() {
|
|
4277
|
+
return {
|
|
4278
|
+
...remoteRegistryFollowerState,
|
|
4279
|
+
pollMs: REMOTE_REGISTRY_FOLLOWER_POLL_MS,
|
|
4280
|
+
fastRetryMs: REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
function updateRemoteRegistryFollowerState(patch = {}) {
|
|
4285
|
+
remoteRegistryFollowerState = {
|
|
4286
|
+
...remoteRegistryFollowerState,
|
|
4287
|
+
...patch,
|
|
4288
|
+
enabled: REMOTE_REGISTRY_FOLLOWER_ENABLED
|
|
4289
|
+
};
|
|
4290
|
+
emitBridgeEvent('RemoteRegistryFollowerUpdated', serializeRemoteRegistryFollowerState());
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4293
|
+
function readSupabaseRuntimeConfig() {
|
|
4294
|
+
const envUrl = String(process.env.SUPABASE_URL || process.env.MINDEXEC_SUPABASE_URL || '').trim();
|
|
4295
|
+
const envKey = String(
|
|
4296
|
+
process.env.SUPABASE_KEY
|
|
4297
|
+
|| process.env.SUPABASE_ANON_KEY
|
|
4298
|
+
|| process.env.SUPABASE_PUBLISHABLE_KEY
|
|
4299
|
+
|| process.env.MINDEXEC_SUPABASE_KEY
|
|
4300
|
+
|| '').trim();
|
|
4301
|
+
if (envUrl && envKey) {
|
|
4302
|
+
return { url: envUrl.replace(/\/+$/, ''), key: envKey };
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
const candidates = [
|
|
4306
|
+
path.join(WEB_APP_ROOT || '', 'appsettings.json'),
|
|
4307
|
+
path.join(DEFAULT_WEB_APP_ROOT, 'appsettings.json'),
|
|
4308
|
+
path.resolve(BRIDGE_ROOT, '..', 'MindExecution.Web', 'wwwroot', 'appsettings.json')
|
|
4309
|
+
].filter(Boolean);
|
|
4310
|
+
|
|
4311
|
+
for (const candidate of Array.from(new Set(candidates.map(item => path.resolve(item))))) {
|
|
4312
|
+
try {
|
|
4313
|
+
const payload = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
4314
|
+
const url = String(payload?.Supabase?.Url || '').trim().replace(/\/+$/, '');
|
|
4315
|
+
const key = String(payload?.Supabase?.Key || payload?.Supabase?.AnonKey || payload?.Supabase?.PublishableKey || '').trim();
|
|
4316
|
+
if (url && key) {
|
|
4317
|
+
return { url, key };
|
|
4318
|
+
}
|
|
4319
|
+
} catch {
|
|
4320
|
+
// Try the next appsettings candidate.
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
return { url: '', key: '' };
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
function readSupabaseSessionField(source, ...keys) {
|
|
4328
|
+
for (const key of keys) {
|
|
4329
|
+
const value = source?.[key];
|
|
4330
|
+
if (value !== undefined && value !== null && String(value).trim()) {
|
|
4331
|
+
return value;
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
return '';
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
function parseSupabaseSessionForRegistry(content) {
|
|
4338
|
+
let session = null;
|
|
4339
|
+
try {
|
|
4340
|
+
session = JSON.parse(String(content || ''));
|
|
4341
|
+
} catch {
|
|
4342
|
+
return null;
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
const user = session?.user || session?.User || {};
|
|
4346
|
+
const accessToken = String(readSupabaseSessionField(session, 'access_token', 'accessToken', 'AccessToken')).trim();
|
|
4347
|
+
const userId = String(
|
|
4348
|
+
readSupabaseSessionField(user, 'id', 'Id')
|
|
4349
|
+
|| readSupabaseSessionField(session, 'user_id', 'userId', 'UserId')).trim();
|
|
4350
|
+
const expiresAtRaw = Number(readSupabaseSessionField(session, 'expires_at', 'expiresAt', 'ExpiresAt') || 0);
|
|
4351
|
+
const expiresAtMs = Number.isFinite(expiresAtRaw) && expiresAtRaw > 0
|
|
4352
|
+
? (expiresAtRaw > 9999999999 ? expiresAtRaw : expiresAtRaw * 1000)
|
|
4353
|
+
: 0;
|
|
4354
|
+
|
|
4355
|
+
if (!accessToken || !userId) {
|
|
4356
|
+
return null;
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
return {
|
|
4360
|
+
accessToken,
|
|
4361
|
+
userId,
|
|
4362
|
+
expiresAtMs
|
|
4363
|
+
};
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
function readRegistryTargetField(target, ...keys) {
|
|
4367
|
+
for (const key of keys) {
|
|
4368
|
+
const value = target?.[key];
|
|
4369
|
+
if (value !== undefined && value !== null) {
|
|
4370
|
+
return value;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
return '';
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4376
|
+
function parseRegistryEndpointCandidates(value) {
|
|
4377
|
+
if (Array.isArray(value)) {
|
|
4378
|
+
return value.map(item => String(item || '').trim()).filter(Boolean);
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
const text = String(value || '').trim();
|
|
4382
|
+
if (!text) {
|
|
4383
|
+
return [];
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
try {
|
|
4387
|
+
const parsed = JSON.parse(text);
|
|
4388
|
+
if (Array.isArray(parsed)) {
|
|
4389
|
+
return parsed.map(item => String(item || '').trim()).filter(Boolean);
|
|
4390
|
+
}
|
|
4391
|
+
} catch {
|
|
4392
|
+
// Fall through to comma/newline split.
|
|
4393
|
+
}
|
|
4394
|
+
|
|
4395
|
+
return text.split(/[,\n]/g).map(item => item.trim()).filter(Boolean);
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
function normalizeRemoteRegistryTarget(row) {
|
|
4399
|
+
if (!row || typeof row !== 'object') {
|
|
4400
|
+
return null;
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
const activeRaw = readRegistryTargetField(row, 'active', 'Active');
|
|
4404
|
+
const active = activeRaw === true || /^(1|true|yes|on)$/i.test(String(activeRaw || ''));
|
|
4405
|
+
const endpoint = String(readRegistryTargetField(row, 'endpoint', 'Endpoint') || '').trim();
|
|
4406
|
+
const endpointCandidates = normalizeRemoteManagerEndpointList(
|
|
4407
|
+
parseRegistryEndpointCandidates(readRegistryTargetField(row, 'endpoint_candidates', 'endpointCandidates', 'EndpointCandidates')),
|
|
4408
|
+
endpoint);
|
|
4409
|
+
const pairToken = String(readRegistryTargetField(row, 'pair_token', 'pairToken', 'PairToken') || '').trim();
|
|
4410
|
+
const leaseId = String(readRegistryTargetField(row, 'lease_id', 'leaseId', 'LeaseId') || '').trim();
|
|
4411
|
+
const nodeId = String(readRegistryTargetField(row, 'node_id', 'nodeId', 'NodeId') || '').trim();
|
|
4412
|
+
const hostInstanceId = String(readRegistryTargetField(row, 'host_instance_id', 'hostInstanceId', 'HostInstanceId') || '').trim();
|
|
4413
|
+
const expiresAt = String(readRegistryTargetField(row, 'expires_at', 'expiresAt', 'ExpiresAt') || '').trim();
|
|
4414
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
4415
|
+
|
|
4416
|
+
return {
|
|
4417
|
+
active,
|
|
4418
|
+
endpoint,
|
|
4419
|
+
endpointCandidates,
|
|
4420
|
+
pairToken,
|
|
4421
|
+
leaseId,
|
|
4422
|
+
nodeId,
|
|
4423
|
+
hostInstanceId,
|
|
4424
|
+
expiresAt,
|
|
4425
|
+
expiresAtMs: Number.isFinite(expiresAtMs) ? expiresAtMs : 0
|
|
4426
|
+
};
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
function isRemoteRegistryTargetExpired(target) {
|
|
4430
|
+
return !target?.expiresAtMs || target.expiresAtMs <= Date.now();
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
function isRemoteRegistryTargetSameAsLocalHost(localHub, target) {
|
|
4434
|
+
const localLeaseId = safeRemoteAgentField(localHub?.hostTargetLeaseId, 128);
|
|
4435
|
+
const localHostInstanceId = safeRemoteAgentField(localHub?.hostTargetHostInstanceId || localHub?.hostInstanceId, 128);
|
|
4436
|
+
return !!localLeaseId
|
|
4437
|
+
&& !!localHostInstanceId
|
|
4438
|
+
&& !!target?.leaseId
|
|
4439
|
+
&& !!target?.hostInstanceId
|
|
4440
|
+
&& localLeaseId.toLowerCase() === String(target.leaseId).toLowerCase()
|
|
4441
|
+
&& localHostInstanceId.toLowerCase() === String(target.hostInstanceId).toLowerCase();
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
async function fetchRemoteRegistryTarget(config, session) {
|
|
4445
|
+
const url = new URL('/rest/v1/remote_host_targets', config.url);
|
|
4446
|
+
url.searchParams.set('select', '*');
|
|
4447
|
+
url.searchParams.set('user_id', `eq.${session.userId}`);
|
|
4448
|
+
url.searchParams.set('limit', '1');
|
|
4449
|
+
|
|
4450
|
+
const response = await fetch(url.toString(), {
|
|
4451
|
+
method: 'GET',
|
|
4452
|
+
headers: {
|
|
4453
|
+
apikey: config.key,
|
|
4454
|
+
Authorization: `Bearer ${session.accessToken}`,
|
|
4455
|
+
Accept: 'application/json'
|
|
4456
|
+
}
|
|
4457
|
+
});
|
|
4458
|
+
|
|
4459
|
+
if (!response.ok) {
|
|
4460
|
+
const text = await response.text().catch(() => '');
|
|
4461
|
+
const error = new Error(`registry-rest-${response.status}${text ? `:${shortenText(text, 160)}` : ''}`);
|
|
4462
|
+
error.statusCode = response.status;
|
|
4463
|
+
throw error;
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
const payload = await response.json();
|
|
4467
|
+
return normalizeRemoteRegistryTarget(Array.isArray(payload) ? payload[0] : payload);
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
async function reportRemoteRegistryFollowerSync(payload = {}) {
|
|
4471
|
+
const report = createRemoteAgentSyncReport(payload);
|
|
4472
|
+
rememberRemoteAgentSyncReport(report);
|
|
4473
|
+
return report;
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
function scheduleRemoteRegistryFollower(delayMs = REMOTE_REGISTRY_FOLLOWER_POLL_MS, reason = 'timer') {
|
|
4477
|
+
if (!REMOTE_REGISTRY_FOLLOWER_ENABLED || isShuttingDown) {
|
|
4478
|
+
return;
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
if (remoteRegistryFollowerTimer) {
|
|
4482
|
+
clearTimeout(remoteRegistryFollowerTimer);
|
|
4483
|
+
remoteRegistryFollowerTimer = null;
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4486
|
+
remoteRegistryFollowerTimer = setTimeout(() => {
|
|
4487
|
+
remoteRegistryFollowerTimer = null;
|
|
4488
|
+
runRemoteRegistryFollowerOnce(reason).catch(error => {
|
|
4489
|
+
logWarn('remote', `registry follower failed: ${error?.message || error}`);
|
|
4490
|
+
updateRemoteRegistryFollowerState({
|
|
4491
|
+
status: 'error',
|
|
4492
|
+
reason: 'follower-error',
|
|
4493
|
+
lastError: error?.message || String(error || ''),
|
|
4494
|
+
lastAttemptAt: new Date().toISOString()
|
|
4495
|
+
});
|
|
4496
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS, 'error-retry');
|
|
4497
|
+
});
|
|
4498
|
+
}, Math.max(0, Number(delayMs) || 0));
|
|
4499
|
+
remoteRegistryFollowerTimer?.unref?.();
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
async function wakeRemoteRegistryFollower(reason = 'wake') {
|
|
4503
|
+
if (!REMOTE_REGISTRY_FOLLOWER_ENABLED) {
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
scheduleRemoteRegistryFollower(0, reason);
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
async function runRemoteRegistryFollowerOnce(trigger = 'timer') {
|
|
4510
|
+
if (!REMOTE_REGISTRY_FOLLOWER_ENABLED || remoteRegistryFollowerInFlight) {
|
|
4511
|
+
return serializeRemoteRegistryFollowerState();
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
remoteRegistryFollowerInFlight = true;
|
|
4515
|
+
const attemptedAt = new Date().toISOString();
|
|
4516
|
+
try {
|
|
4517
|
+
const config = readSupabaseRuntimeConfig();
|
|
4518
|
+
if (!config.url || !config.key) {
|
|
4519
|
+
updateRemoteRegistryFollowerState({
|
|
4520
|
+
status: 'skipped',
|
|
4521
|
+
reason: 'supabase-config-missing',
|
|
4522
|
+
authenticated: false,
|
|
4523
|
+
lastAttemptAt: attemptedAt,
|
|
4524
|
+
lastError: ''
|
|
4525
|
+
});
|
|
4526
|
+
await reportRemoteRegistryFollowerSync({
|
|
4527
|
+
ok: false,
|
|
4528
|
+
skipped: true,
|
|
4529
|
+
reason: 'supabase-config-missing',
|
|
4530
|
+
trigger,
|
|
4531
|
+
authenticated: false
|
|
4532
|
+
});
|
|
4533
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'config-missing');
|
|
4534
|
+
return serializeRemoteRegistryFollowerState();
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
const sessionPayload = await readStableAuthSessionPayload();
|
|
4538
|
+
const session = sessionPayload.found ? parseSupabaseSessionForRegistry(sessionPayload.content) : null;
|
|
4539
|
+
if (!session) {
|
|
4540
|
+
remoteRegistryFollowerConsecutiveMissingSession += 1;
|
|
4541
|
+
if (remoteRegistryFollowerConsecutiveMissingSession >= REMOTE_REGISTRY_FOLLOWER_MISSING_SESSION_STOP_COUNT
|
|
4542
|
+
&& isRemoteAgentProcessRunning()) {
|
|
4543
|
+
await stopRemoteAgentConnection('registry-not-authenticated');
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4546
|
+
updateRemoteRegistryFollowerState({
|
|
4547
|
+
status: 'skipped',
|
|
4548
|
+
reason: 'registry-not-authenticated',
|
|
4549
|
+
authenticated: false,
|
|
4550
|
+
lastAttemptAt: attemptedAt,
|
|
4551
|
+
lastError: '',
|
|
4552
|
+
targetEndpoint: '',
|
|
4553
|
+
targetEndpointCandidates: [],
|
|
4554
|
+
targetLeaseId: '',
|
|
4555
|
+
targetNodeId: ''
|
|
4556
|
+
});
|
|
4557
|
+
await reportRemoteRegistryFollowerSync({
|
|
4558
|
+
ok: true,
|
|
4559
|
+
skipped: true,
|
|
4560
|
+
reason: 'registry-not-authenticated',
|
|
4561
|
+
trigger,
|
|
4562
|
+
authenticated: false
|
|
4563
|
+
});
|
|
4564
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'auth-missing');
|
|
4565
|
+
return serializeRemoteRegistryFollowerState();
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
remoteRegistryFollowerConsecutiveMissingSession = 0;
|
|
4569
|
+
if (session.expiresAtMs && session.expiresAtMs <= Date.now()) {
|
|
4570
|
+
updateRemoteRegistryFollowerState({
|
|
4571
|
+
status: 'skipped',
|
|
4572
|
+
reason: 'session-expired',
|
|
4573
|
+
authenticated: false,
|
|
4574
|
+
lastAttemptAt: attemptedAt,
|
|
4575
|
+
lastError: ''
|
|
4576
|
+
});
|
|
4577
|
+
await reportRemoteRegistryFollowerSync({
|
|
4578
|
+
ok: true,
|
|
4579
|
+
skipped: true,
|
|
4580
|
+
reason: 'session-expired',
|
|
4581
|
+
trigger,
|
|
4582
|
+
authenticated: false
|
|
4583
|
+
});
|
|
4584
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'session-expired');
|
|
4585
|
+
return serializeRemoteRegistryFollowerState();
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4588
|
+
const localHub = remoteHub.getStatus({ includeSecrets: false });
|
|
4589
|
+
const target = await fetchRemoteRegistryTarget(config, session);
|
|
4590
|
+
|
|
4591
|
+
if (localHub?.hostTargetActive === true) {
|
|
4592
|
+
if (target?.active === true
|
|
4593
|
+
&& !isRemoteRegistryTargetExpired(target)
|
|
4594
|
+
&& !isRemoteRegistryTargetSameAsLocalHost(localHub, target)) {
|
|
4595
|
+
remoteHub.setHostTarget({
|
|
4596
|
+
enabled: false,
|
|
4597
|
+
nodeId: localHub.hostTargetNodeId
|
|
4598
|
+
});
|
|
4599
|
+
updateRemoteRegistryFollowerState({
|
|
4600
|
+
status: 'local-host-superseded',
|
|
4601
|
+
reason: 'local-host-superseded',
|
|
4602
|
+
authenticated: true,
|
|
4603
|
+
lastAttemptAt: attemptedAt,
|
|
4604
|
+
lastSuccessAt: attemptedAt,
|
|
4605
|
+
targetEndpoint: target.endpoint,
|
|
4606
|
+
targetEndpointCandidates: target.endpointCandidates,
|
|
4607
|
+
targetLeaseId: target.leaseId,
|
|
4608
|
+
targetNodeId: target.nodeId,
|
|
4609
|
+
lastError: ''
|
|
4610
|
+
});
|
|
4611
|
+
await reportRemoteRegistryFollowerSync({
|
|
4612
|
+
ok: true,
|
|
4613
|
+
attempted: true,
|
|
4614
|
+
disconnected: true,
|
|
4615
|
+
reason: 'local-host-superseded',
|
|
4616
|
+
trigger,
|
|
4617
|
+
authenticated: true,
|
|
4618
|
+
targetActive: true,
|
|
4619
|
+
targetEndpoint: target.endpoint,
|
|
4620
|
+
targetEndpointCandidates: target.endpointCandidates,
|
|
4621
|
+
targetLeaseId: target.leaseId,
|
|
4622
|
+
targetNodeId: target.nodeId,
|
|
4623
|
+
targetExpiresAt: target.expiresAt
|
|
4624
|
+
});
|
|
4625
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS, 'local-host-cleared');
|
|
4626
|
+
return serializeRemoteRegistryFollowerState();
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4629
|
+
if (isRemoteAgentProcessRunning()) {
|
|
4630
|
+
await stopRemoteAgentConnection('local-host-target-active');
|
|
4631
|
+
}
|
|
4632
|
+
updateRemoteRegistryFollowerState({
|
|
4633
|
+
status: 'skipped',
|
|
4634
|
+
reason: 'local-monitor-is-host',
|
|
4635
|
+
authenticated: true,
|
|
4636
|
+
lastAttemptAt: attemptedAt,
|
|
4637
|
+
lastSuccessAt: attemptedAt,
|
|
4638
|
+
lastError: ''
|
|
4639
|
+
});
|
|
4640
|
+
await reportRemoteRegistryFollowerSync({
|
|
4641
|
+
ok: true,
|
|
4642
|
+
skipped: true,
|
|
4643
|
+
reason: 'local-monitor-is-host',
|
|
4644
|
+
trigger,
|
|
4645
|
+
authenticated: true,
|
|
4646
|
+
localHostTargetActive: true,
|
|
4647
|
+
localHostTargetLeaseId: localHub.hostTargetLeaseId,
|
|
4648
|
+
localHostTargetNodeId: localHub.hostTargetNodeId
|
|
4649
|
+
});
|
|
4650
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'local-host');
|
|
4651
|
+
return serializeRemoteRegistryFollowerState();
|
|
4652
|
+
}
|
|
4653
|
+
|
|
4654
|
+
if (!target?.active || isRemoteRegistryTargetExpired(target)) {
|
|
4655
|
+
if (isRemoteAgentProcessRunning()) {
|
|
4656
|
+
await stopRemoteAgentConnection('registry-inactive');
|
|
4657
|
+
}
|
|
4658
|
+
updateRemoteRegistryFollowerState({
|
|
4659
|
+
status: 'idle',
|
|
4660
|
+
reason: 'no-active-target',
|
|
4661
|
+
authenticated: true,
|
|
4662
|
+
lastAttemptAt: attemptedAt,
|
|
4663
|
+
lastSuccessAt: attemptedAt,
|
|
4664
|
+
targetEndpoint: '',
|
|
4665
|
+
targetEndpointCandidates: [],
|
|
4666
|
+
targetLeaseId: '',
|
|
4667
|
+
targetNodeId: '',
|
|
4668
|
+
lastError: ''
|
|
4669
|
+
});
|
|
4670
|
+
await reportRemoteRegistryFollowerSync({
|
|
4671
|
+
ok: true,
|
|
4672
|
+
skipped: true,
|
|
4673
|
+
reason: 'no-active-target',
|
|
4674
|
+
trigger,
|
|
4675
|
+
authenticated: true
|
|
4676
|
+
});
|
|
4677
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'no-target');
|
|
4678
|
+
return serializeRemoteRegistryFollowerState();
|
|
4679
|
+
}
|
|
4680
|
+
|
|
4681
|
+
if (isRemoteRegistryTargetSameAsLocalHost(localHub, target)) {
|
|
4682
|
+
updateRemoteRegistryFollowerState({
|
|
4683
|
+
status: 'skipped',
|
|
4684
|
+
reason: 'same-host-lease',
|
|
4685
|
+
authenticated: true,
|
|
4686
|
+
lastAttemptAt: attemptedAt,
|
|
4687
|
+
lastSuccessAt: attemptedAt,
|
|
4688
|
+
targetEndpoint: target.endpoint,
|
|
4689
|
+
targetEndpointCandidates: target.endpointCandidates,
|
|
4690
|
+
targetLeaseId: target.leaseId,
|
|
4691
|
+
targetNodeId: target.nodeId,
|
|
4692
|
+
lastError: ''
|
|
4693
|
+
});
|
|
4694
|
+
await reportRemoteRegistryFollowerSync({
|
|
4695
|
+
ok: true,
|
|
4696
|
+
skipped: true,
|
|
4697
|
+
reason: 'same-host-lease',
|
|
4698
|
+
trigger,
|
|
4699
|
+
authenticated: true,
|
|
4700
|
+
targetActive: true,
|
|
4701
|
+
targetEndpoint: target.endpoint,
|
|
4702
|
+
targetEndpointCandidates: target.endpointCandidates,
|
|
4703
|
+
targetLeaseId: target.leaseId,
|
|
4704
|
+
targetNodeId: target.nodeId,
|
|
4705
|
+
targetExpiresAt: target.expiresAt
|
|
4706
|
+
});
|
|
4707
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'same-host');
|
|
4708
|
+
return serializeRemoteRegistryFollowerState();
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4711
|
+
if (target.endpointCandidates.length === 0 || !target.pairToken) {
|
|
4712
|
+
updateRemoteRegistryFollowerState({
|
|
4713
|
+
status: 'skipped',
|
|
4714
|
+
reason: 'target-missing-endpoint-or-pair',
|
|
4715
|
+
authenticated: true,
|
|
4716
|
+
lastAttemptAt: attemptedAt,
|
|
4717
|
+
lastSuccessAt: attemptedAt,
|
|
4718
|
+
targetEndpoint: target.endpoint,
|
|
4719
|
+
targetEndpointCandidates: target.endpointCandidates,
|
|
4720
|
+
targetLeaseId: target.leaseId,
|
|
4721
|
+
targetNodeId: target.nodeId,
|
|
4722
|
+
lastError: ''
|
|
4723
|
+
});
|
|
4724
|
+
await reportRemoteRegistryFollowerSync({
|
|
4725
|
+
ok: true,
|
|
4726
|
+
skipped: true,
|
|
4727
|
+
reason: 'target-missing-endpoint-or-pair',
|
|
4728
|
+
trigger,
|
|
4729
|
+
authenticated: true,
|
|
4730
|
+
targetActive: true,
|
|
4731
|
+
targetEndpoint: target.endpoint,
|
|
4732
|
+
targetEndpointCandidates: target.endpointCandidates,
|
|
4733
|
+
targetLeaseId: target.leaseId,
|
|
4734
|
+
targetNodeId: target.nodeId,
|
|
4735
|
+
targetExpiresAt: target.expiresAt
|
|
4736
|
+
});
|
|
4737
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'target-incomplete');
|
|
4738
|
+
return serializeRemoteRegistryFollowerState();
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4741
|
+
const connect = await startRemoteAgentConnection({
|
|
4742
|
+
manager: target.endpointCandidates[0],
|
|
4743
|
+
managerCandidates: target.endpointCandidates,
|
|
4744
|
+
pairToken: target.pairToken,
|
|
4745
|
+
leaseId: target.leaseId,
|
|
4746
|
+
nodeId: target.nodeId,
|
|
4747
|
+
engine: 'auto',
|
|
4748
|
+
source: 'local-bridge-registry'
|
|
4749
|
+
});
|
|
4750
|
+
|
|
4751
|
+
const ok = connect?.ok === true;
|
|
4752
|
+
updateRemoteRegistryFollowerState({
|
|
4753
|
+
status: ok ? 'connected' : 'connect-failed',
|
|
4754
|
+
reason: ok ? (connect.alreadyRunning ? 'already-running' : 'connected') : 'connect-failed',
|
|
4755
|
+
authenticated: true,
|
|
4756
|
+
lastAttemptAt: attemptedAt,
|
|
4757
|
+
lastSuccessAt: ok ? new Date().toISOString() : remoteRegistryFollowerState.lastSuccessAt,
|
|
4758
|
+
targetEndpoint: target.endpointCandidates[0],
|
|
4759
|
+
targetEndpointCandidates: target.endpointCandidates,
|
|
4760
|
+
targetLeaseId: target.leaseId,
|
|
4761
|
+
targetNodeId: target.nodeId,
|
|
4762
|
+
lastError: ok ? '' : (connect?.error || 'remote-agent-connect-failed')
|
|
4763
|
+
});
|
|
4764
|
+
await reportRemoteRegistryFollowerSync({
|
|
4765
|
+
ok,
|
|
4766
|
+
attempted: true,
|
|
4767
|
+
connected: ok,
|
|
4768
|
+
skipped: connect?.alreadyRunning === true,
|
|
4769
|
+
reason: ok ? (connect.alreadyRunning ? 'already-running' : 'connected') : 'connect-failed',
|
|
4770
|
+
error: ok ? '' : connect?.error,
|
|
4771
|
+
trigger,
|
|
4772
|
+
authenticated: true,
|
|
4773
|
+
targetActive: true,
|
|
4774
|
+
targetEndpoint: target.endpointCandidates[0],
|
|
4775
|
+
targetEndpointCandidates: target.endpointCandidates,
|
|
4776
|
+
targetLeaseId: target.leaseId,
|
|
4777
|
+
targetNodeId: target.nodeId,
|
|
4778
|
+
targetExpiresAt: target.expiresAt
|
|
4779
|
+
});
|
|
4780
|
+
|
|
4781
|
+
scheduleRemoteRegistryFollower(ok ? REMOTE_REGISTRY_FOLLOWER_POLL_MS : REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS, ok ? 'connected' : 'connect-failed');
|
|
4782
|
+
return serializeRemoteRegistryFollowerState();
|
|
4783
|
+
} catch (error) {
|
|
4784
|
+
const transientAuth = error?.statusCode === 401 || error?.statusCode === 403;
|
|
4785
|
+
updateRemoteRegistryFollowerState({
|
|
4786
|
+
status: transientAuth ? 'auth-pending' : 'error',
|
|
4787
|
+
reason: transientAuth ? 'registry-auth-pending' : 'registry-sync-failed',
|
|
4788
|
+
authenticated: false,
|
|
4789
|
+
lastAttemptAt: attemptedAt,
|
|
4790
|
+
lastError: error?.message || String(error || '')
|
|
4791
|
+
});
|
|
4792
|
+
await reportRemoteRegistryFollowerSync({
|
|
4793
|
+
ok: isRemoteAgentProcessRunning(),
|
|
4794
|
+
skipped: true,
|
|
4795
|
+
reason: transientAuth ? 'registry-auth-pending-agent-kept' : 'registry-sync-failed',
|
|
4796
|
+
error: error?.message || String(error || ''),
|
|
4797
|
+
trigger,
|
|
4798
|
+
authenticated: false
|
|
4799
|
+
});
|
|
4800
|
+
scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS, transientAuth ? 'auth-pending' : 'sync-error');
|
|
4801
|
+
return serializeRemoteRegistryFollowerState();
|
|
4802
|
+
} finally {
|
|
4803
|
+
remoteRegistryFollowerInFlight = false;
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
function startRemoteRegistryFollower() {
|
|
4808
|
+
if (!REMOTE_REGISTRY_FOLLOWER_ENABLED || remoteRegistryFollowerStarted) {
|
|
4809
|
+
return;
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4812
|
+
remoteRegistryFollowerStarted = true;
|
|
4813
|
+
updateRemoteRegistryFollowerState({
|
|
4814
|
+
status: 'starting',
|
|
4815
|
+
reason: 'startup',
|
|
4816
|
+
lastAttemptAt: '',
|
|
4817
|
+
lastError: ''
|
|
4818
|
+
});
|
|
4819
|
+
scheduleRemoteRegistryFollower(250, 'startup');
|
|
4820
|
+
}
|
|
4821
|
+
|
|
4822
|
+
function stopRemoteRegistryFollower() {
|
|
4823
|
+
if (remoteRegistryFollowerTimer) {
|
|
4824
|
+
clearTimeout(remoteRegistryFollowerTimer);
|
|
4825
|
+
remoteRegistryFollowerTimer = null;
|
|
4826
|
+
}
|
|
4827
|
+
remoteRegistryFollowerStarted = false;
|
|
4828
|
+
updateRemoteRegistryFollowerState({
|
|
4829
|
+
status: REMOTE_REGISTRY_FOLLOWER_ENABLED ? 'stopped' : 'disabled',
|
|
4830
|
+
reason: 'bridge-shutdown'
|
|
4831
|
+
});
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4157
4834
|
function trimShellOutput(value, maxLength) {
|
|
4158
4835
|
const text = String(value || '');
|
|
4159
4836
|
if (text.length <= maxLength) {
|
|
4160
|
-
return text;
|
|
4161
|
-
}
|
|
4837
|
+
return text;
|
|
4838
|
+
}
|
|
4162
4839
|
|
|
4163
4840
|
return text.slice(text.length - maxLength);
|
|
4164
4841
|
}
|
|
@@ -8610,6 +9287,7 @@ app.get('/api/status', async (req, res) => {
|
|
|
8610
9287
|
bridgeAuthRequired,
|
|
8611
9288
|
remoteHub: remoteHub.getStatus({ includeSecrets: false }),
|
|
8612
9289
|
remoteAgent: serializeRemoteAgentState(),
|
|
9290
|
+
remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
|
|
8613
9291
|
shellJobsPath: '/api/shell/jobs',
|
|
8614
9292
|
companyCore: {
|
|
8615
9293
|
baseUrl: companyCoreBaseUrl,
|
|
@@ -10400,15 +11078,14 @@ async function startBridgeServer() {
|
|
|
10400
11078
|
`${tone('listen', 'muted')} ${tone(`http://127.0.0.1:${PORT}`, 'path')}`,
|
|
10401
11079
|
`${tone('stop', 'muted')} Ctrl+C`
|
|
10402
11080
|
]);
|
|
11081
|
+
startRemoteRegistryFollower();
|
|
10403
11082
|
});
|
|
10404
11083
|
}
|
|
10405
11084
|
|
|
10406
11085
|
await startBridgeServer();
|
|
10407
11086
|
|
|
10408
|
-
// Graceful shutdown
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
async function shutdownBridge(signal) {
|
|
11087
|
+
// Graceful shutdown
|
|
11088
|
+
async function shutdownBridge(signal) {
|
|
10412
11089
|
if (isShuttingDown) return;
|
|
10413
11090
|
isShuttingDown = true;
|
|
10414
11091
|
|
|
@@ -10435,6 +11112,12 @@ async function shutdownBridge(signal) {
|
|
|
10435
11112
|
// Ignore if already closed
|
|
10436
11113
|
}
|
|
10437
11114
|
|
|
11115
|
+
try {
|
|
11116
|
+
stopRemoteRegistryFollower();
|
|
11117
|
+
} catch {
|
|
11118
|
+
// Ignore registry follower shutdown errors
|
|
11119
|
+
}
|
|
11120
|
+
|
|
10438
11121
|
try {
|
|
10439
11122
|
await stopRemoteAgentConnection('bridge-shutdown');
|
|
10440
11123
|
} catch {
|