@mindexec/cli 0.2.151 → 0.2.153
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 +6 -5
- package/scripts/remote-fleet-render-smoke.mjs +38 -14
- package/scripts/remote-input-ws-smoke.mjs +263 -0
- package/scripts/remote-registry-follower-smoke.mjs +115 -9
- package/server.js +642 -5
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +314 -45
- package/wwwroot/service-worker-assets.js +2 -2
- package/wwwroot/service-worker.js +1 -1
package/server.js
CHANGED
|
@@ -2125,10 +2125,13 @@ app.get('/api/local-files/:id', async (req, res) => {
|
|
|
2125
2125
|
const httpServer = createServer(app);
|
|
2126
2126
|
const wss = new WebSocketServer({ noServer: true });
|
|
2127
2127
|
const remoteFrameWss = new WebSocketServer({ noServer: true });
|
|
2128
|
+
const remoteInputWss = new WebSocketServer({ noServer: true });
|
|
2128
2129
|
const wsClients = new Set();
|
|
2129
2130
|
const remoteFrameClients = new Set();
|
|
2131
|
+
const remoteInputClients = new Set();
|
|
2130
2132
|
const shellJobs = new Map();
|
|
2131
2133
|
let remoteFrameClientSeq = 0;
|
|
2134
|
+
let remoteInputClientSeq = 0;
|
|
2132
2135
|
const remoteFrameWsDiagnostics = {
|
|
2133
2136
|
opened: 0,
|
|
2134
2137
|
closed: 0,
|
|
@@ -2157,6 +2160,25 @@ const remoteFrameWsDiagnostics = {
|
|
|
2157
2160
|
lastAutoStartStarted: 0,
|
|
2158
2161
|
lastAutoStartSkipped: 0
|
|
2159
2162
|
};
|
|
2163
|
+
const remoteInputWsDiagnostics = {
|
|
2164
|
+
opened: 0,
|
|
2165
|
+
closed: 0,
|
|
2166
|
+
errors: 0,
|
|
2167
|
+
messagesReceived: 0,
|
|
2168
|
+
messagesInvalid: 0,
|
|
2169
|
+
inputsQueued: 0,
|
|
2170
|
+
inputsFailed: 0,
|
|
2171
|
+
pointerMovesQueued: 0,
|
|
2172
|
+
keyEventsQueued: 0,
|
|
2173
|
+
lastOpenAt: '',
|
|
2174
|
+
lastCloseAt: '',
|
|
2175
|
+
lastErrorAt: '',
|
|
2176
|
+
lastInputAt: '',
|
|
2177
|
+
lastDeviceId: '',
|
|
2178
|
+
lastInputType: '',
|
|
2179
|
+
lastCommandId: '',
|
|
2180
|
+
lastError: ''
|
|
2181
|
+
};
|
|
2160
2182
|
const REMOTE_FRAME_WS_AUTO_START_LIMIT = 120;
|
|
2161
2183
|
const REMOTE_FRAME_WS_DEFAULT_FPS = 12;
|
|
2162
2184
|
const REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH = 960;
|
|
@@ -2183,6 +2205,14 @@ httpServer.on('upgrade', (req, socket, head) => {
|
|
|
2183
2205
|
return;
|
|
2184
2206
|
}
|
|
2185
2207
|
|
|
2208
|
+
if (parsed.pathname === '/api/remote/input/ws'
|
|
2209
|
+
&& (!bridgeAuthRequired || token === bridgeToken)) {
|
|
2210
|
+
remoteInputWss.handleUpgrade(req, socket, head, (ws) => {
|
|
2211
|
+
remoteInputWss.emit('connection', ws, req);
|
|
2212
|
+
});
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2186
2216
|
{
|
|
2187
2217
|
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
2188
2218
|
socket.destroy();
|
|
@@ -2552,6 +2582,195 @@ remoteFrameWss.on('connection', (ws, req) => {
|
|
|
2552
2582
|
});
|
|
2553
2583
|
});
|
|
2554
2584
|
|
|
2585
|
+
function sendRemoteInputClientJson(ws, payload) {
|
|
2586
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
2587
|
+
return false;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
try {
|
|
2591
|
+
ws.send(JSON.stringify(payload));
|
|
2592
|
+
return true;
|
|
2593
|
+
} catch {
|
|
2594
|
+
return false;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
function normalizeRemoteInputWsPayload(payload = {}) {
|
|
2599
|
+
const outer = payload && typeof payload === 'object' ? payload : {};
|
|
2600
|
+
const inputSource = outer.input && typeof outer.input === 'object'
|
|
2601
|
+
? outer.input
|
|
2602
|
+
: outer.Input && typeof outer.Input === 'object'
|
|
2603
|
+
? outer.Input
|
|
2604
|
+
: outer;
|
|
2605
|
+
const input = { ...inputSource };
|
|
2606
|
+
input.type = String(input.type ?? input.Type ?? '').trim();
|
|
2607
|
+
const deviceId = String(
|
|
2608
|
+
outer.deviceId
|
|
2609
|
+
?? outer.DeviceId
|
|
2610
|
+
?? input.deviceId
|
|
2611
|
+
?? input.DeviceId
|
|
2612
|
+
?? ''
|
|
2613
|
+
).trim();
|
|
2614
|
+
delete input.deviceId;
|
|
2615
|
+
delete input.DeviceId;
|
|
2616
|
+
|
|
2617
|
+
return {
|
|
2618
|
+
deviceId,
|
|
2619
|
+
input,
|
|
2620
|
+
requestId: String(outer.requestId ?? outer.RequestId ?? '').trim(),
|
|
2621
|
+
ack: outer.ack === true || outer.Ack === true
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
function serializeRemoteInputWsDiagnostics() {
|
|
2626
|
+
const clients = [];
|
|
2627
|
+
for (const client of remoteInputClients) {
|
|
2628
|
+
clients.push({
|
|
2629
|
+
id: client.remoteInputClientId || '',
|
|
2630
|
+
readyState: client.readyState,
|
|
2631
|
+
connectedAt: client.remoteInputConnectedAt || '',
|
|
2632
|
+
received: Number(client.remoteInputReceivedCount || 0),
|
|
2633
|
+
queued: Number(client.remoteInputQueuedCount || 0),
|
|
2634
|
+
failed: Number(client.remoteInputFailedCount || 0),
|
|
2635
|
+
bufferedAmount: Number(client.bufferedAmount || 0),
|
|
2636
|
+
lastInputAt: client.remoteInputLastInputAt || '',
|
|
2637
|
+
lastDeviceId: client.remoteInputLastDeviceId || '',
|
|
2638
|
+
lastInputType: client.remoteInputLastInputType || ''
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
return {
|
|
2643
|
+
protocol: 'mindexec.remote.input.json.v1',
|
|
2644
|
+
path: '/api/remote/input/ws',
|
|
2645
|
+
clientCount: remoteInputClients.size,
|
|
2646
|
+
...remoteInputWsDiagnostics,
|
|
2647
|
+
clients
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
remoteInputWss.on('connection', (ws) => {
|
|
2652
|
+
remoteInputClients.add(ws);
|
|
2653
|
+
ws.remoteInputClientId = `riws-${++remoteInputClientSeq}`;
|
|
2654
|
+
ws.remoteInputConnectedAt = new Date().toISOString();
|
|
2655
|
+
ws.remoteInputReceivedCount = 0;
|
|
2656
|
+
ws.remoteInputQueuedCount = 0;
|
|
2657
|
+
ws.remoteInputFailedCount = 0;
|
|
2658
|
+
remoteInputWsDiagnostics.opened += 1;
|
|
2659
|
+
remoteInputWsDiagnostics.lastOpenAt = ws.remoteInputConnectedAt;
|
|
2660
|
+
|
|
2661
|
+
ws.on('message', data => {
|
|
2662
|
+
const receivedAt = new Date().toISOString();
|
|
2663
|
+
remoteInputWsDiagnostics.messagesReceived += 1;
|
|
2664
|
+
ws.remoteInputReceivedCount = (ws.remoteInputReceivedCount || 0) + 1;
|
|
2665
|
+
|
|
2666
|
+
let payload = null;
|
|
2667
|
+
try {
|
|
2668
|
+
const text = Buffer.isBuffer(data) ? data.toString('utf8') : String(data || '');
|
|
2669
|
+
payload = JSON.parse(text);
|
|
2670
|
+
} catch {
|
|
2671
|
+
remoteInputWsDiagnostics.messagesInvalid += 1;
|
|
2672
|
+
sendRemoteInputClientJson(ws, {
|
|
2673
|
+
type: 'RemoteInputError',
|
|
2674
|
+
timestamp: receivedAt,
|
|
2675
|
+
error: 'invalid-json'
|
|
2676
|
+
});
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
const messageType = String(payload?.type || payload?.Type || '').trim().toLowerCase();
|
|
2681
|
+
if (messageType === 'ping') {
|
|
2682
|
+
sendRemoteInputClientJson(ws, {
|
|
2683
|
+
type: 'RemoteInputPong',
|
|
2684
|
+
timestamp: receivedAt
|
|
2685
|
+
});
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
if (messageType !== 'input' && messageType !== 'remote.input') {
|
|
2690
|
+
remoteInputWsDiagnostics.messagesInvalid += 1;
|
|
2691
|
+
sendRemoteInputClientJson(ws, {
|
|
2692
|
+
type: 'RemoteInputError',
|
|
2693
|
+
timestamp: receivedAt,
|
|
2694
|
+
error: 'unsupported-message'
|
|
2695
|
+
});
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
const normalized = normalizeRemoteInputWsPayload(payload);
|
|
2700
|
+
if (!normalized.deviceId || !normalized.input.type) {
|
|
2701
|
+
remoteInputWsDiagnostics.messagesInvalid += 1;
|
|
2702
|
+
sendRemoteInputClientJson(ws, {
|
|
2703
|
+
type: 'RemoteInputResult',
|
|
2704
|
+
timestamp: receivedAt,
|
|
2705
|
+
ok: false,
|
|
2706
|
+
requestId: normalized.requestId,
|
|
2707
|
+
deviceId: normalized.deviceId,
|
|
2708
|
+
inputType: normalized.input.type,
|
|
2709
|
+
error: !normalized.deviceId ? 'missing-device-id' : 'missing-input-type'
|
|
2710
|
+
});
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
const result = remoteHub.sendInputControl(normalized.deviceId, normalized.input);
|
|
2715
|
+
const ok = result?.ok === true;
|
|
2716
|
+
const inputType = String(normalized.input.type || '').trim();
|
|
2717
|
+
remoteInputWsDiagnostics.lastInputAt = receivedAt;
|
|
2718
|
+
remoteInputWsDiagnostics.lastDeviceId = normalized.deviceId;
|
|
2719
|
+
remoteInputWsDiagnostics.lastInputType = inputType;
|
|
2720
|
+
remoteInputWsDiagnostics.lastCommandId = String(result?.commandId || '');
|
|
2721
|
+
ws.remoteInputLastInputAt = receivedAt;
|
|
2722
|
+
ws.remoteInputLastDeviceId = normalized.deviceId;
|
|
2723
|
+
ws.remoteInputLastInputType = inputType;
|
|
2724
|
+
|
|
2725
|
+
if (ok) {
|
|
2726
|
+
remoteInputWsDiagnostics.inputsQueued += 1;
|
|
2727
|
+
ws.remoteInputQueuedCount = (ws.remoteInputQueuedCount || 0) + 1;
|
|
2728
|
+
if (/^pointermove$/i.test(inputType)) {
|
|
2729
|
+
remoteInputWsDiagnostics.pointerMovesQueued += 1;
|
|
2730
|
+
}
|
|
2731
|
+
if (/^key(up|down)$/i.test(inputType)) {
|
|
2732
|
+
remoteInputWsDiagnostics.keyEventsQueued += 1;
|
|
2733
|
+
}
|
|
2734
|
+
} else {
|
|
2735
|
+
remoteInputWsDiagnostics.inputsFailed += 1;
|
|
2736
|
+
remoteInputWsDiagnostics.lastError = String(result?.error || 'input-failed');
|
|
2737
|
+
ws.remoteInputFailedCount = (ws.remoteInputFailedCount || 0) + 1;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
const shouldAck = normalized.ack === true || !ok || !/^pointermove$/i.test(inputType);
|
|
2741
|
+
if (shouldAck) {
|
|
2742
|
+
sendRemoteInputClientJson(ws, {
|
|
2743
|
+
type: 'RemoteInputResult',
|
|
2744
|
+
timestamp: receivedAt,
|
|
2745
|
+
ok,
|
|
2746
|
+
requestId: normalized.requestId,
|
|
2747
|
+
deviceId: normalized.deviceId,
|
|
2748
|
+
inputType,
|
|
2749
|
+
commandId: result?.commandId || '',
|
|
2750
|
+
error: result?.error || ''
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
});
|
|
2754
|
+
|
|
2755
|
+
ws.on('close', () => {
|
|
2756
|
+
remoteInputClients.delete(ws);
|
|
2757
|
+
remoteInputWsDiagnostics.closed += 1;
|
|
2758
|
+
remoteInputWsDiagnostics.lastCloseAt = new Date().toISOString();
|
|
2759
|
+
});
|
|
2760
|
+
ws.on('error', error => {
|
|
2761
|
+
remoteInputClients.delete(ws);
|
|
2762
|
+
remoteInputWsDiagnostics.errors += 1;
|
|
2763
|
+
remoteInputWsDiagnostics.lastErrorAt = new Date().toISOString();
|
|
2764
|
+
remoteInputWsDiagnostics.lastError = String(error?.message || error || 'input-ws-error');
|
|
2765
|
+
});
|
|
2766
|
+
sendRemoteInputClientJson(ws, {
|
|
2767
|
+
type: 'RemoteInputSocketReady',
|
|
2768
|
+
timestamp: new Date().toISOString(),
|
|
2769
|
+
protocol: 'mindexec.remote.input.json.v1',
|
|
2770
|
+
clientId: ws.remoteInputClientId
|
|
2771
|
+
});
|
|
2772
|
+
});
|
|
2773
|
+
|
|
2555
2774
|
// Helper: Validate path is within workspace
|
|
2556
2775
|
function validatePath(requestedPath) {
|
|
2557
2776
|
const fullPath = resolveWorkspaceBoundPath(requestedPath);
|
|
@@ -3146,6 +3365,15 @@ const REMOTE_REGISTRY_REALTIME_ENABLED = REMOTE_REGISTRY_FOLLOWER_ENABLED
|
|
|
3146
3365
|
const REMOTE_REGISTRY_REALTIME_HEARTBEAT_MS = Math.max(10000, Number(process.env.MINDEXEC_REMOTE_REGISTRY_REALTIME_HEARTBEAT_MS || 25000) || 25000);
|
|
3147
3366
|
const REMOTE_REGISTRY_REALTIME_RECONNECT_MS = Math.max(1000, Number(process.env.MINDEXEC_REMOTE_REGISTRY_REALTIME_RECONNECT_MS || 2500) || 2500);
|
|
3148
3367
|
const REMOTE_REGISTRY_REALTIME_DEBOUNCE_MS = Math.max(100, Number(process.env.MINDEXEC_REMOTE_REGISTRY_REALTIME_DEBOUNCE_MS || 250) || 250);
|
|
3368
|
+
const REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED = REMOTE_REGISTRY_FOLLOWER_ENABLED
|
|
3369
|
+
&& !/^(0|false|no|off)$/i.test(String(process.env.MINDEXEC_REMOTE_HOST_TARGET_AUTO_RENEW || 'true'));
|
|
3370
|
+
const REMOTE_HOST_TARGET_LEASE_MS = Math.max(60000, Number(process.env.MINDEXEC_REMOTE_HOST_TARGET_LEASE_MS || 120000) || 120000);
|
|
3371
|
+
const REMOTE_HOST_TARGET_RENEW_MS = Math.max(
|
|
3372
|
+
5000,
|
|
3373
|
+
Math.min(
|
|
3374
|
+
Math.floor(REMOTE_HOST_TARGET_LEASE_MS / 2),
|
|
3375
|
+
Number(process.env.MINDEXEC_REMOTE_HOST_TARGET_RENEW_MS || 25000) || 25000));
|
|
3376
|
+
const REMOTE_HOST_TARGET_RENEW_LOG_REPEAT_MS = 60000;
|
|
3149
3377
|
let remoteAgentState = createRemoteAgentIdleState();
|
|
3150
3378
|
let remoteAgentSyncReportState = null;
|
|
3151
3379
|
let remoteAgentSyncReportLogKey = '';
|
|
@@ -3168,6 +3396,10 @@ let remoteRegistryRealtimeHeartbeatTimer = null;
|
|
|
3168
3396
|
let remoteRegistryRealtimeReconnectTimer = null;
|
|
3169
3397
|
let remoteRegistryRealtimeReconnectContext = null;
|
|
3170
3398
|
let remoteRegistryRealtimeLastWakeAt = 0;
|
|
3399
|
+
let remoteHostTargetRenewTimer = null;
|
|
3400
|
+
let remoteHostTargetRenewInFlight = false;
|
|
3401
|
+
let remoteHostTargetRenewLogKey = '';
|
|
3402
|
+
let remoteHostTargetRenewLogAt = 0;
|
|
3171
3403
|
let remoteRegistryFollowerState = {
|
|
3172
3404
|
enabled: REMOTE_REGISTRY_FOLLOWER_ENABLED,
|
|
3173
3405
|
status: REMOTE_REGISTRY_FOLLOWER_ENABLED ? 'idle' : 'disabled',
|
|
@@ -3201,6 +3433,20 @@ let remoteRegistryRealtimeState = {
|
|
|
3201
3433
|
changes: 0,
|
|
3202
3434
|
wakeups: 0
|
|
3203
3435
|
};
|
|
3436
|
+
let remoteHostTargetRenewState = {
|
|
3437
|
+
enabled: REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED,
|
|
3438
|
+
status: REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED ? 'idle' : 'disabled',
|
|
3439
|
+
reason: '',
|
|
3440
|
+
nodeId: '',
|
|
3441
|
+
leaseId: '',
|
|
3442
|
+
hostInstanceId: '',
|
|
3443
|
+
endpoint: '',
|
|
3444
|
+
endpointCandidates: [],
|
|
3445
|
+
expiresAt: '',
|
|
3446
|
+
lastAttemptAt: '',
|
|
3447
|
+
lastSuccessAt: '',
|
|
3448
|
+
lastError: ''
|
|
3449
|
+
};
|
|
3204
3450
|
|
|
3205
3451
|
function createRemoteAgentIdleState(overrides = {}) {
|
|
3206
3452
|
return {
|
|
@@ -4499,6 +4745,23 @@ function updateRemoteRegistryFollowerState(patch = {}) {
|
|
|
4499
4745
|
emitBridgeEvent('RemoteRegistryFollowerUpdated', serializeRemoteRegistryFollowerState());
|
|
4500
4746
|
}
|
|
4501
4747
|
|
|
4748
|
+
function serializeRemoteHostTargetRenewState() {
|
|
4749
|
+
return {
|
|
4750
|
+
...remoteHostTargetRenewState,
|
|
4751
|
+
leaseMs: REMOTE_HOST_TARGET_LEASE_MS,
|
|
4752
|
+
renewMs: REMOTE_HOST_TARGET_RENEW_MS
|
|
4753
|
+
};
|
|
4754
|
+
}
|
|
4755
|
+
|
|
4756
|
+
function updateRemoteHostTargetRenewState(patch = {}) {
|
|
4757
|
+
remoteHostTargetRenewState = {
|
|
4758
|
+
...remoteHostTargetRenewState,
|
|
4759
|
+
...patch,
|
|
4760
|
+
enabled: REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED
|
|
4761
|
+
};
|
|
4762
|
+
emitBridgeEvent('RemoteHostTargetRenewUpdated', serializeRemoteHostTargetRenewState());
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4502
4765
|
function serializeRemoteRegistryRealtimeState() {
|
|
4503
4766
|
return {
|
|
4504
4767
|
...remoteRegistryRealtimeState,
|
|
@@ -5094,6 +5357,328 @@ async function fetchRemoteRegistryTarget(config, session) {
|
|
|
5094
5357
|
return normalizeRemoteRegistryTarget(Array.isArray(payload) ? payload[0] : payload);
|
|
5095
5358
|
}
|
|
5096
5359
|
|
|
5360
|
+
async function callRemoteRegistryRpc(config, session, functionName, payload) {
|
|
5361
|
+
const url = new URL(`/rest/v1/rpc/${functionName}`, config.url);
|
|
5362
|
+
const response = await fetch(url.toString(), {
|
|
5363
|
+
method: 'POST',
|
|
5364
|
+
headers: {
|
|
5365
|
+
apikey: config.key,
|
|
5366
|
+
Authorization: `Bearer ${session.accessToken}`,
|
|
5367
|
+
Accept: 'application/json',
|
|
5368
|
+
'Content-Type': 'application/json'
|
|
5369
|
+
},
|
|
5370
|
+
body: JSON.stringify(payload)
|
|
5371
|
+
});
|
|
5372
|
+
|
|
5373
|
+
if (!response.ok) {
|
|
5374
|
+
const text = await response.text().catch(() => '');
|
|
5375
|
+
const error = new Error(`registry-rpc-${functionName}-${response.status}${text ? `:${shortenText(text, 160)}` : ''}`);
|
|
5376
|
+
error.statusCode = response.status;
|
|
5377
|
+
throw error;
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
const rpcPayload = await response.json().catch(() => null);
|
|
5381
|
+
const row = Array.isArray(rpcPayload) ? rpcPayload[0] : rpcPayload;
|
|
5382
|
+
return {
|
|
5383
|
+
ok: row?.ok === true || row?.Ok === true,
|
|
5384
|
+
reason: safeRemoteAgentField(row?.reason || row?.Reason || '', 160)
|
|
5385
|
+
};
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5388
|
+
async function readRemoteRegistryContext() {
|
|
5389
|
+
const config = readSupabaseRuntimeConfig();
|
|
5390
|
+
if (!config.url || !config.key) {
|
|
5391
|
+
return { ok: false, reason: 'supabase-config-missing' };
|
|
5392
|
+
}
|
|
5393
|
+
|
|
5394
|
+
const sessionPayload = await readStableAuthSessionPayload();
|
|
5395
|
+
const session = sessionPayload.found ? parseSupabaseSessionForRegistry(sessionPayload.content) : null;
|
|
5396
|
+
if (!session) {
|
|
5397
|
+
return { ok: false, reason: 'registry-not-authenticated' };
|
|
5398
|
+
}
|
|
5399
|
+
|
|
5400
|
+
if (session.expiresAtMs && session.expiresAtMs <= Date.now()) {
|
|
5401
|
+
return { ok: false, reason: 'session-expired' };
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
return { ok: true, config, session };
|
|
5405
|
+
}
|
|
5406
|
+
|
|
5407
|
+
function getRemoteHostTargetEndpointReason(endpoint) {
|
|
5408
|
+
const normalized = normalizeRemoteManagerEndpoint(endpoint);
|
|
5409
|
+
if (!normalized) {
|
|
5410
|
+
return 'invalid-manager-endpoint';
|
|
5411
|
+
}
|
|
5412
|
+
|
|
5413
|
+
const match = normalized.match(/^(\[[^\]]+\]|[^:\s]+):(\d{1,5})$/);
|
|
5414
|
+
const host = String(match?.[1] || '').replace(/^\[|\]$/g, '').toLowerCase();
|
|
5415
|
+
if (!host || host === 'localhost' || host === '::1' || host === '0:0:0:0:0:0:0:1' || /^127\./.test(host)) {
|
|
5416
|
+
return 'loopback-endpoint';
|
|
5417
|
+
}
|
|
5418
|
+
|
|
5419
|
+
if (host === '0.0.0.0' || host === '::' || host === '*') {
|
|
5420
|
+
return 'wildcard-endpoint';
|
|
5421
|
+
}
|
|
5422
|
+
|
|
5423
|
+
if (/^169\.254\./.test(host)) {
|
|
5424
|
+
return 'link-local-endpoint';
|
|
5425
|
+
}
|
|
5426
|
+
|
|
5427
|
+
return 'ok';
|
|
5428
|
+
}
|
|
5429
|
+
|
|
5430
|
+
function buildRemoteHostTargetRegistryPayload(hub) {
|
|
5431
|
+
const rawEndpointCandidates = normalizeRemoteManagerEndpointList(
|
|
5432
|
+
hub?.hostTargetEndpointCandidates,
|
|
5433
|
+
hub?.agentEndpointCandidates,
|
|
5434
|
+
hub?.hostTargetEndpoint,
|
|
5435
|
+
hub?.agentEndpoint);
|
|
5436
|
+
const endpointCandidates = rawEndpointCandidates
|
|
5437
|
+
.filter(endpoint => getRemoteHostTargetEndpointReason(endpoint) === 'ok')
|
|
5438
|
+
.slice(0, 12);
|
|
5439
|
+
const endpoint = endpointCandidates[0] || '';
|
|
5440
|
+
const pairToken = safeRemoteAgentField(hub?.pairToken, 512);
|
|
5441
|
+
const leaseId = safeRemoteAgentField(hub?.hostTargetLeaseId, 128);
|
|
5442
|
+
const hostInstanceId = safeRemoteAgentField(hub?.hostTargetHostInstanceId || hub?.hostInstanceId, 128);
|
|
5443
|
+
const nodeId = safeRemoteAgentField(hub?.hostTargetNodeId, 128);
|
|
5444
|
+
const activatedAt = safeRemoteAgentField(hub?.hostTargetActivatedAt, 80) || new Date().toISOString();
|
|
5445
|
+
const expiresAt = safeRemoteAgentField(hub?.hostTargetExpiresAt, 80) || new Date(Date.now() + REMOTE_HOST_TARGET_LEASE_MS).toISOString();
|
|
5446
|
+
|
|
5447
|
+
if (!pairToken || !leaseId || !hostInstanceId || !nodeId) {
|
|
5448
|
+
return {
|
|
5449
|
+
ok: false,
|
|
5450
|
+
reason: 'missing-fields',
|
|
5451
|
+
endpoint: '',
|
|
5452
|
+
endpointCandidates: []
|
|
5453
|
+
};
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5456
|
+
if (endpointCandidates.length === 0) {
|
|
5457
|
+
const reason = rawEndpointCandidates.length === 0
|
|
5458
|
+
? safeRemoteAgentField(hub?.agentEndpointRouteReason || 'missing-endpoint-candidates', 160)
|
|
5459
|
+
: getRemoteHostTargetEndpointReason(rawEndpointCandidates[0]);
|
|
5460
|
+
return {
|
|
5461
|
+
ok: false,
|
|
5462
|
+
reason,
|
|
5463
|
+
endpoint: rawEndpointCandidates[0] || '',
|
|
5464
|
+
endpointCandidates: rawEndpointCandidates
|
|
5465
|
+
};
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5468
|
+
return {
|
|
5469
|
+
ok: true,
|
|
5470
|
+
endpoint,
|
|
5471
|
+
endpointCandidates,
|
|
5472
|
+
leaseId,
|
|
5473
|
+
hostInstanceId,
|
|
5474
|
+
nodeId,
|
|
5475
|
+
expiresAt,
|
|
5476
|
+
payload: {
|
|
5477
|
+
p_node_id: nodeId,
|
|
5478
|
+
p_lease_id: leaseId,
|
|
5479
|
+
p_host_instance_id: hostInstanceId,
|
|
5480
|
+
p_endpoint: endpoint,
|
|
5481
|
+
p_pair_token: pairToken,
|
|
5482
|
+
p_manager_package: safeRemoteAgentField(hub?.managerPackage || '@mindexec/cli', 160),
|
|
5483
|
+
p_manager_version: safeRemoteAgentField(hub?.managerVersion || '', 80),
|
|
5484
|
+
p_agent_package: safeRemoteAgentField(hub?.agentPackage || '@mindexec/remote', 160),
|
|
5485
|
+
p_activated_at: activatedAt,
|
|
5486
|
+
p_expires_at: expiresAt,
|
|
5487
|
+
p_endpoint_candidates: endpointCandidates
|
|
5488
|
+
}
|
|
5489
|
+
};
|
|
5490
|
+
}
|
|
5491
|
+
|
|
5492
|
+
function logRemoteHostTargetRenew(status, reason, endpoint = '') {
|
|
5493
|
+
const key = [status, reason, endpoint].join('|');
|
|
5494
|
+
const now = Date.now();
|
|
5495
|
+
if (key === remoteHostTargetRenewLogKey
|
|
5496
|
+
&& now - remoteHostTargetRenewLogAt < REMOTE_HOST_TARGET_RENEW_LOG_REPEAT_MS) {
|
|
5497
|
+
return;
|
|
5498
|
+
}
|
|
5499
|
+
|
|
5500
|
+
remoteHostTargetRenewLogKey = key;
|
|
5501
|
+
remoteHostTargetRenewLogAt = now;
|
|
5502
|
+
logEvent(
|
|
5503
|
+
'remote',
|
|
5504
|
+
`host target renew ${status} ${formatKeyValue('reason', reason || '-')} ${formatKeyValue('endpoint', endpoint || '-')}`,
|
|
5505
|
+
status === 'ok' ? 'remote' : 'warn');
|
|
5506
|
+
}
|
|
5507
|
+
|
|
5508
|
+
function isRemoteHostTargetRenewSoftSkipReason(reason) {
|
|
5509
|
+
return /^(registry-not-authenticated|session-expired|supabase-config-missing|missing-endpoint-candidates|loopback-endpoint|wildcard-endpoint|link-local-endpoint)$/i
|
|
5510
|
+
.test(String(reason || '').trim());
|
|
5511
|
+
}
|
|
5512
|
+
|
|
5513
|
+
async function publishLocalRemoteHostTargetToRegistry(hub, { takeover = false } = {}) {
|
|
5514
|
+
const registryPayload = buildRemoteHostTargetRegistryPayload(hub);
|
|
5515
|
+
if (!registryPayload.ok) {
|
|
5516
|
+
return {
|
|
5517
|
+
ok: false,
|
|
5518
|
+
active: false,
|
|
5519
|
+
reason: registryPayload.reason,
|
|
5520
|
+
endpoint: registryPayload.endpoint,
|
|
5521
|
+
endpointCandidates: registryPayload.endpointCandidates,
|
|
5522
|
+
stale: false
|
|
5523
|
+
};
|
|
5524
|
+
}
|
|
5525
|
+
|
|
5526
|
+
const context = await readRemoteRegistryContext();
|
|
5527
|
+
if (!context.ok) {
|
|
5528
|
+
return {
|
|
5529
|
+
ok: false,
|
|
5530
|
+
active: false,
|
|
5531
|
+
reason: context.reason,
|
|
5532
|
+
endpoint: registryPayload.endpoint,
|
|
5533
|
+
endpointCandidates: registryPayload.endpointCandidates,
|
|
5534
|
+
stale: false
|
|
5535
|
+
};
|
|
5536
|
+
}
|
|
5537
|
+
|
|
5538
|
+
const row = await callRemoteRegistryRpc(
|
|
5539
|
+
context.config,
|
|
5540
|
+
context.session,
|
|
5541
|
+
'set_remote_host_target',
|
|
5542
|
+
{
|
|
5543
|
+
...registryPayload.payload,
|
|
5544
|
+
p_takeover: takeover === true
|
|
5545
|
+
});
|
|
5546
|
+
const reason = row.reason || (row.ok ? 'ok' : 'registry-publish-failed');
|
|
5547
|
+
return {
|
|
5548
|
+
ok: row.ok,
|
|
5549
|
+
active: row.ok,
|
|
5550
|
+
reason,
|
|
5551
|
+
endpoint: registryPayload.endpoint,
|
|
5552
|
+
endpointCandidates: registryPayload.endpointCandidates,
|
|
5553
|
+
leaseId: registryPayload.leaseId,
|
|
5554
|
+
nodeId: registryPayload.nodeId,
|
|
5555
|
+
hostInstanceId: registryPayload.hostInstanceId,
|
|
5556
|
+
expiresAt: registryPayload.expiresAt,
|
|
5557
|
+
stale: reason === 'host-target-taken'
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
|
|
5561
|
+
function scheduleRemoteHostTargetRenew(delayMs = REMOTE_HOST_TARGET_RENEW_MS, reason = 'timer') {
|
|
5562
|
+
if (!REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED || isShuttingDown) {
|
|
5563
|
+
return;
|
|
5564
|
+
}
|
|
5565
|
+
|
|
5566
|
+
if (remoteHostTargetRenewTimer) {
|
|
5567
|
+
clearTimeout(remoteHostTargetRenewTimer);
|
|
5568
|
+
remoteHostTargetRenewTimer = null;
|
|
5569
|
+
}
|
|
5570
|
+
|
|
5571
|
+
remoteHostTargetRenewTimer = setTimeout(() => {
|
|
5572
|
+
remoteHostTargetRenewTimer = null;
|
|
5573
|
+
runRemoteHostTargetRenewOnce(reason).catch(error => {
|
|
5574
|
+
const message = error?.message || String(error || '');
|
|
5575
|
+
updateRemoteHostTargetRenewState({
|
|
5576
|
+
status: 'error',
|
|
5577
|
+
reason: 'renew-error',
|
|
5578
|
+
lastAttemptAt: new Date().toISOString(),
|
|
5579
|
+
lastError: message
|
|
5580
|
+
});
|
|
5581
|
+
logRemoteHostTargetRenew('failed', message);
|
|
5582
|
+
scheduleRemoteHostTargetRenew(REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS, 'error-retry');
|
|
5583
|
+
});
|
|
5584
|
+
}, Math.max(0, Number(delayMs) || 0));
|
|
5585
|
+
remoteHostTargetRenewTimer?.unref?.();
|
|
5586
|
+
}
|
|
5587
|
+
|
|
5588
|
+
function stopRemoteHostTargetRenew(reason = 'stopped') {
|
|
5589
|
+
if (remoteHostTargetRenewTimer) {
|
|
5590
|
+
clearTimeout(remoteHostTargetRenewTimer);
|
|
5591
|
+
remoteHostTargetRenewTimer = null;
|
|
5592
|
+
}
|
|
5593
|
+
updateRemoteHostTargetRenewState({
|
|
5594
|
+
status: REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED ? 'idle' : 'disabled',
|
|
5595
|
+
reason,
|
|
5596
|
+
nodeId: '',
|
|
5597
|
+
leaseId: '',
|
|
5598
|
+
hostInstanceId: '',
|
|
5599
|
+
endpoint: '',
|
|
5600
|
+
endpointCandidates: [],
|
|
5601
|
+
expiresAt: '',
|
|
5602
|
+
lastError: ''
|
|
5603
|
+
});
|
|
5604
|
+
}
|
|
5605
|
+
|
|
5606
|
+
async function runRemoteHostTargetRenewOnce(trigger = 'timer', options = {}) {
|
|
5607
|
+
if (!REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED || remoteHostTargetRenewInFlight) {
|
|
5608
|
+
return serializeRemoteHostTargetRenewState();
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5611
|
+
remoteHostTargetRenewInFlight = true;
|
|
5612
|
+
const attemptedAt = new Date().toISOString();
|
|
5613
|
+
try {
|
|
5614
|
+
const currentHub = remoteHub.getStatus({ includeSecrets: true });
|
|
5615
|
+
if (currentHub?.hostTargetActive !== true || !currentHub.hostTargetNodeId) {
|
|
5616
|
+
stopRemoteHostTargetRenew('no-local-host-target');
|
|
5617
|
+
return serializeRemoteHostTargetRenewState();
|
|
5618
|
+
}
|
|
5619
|
+
|
|
5620
|
+
const renewed = remoteHub.setHostTarget({
|
|
5621
|
+
enabled: true,
|
|
5622
|
+
nodeId: currentHub.hostTargetNodeId,
|
|
5623
|
+
leaseMs: REMOTE_HOST_TARGET_LEASE_MS
|
|
5624
|
+
});
|
|
5625
|
+
if (renewed?.ok !== true || renewed?.active !== true) {
|
|
5626
|
+
updateRemoteHostTargetRenewState({
|
|
5627
|
+
status: 'error',
|
|
5628
|
+
reason: renewed?.error || 'local-host-renew-failed',
|
|
5629
|
+
nodeId: currentHub.hostTargetNodeId,
|
|
5630
|
+
lastAttemptAt: attemptedAt,
|
|
5631
|
+
lastError: renewed?.error || 'local-host-renew-failed'
|
|
5632
|
+
});
|
|
5633
|
+
scheduleRemoteHostTargetRenew(REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS, 'local-renew-failed');
|
|
5634
|
+
return serializeRemoteHostTargetRenewState();
|
|
5635
|
+
}
|
|
5636
|
+
|
|
5637
|
+
const hub = remoteHub.getStatus({ includeSecrets: true });
|
|
5638
|
+
const registry = await publishLocalRemoteHostTargetToRegistry(hub, {
|
|
5639
|
+
takeover: options.takeover === true
|
|
5640
|
+
});
|
|
5641
|
+
const status = registry.ok ? 'active' : registry.stale ? 'superseded' : 'skipped';
|
|
5642
|
+
updateRemoteHostTargetRenewState({
|
|
5643
|
+
status,
|
|
5644
|
+
reason: registry.reason || (registry.ok ? 'ok' : 'registry-skipped'),
|
|
5645
|
+
nodeId: hub.hostTargetNodeId,
|
|
5646
|
+
leaseId: hub.hostTargetLeaseId,
|
|
5647
|
+
hostInstanceId: hub.hostTargetHostInstanceId || hub.hostInstanceId,
|
|
5648
|
+
endpoint: registry.endpoint || hub.hostTargetEndpoint,
|
|
5649
|
+
endpointCandidates: registry.endpointCandidates || hub.hostTargetEndpointCandidates || [],
|
|
5650
|
+
expiresAt: hub.hostTargetExpiresAt,
|
|
5651
|
+
lastAttemptAt: attemptedAt,
|
|
5652
|
+
lastSuccessAt: registry.ok ? new Date().toISOString() : remoteHostTargetRenewState.lastSuccessAt,
|
|
5653
|
+
lastError: registry.ok || /^(registry-not-authenticated|session-expired|supabase-config-missing)$/i.test(registry.reason || '')
|
|
5654
|
+
? ''
|
|
5655
|
+
: (registry.reason || 'registry-publish-failed')
|
|
5656
|
+
});
|
|
5657
|
+
|
|
5658
|
+
if (registry.stale) {
|
|
5659
|
+
remoteHub.setHostTarget({
|
|
5660
|
+
enabled: false,
|
|
5661
|
+
nodeId: hub.hostTargetNodeId
|
|
5662
|
+
});
|
|
5663
|
+
logRemoteHostTargetRenew('superseded', registry.reason, registry.endpoint);
|
|
5664
|
+
stopRemoteHostTargetRenew('host-target-superseded');
|
|
5665
|
+
wakeRemoteRegistryFollower('host-target-superseded').catch(() => {});
|
|
5666
|
+
return serializeRemoteHostTargetRenewState();
|
|
5667
|
+
}
|
|
5668
|
+
|
|
5669
|
+
logRemoteHostTargetRenew(registry.ok ? 'ok' : 'skipped', registry.reason, registry.endpoint);
|
|
5670
|
+
const retryDelay = registry.ok || isRemoteHostTargetRenewSoftSkipReason(registry.reason)
|
|
5671
|
+
? REMOTE_HOST_TARGET_RENEW_MS
|
|
5672
|
+
: REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS;
|
|
5673
|
+
scheduleRemoteHostTargetRenew(
|
|
5674
|
+
retryDelay,
|
|
5675
|
+
registry.ok ? 'renewed' : 'renew-skipped');
|
|
5676
|
+
return serializeRemoteHostTargetRenewState();
|
|
5677
|
+
} finally {
|
|
5678
|
+
remoteHostTargetRenewInFlight = false;
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
|
|
5097
5682
|
async function reportRemoteRegistryFollowerSync(payload = {}) {
|
|
5098
5683
|
const report = createRemoteAgentSyncReport(payload);
|
|
5099
5684
|
rememberRemoteAgentSyncReport(report);
|
|
@@ -9913,14 +10498,17 @@ app.get('/api/status', async (req, res) => {
|
|
|
9913
10498
|
wsToken,
|
|
9914
10499
|
wsPath: '/events',
|
|
9915
10500
|
remoteFrameWsPath: '/api/remote/frames/ws',
|
|
10501
|
+
remoteInputWsPath: '/api/remote/input/ws',
|
|
9916
10502
|
bridgeToken,
|
|
9917
10503
|
bridgeTokenHeader,
|
|
9918
10504
|
bridgeAuthRequired,
|
|
9919
10505
|
remoteHub: remoteHub.getStatus({ includeSecrets: false }),
|
|
9920
10506
|
remoteFrameWs: serializeRemoteFrameWsDiagnostics(),
|
|
10507
|
+
remoteInputWs: serializeRemoteInputWsDiagnostics(),
|
|
9921
10508
|
remoteAgent: serializeRemoteAgentState(),
|
|
9922
10509
|
remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
|
|
9923
10510
|
remoteRegistryRealtime: serializeRemoteRegistryRealtimeState(),
|
|
10511
|
+
remoteHostTargetRenew: serializeRemoteHostTargetRenewState(),
|
|
9924
10512
|
shellJobsPath: '/api/shell/jobs',
|
|
9925
10513
|
companyCore: {
|
|
9926
10514
|
baseUrl: companyCoreBaseUrl,
|
|
@@ -9997,20 +10585,27 @@ app.post('/api/remote/frames', (req, res) => {
|
|
|
9997
10585
|
app.post('/api/remote/host-target', async (req, res) => {
|
|
9998
10586
|
res.setHeader('Cache-Control', 'no-store');
|
|
9999
10587
|
try {
|
|
10588
|
+
const requestedLeaseMs = Number(req.body?.leaseMs);
|
|
10000
10589
|
const result = remoteHub.setHostTarget({
|
|
10001
10590
|
enabled: req.body?.enabled !== false,
|
|
10002
10591
|
nodeId: req.body?.nodeId,
|
|
10003
|
-
leaseMs:
|
|
10592
|
+
leaseMs: Math.max(
|
|
10593
|
+
REMOTE_HOST_TARGET_LEASE_MS,
|
|
10594
|
+
Number.isFinite(requestedLeaseMs) && requestedLeaseMs > 0 ? requestedLeaseMs : 0)
|
|
10004
10595
|
});
|
|
10005
10596
|
if (result?.ok === true && result?.active === true && isLocalRemoteHostTargetActive()) {
|
|
10006
10597
|
if (isRemoteAgentProcessRunning()) {
|
|
10007
10598
|
await stopRemoteAgentConnection('local-host-target-active');
|
|
10008
10599
|
logEvent('remote', 'managed RemoteAgent held stopped while local host target is active', 'remote');
|
|
10009
10600
|
}
|
|
10601
|
+
await runRemoteHostTargetRenewOnce('set-host', { takeover: true });
|
|
10602
|
+
} else if (result?.active !== true) {
|
|
10603
|
+
stopRemoteHostTargetRenew('host-target-inactive');
|
|
10010
10604
|
}
|
|
10011
10605
|
res.json({
|
|
10012
10606
|
...result,
|
|
10013
|
-
agent: serializeRemoteAgentState()
|
|
10607
|
+
agent: serializeRemoteAgentState(),
|
|
10608
|
+
hostTargetRenew: serializeRemoteHostTargetRenewState()
|
|
10014
10609
|
});
|
|
10015
10610
|
} catch (err) {
|
|
10016
10611
|
logError('remote', 'remote host target update failed.', err);
|
|
@@ -10018,17 +10613,25 @@ app.post('/api/remote/host-target', async (req, res) => {
|
|
|
10018
10613
|
ok: false,
|
|
10019
10614
|
active: false,
|
|
10020
10615
|
error: err?.message || String(err),
|
|
10021
|
-
agent: serializeRemoteAgentState()
|
|
10616
|
+
agent: serializeRemoteAgentState(),
|
|
10617
|
+
hostTargetRenew: serializeRemoteHostTargetRenewState()
|
|
10022
10618
|
});
|
|
10023
10619
|
}
|
|
10024
10620
|
});
|
|
10025
10621
|
|
|
10026
10622
|
app.delete('/api/remote/host-target', (req, res) => {
|
|
10027
10623
|
res.setHeader('Cache-Control', 'no-store');
|
|
10028
|
-
|
|
10624
|
+
const result = remoteHub.setHostTarget({
|
|
10029
10625
|
enabled: false,
|
|
10030
10626
|
nodeId: req.body?.nodeId
|
|
10031
|
-
})
|
|
10627
|
+
});
|
|
10628
|
+
if (result?.ok === true) {
|
|
10629
|
+
stopRemoteHostTargetRenew('host-target-cleared');
|
|
10630
|
+
}
|
|
10631
|
+
res.json({
|
|
10632
|
+
...result,
|
|
10633
|
+
hostTargetRenew: serializeRemoteHostTargetRenewState()
|
|
10634
|
+
});
|
|
10032
10635
|
});
|
|
10033
10636
|
|
|
10034
10637
|
app.get('/api/remote/agent/status', (req, res) => {
|
|
@@ -11745,12 +12348,46 @@ async function shutdownBridge(signal) {
|
|
|
11745
12348
|
// Ignore if already closed
|
|
11746
12349
|
}
|
|
11747
12350
|
|
|
12351
|
+
for (const client of remoteFrameClients) {
|
|
12352
|
+
try {
|
|
12353
|
+
client.close();
|
|
12354
|
+
} catch {
|
|
12355
|
+
// Ignore close race
|
|
12356
|
+
}
|
|
12357
|
+
}
|
|
12358
|
+
|
|
12359
|
+
for (const client of remoteInputClients) {
|
|
12360
|
+
try {
|
|
12361
|
+
client.close();
|
|
12362
|
+
} catch {
|
|
12363
|
+
// Ignore close race
|
|
12364
|
+
}
|
|
12365
|
+
}
|
|
12366
|
+
|
|
12367
|
+
try {
|
|
12368
|
+
remoteFrameWss.close();
|
|
12369
|
+
} catch {
|
|
12370
|
+
// Ignore if already closed
|
|
12371
|
+
}
|
|
12372
|
+
|
|
12373
|
+
try {
|
|
12374
|
+
remoteInputWss.close();
|
|
12375
|
+
} catch {
|
|
12376
|
+
// Ignore if already closed
|
|
12377
|
+
}
|
|
12378
|
+
|
|
11748
12379
|
try {
|
|
11749
12380
|
stopRemoteRegistryFollower();
|
|
11750
12381
|
} catch {
|
|
11751
12382
|
// Ignore registry follower shutdown errors
|
|
11752
12383
|
}
|
|
11753
12384
|
|
|
12385
|
+
try {
|
|
12386
|
+
stopRemoteHostTargetRenew('bridge-shutdown');
|
|
12387
|
+
} catch {
|
|
12388
|
+
// Ignore host-target renew shutdown errors
|
|
12389
|
+
}
|
|
12390
|
+
|
|
11754
12391
|
try {
|
|
11755
12392
|
closeRemoteRegistryRealtime('bridge-shutdown');
|
|
11756
12393
|
} catch {
|