@mindexec/cli 0.2.139 → 0.2.141
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
CHANGED
|
@@ -124,6 +124,30 @@ function spawnBridge({ bridgePort, remoteHubPort, workspacePath, label }) {
|
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
async function startHangingRemoteManager(port) {
|
|
128
|
+
const sockets = new Set();
|
|
129
|
+
const server = net.createServer(socket => {
|
|
130
|
+
sockets.add(socket);
|
|
131
|
+
socket.on('close', () => sockets.delete(socket));
|
|
132
|
+
socket.on('error', () => sockets.delete(socket));
|
|
133
|
+
});
|
|
134
|
+
server.unref();
|
|
135
|
+
await new Promise((resolve, reject) => {
|
|
136
|
+
server.once('error', reject);
|
|
137
|
+
server.listen(port, '127.0.0.1', resolve);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
endpoint: `127.0.0.1:${port}`,
|
|
142
|
+
async stop() {
|
|
143
|
+
for (const socket of sockets) {
|
|
144
|
+
socket.destroy();
|
|
145
|
+
}
|
|
146
|
+
await new Promise(resolve => server.close(resolve));
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
127
151
|
async function waitForBridge(bridge) {
|
|
128
152
|
return await waitFor(async () => {
|
|
129
153
|
const result = await fetchJson(`${bridge.baseUrl}/api/status`);
|
|
@@ -159,11 +183,13 @@ async function main() {
|
|
|
159
183
|
const clientRemotePort = await findFreePort();
|
|
160
184
|
const stalePort = await findFreePort();
|
|
161
185
|
const managerEndpoint = `127.0.0.1:${hostRemotePort}`;
|
|
186
|
+
let hangingManager = null;
|
|
162
187
|
const staleEndpoint = `127.0.0.1:${stalePort}`;
|
|
163
188
|
|
|
164
189
|
let hostBridge = null;
|
|
165
190
|
let clientBridge = null;
|
|
166
191
|
try {
|
|
192
|
+
hangingManager = await startHangingRemoteManager(stalePort);
|
|
167
193
|
hostBridge = spawnBridge({
|
|
168
194
|
bridgePort: hostBridgePort,
|
|
169
195
|
remoteHubPort: hostRemotePort,
|
|
@@ -180,11 +206,16 @@ async function main() {
|
|
|
180
206
|
});
|
|
181
207
|
await waitForBridge(clientBridge);
|
|
182
208
|
|
|
209
|
+
const firstConnectStartedAt = Date.now();
|
|
183
210
|
const firstAgent = await connectManagedAgent(clientBridge, managerEndpoint, staleEndpoint);
|
|
211
|
+
const firstConnectMs = Date.now() - firstConnectStartedAt;
|
|
184
212
|
assert.equal(firstAgent.running, true, JSON.stringify(firstAgent));
|
|
185
213
|
assert.equal(firstAgent.ready, true, JSON.stringify(firstAgent));
|
|
186
214
|
assert.equal(firstAgent.usingNpx, false, JSON.stringify(firstAgent));
|
|
187
215
|
assert.match(String(firstAgent.launcher || ''), /mindexec-remote-fast/i);
|
|
216
|
+
assert.ok(
|
|
217
|
+
firstConnectMs < 4500,
|
|
218
|
+
`managed RemoteAgent must race slow endpoint candidates instead of waiting sequentially, got ${firstConnectMs}ms`);
|
|
188
219
|
|
|
189
220
|
const connectedDevice = await waitFor(async () => {
|
|
190
221
|
const result = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices`);
|
|
@@ -218,6 +249,9 @@ async function main() {
|
|
|
218
249
|
|
|
219
250
|
console.log('RemoteAgent managed supervisor smoke OK');
|
|
220
251
|
} finally {
|
|
252
|
+
if (hangingManager) {
|
|
253
|
+
await hangingManager.stop();
|
|
254
|
+
}
|
|
221
255
|
if (clientBridge) {
|
|
222
256
|
await clientBridge.stop();
|
|
223
257
|
}
|
|
@@ -419,6 +419,10 @@ async function loadCss3DManager() {
|
|
|
419
419
|
const objectUrlCalls = [];
|
|
420
420
|
const revokedObjectUrls = [];
|
|
421
421
|
const imageBitmapCalls = [];
|
|
422
|
+
const fetchCalls = [];
|
|
423
|
+
const runtimeTrace = [];
|
|
424
|
+
const webSocketConnections = [];
|
|
425
|
+
const webSocketSends = [];
|
|
422
426
|
class SmokeURL extends URL {}
|
|
423
427
|
SmokeURL.createObjectURL = blob => {
|
|
424
428
|
objectUrlCalls.push(blob);
|
|
@@ -427,9 +431,43 @@ async function loadCss3DManager() {
|
|
|
427
431
|
SmokeURL.revokeObjectURL = value => {
|
|
428
432
|
revokedObjectUrls.push(String(value || ''));
|
|
429
433
|
};
|
|
434
|
+
class MiniWebSocket {
|
|
435
|
+
static CONNECTING = 0;
|
|
436
|
+
static OPEN = 1;
|
|
437
|
+
static CLOSING = 2;
|
|
438
|
+
static CLOSED = 3;
|
|
439
|
+
|
|
440
|
+
constructor(url) {
|
|
441
|
+
this.url = String(url || '');
|
|
442
|
+
this.readyState = MiniWebSocket.CONNECTING;
|
|
443
|
+
this.binaryType = '';
|
|
444
|
+
this.sent = [];
|
|
445
|
+
webSocketConnections.push(this);
|
|
446
|
+
setTimeout(() => {
|
|
447
|
+
if (this.readyState !== MiniWebSocket.CONNECTING) return;
|
|
448
|
+
this.readyState = MiniWebSocket.OPEN;
|
|
449
|
+
this.onopen?.({ type: 'open' });
|
|
450
|
+
}, 0);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
send(payload) {
|
|
454
|
+
const text = typeof payload === 'string' ? payload : String(payload || '');
|
|
455
|
+
this.sent.push(text);
|
|
456
|
+
webSocketSends.push({ url: this.url, payload: text });
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
close() {
|
|
460
|
+
if (this.readyState === MiniWebSocket.CLOSED) return;
|
|
461
|
+
this.readyState = MiniWebSocket.CLOSED;
|
|
462
|
+
this.onclose?.({ type: 'close' });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
430
465
|
const context = {
|
|
431
466
|
console,
|
|
432
467
|
document,
|
|
468
|
+
location: {
|
|
469
|
+
origin: 'http://localhost:5147'
|
|
470
|
+
},
|
|
433
471
|
navigator: {
|
|
434
472
|
clipboard: {
|
|
435
473
|
writeText: async () => {}
|
|
@@ -466,6 +504,23 @@ async function loadCss3DManager() {
|
|
|
466
504
|
},
|
|
467
505
|
THREE: createThreeStub(),
|
|
468
506
|
URL: SmokeURL,
|
|
507
|
+
WebSocket: MiniWebSocket,
|
|
508
|
+
fetch: async (url, options = {}) => {
|
|
509
|
+
fetchCalls.push({ url: String(url || ''), options });
|
|
510
|
+
return {
|
|
511
|
+
ok: true,
|
|
512
|
+
status: 200,
|
|
513
|
+
json: async () => ({
|
|
514
|
+
bridgeToken: 'render-smoke-bridge-token',
|
|
515
|
+
remoteFrameWsPath: '/api/remote/frames/ws'
|
|
516
|
+
})
|
|
517
|
+
};
|
|
518
|
+
},
|
|
519
|
+
RuntimeTrace: {
|
|
520
|
+
emit(type, data = {}) {
|
|
521
|
+
runtimeTrace.push({ type, ...data });
|
|
522
|
+
}
|
|
523
|
+
},
|
|
469
524
|
Promise
|
|
470
525
|
};
|
|
471
526
|
context.window = context;
|
|
@@ -478,7 +533,11 @@ async function loadCss3DManager() {
|
|
|
478
533
|
diagnostics: {
|
|
479
534
|
objectUrlCalls,
|
|
480
535
|
revokedObjectUrls,
|
|
481
|
-
imageBitmapCalls
|
|
536
|
+
imageBitmapCalls,
|
|
537
|
+
fetchCalls,
|
|
538
|
+
runtimeTrace,
|
|
539
|
+
webSocketConnections,
|
|
540
|
+
webSocketSends
|
|
482
541
|
}
|
|
483
542
|
};
|
|
484
543
|
}
|
|
@@ -530,6 +589,7 @@ function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTask
|
|
|
530
589
|
function createRemoteFleetTemplateShell(document, focusDeviceId = '') {
|
|
531
590
|
const nodeShell = document.createElement('div');
|
|
532
591
|
nodeShell.setAttribute('class', 'map-node-template-card map-node-remote-fleet');
|
|
592
|
+
nodeShell.dataset.nodeId = 'remote-fleet-render-smoke';
|
|
533
593
|
const shell = document.createElement('div');
|
|
534
594
|
shell.setAttribute('class', 'template-card__shell template-card__shell--remote-fleet');
|
|
535
595
|
const header = document.createElement('div');
|
|
@@ -542,7 +602,8 @@ function createRemoteFleetTemplateShell(document, focusDeviceId = '') {
|
|
|
542
602
|
header.appendChild(icon);
|
|
543
603
|
header.appendChild(titleWrap);
|
|
544
604
|
const bodyView = document.createElement('div');
|
|
545
|
-
bodyView.setAttribute('class', 'template-card__remote-fleet-body');
|
|
605
|
+
bodyView.setAttribute('class', 'template-card__remote-fleet-body map-node-remote-fleet__body');
|
|
606
|
+
bodyView.dataset.nodeId = 'remote-fleet-render-smoke';
|
|
546
607
|
bodyView.dataset.remoteFleetAutoMonitor = 'false';
|
|
547
608
|
if (focusDeviceId) {
|
|
548
609
|
bodyView.dataset.remoteFleetFocusDeviceId = focusDeviceId;
|
|
@@ -939,9 +1000,31 @@ try {
|
|
|
939
1000
|
const resultPanel = bodyView.querySelector('[data-remote-fleet-task-results="true"]');
|
|
940
1001
|
assert.equal(resultPanel, null);
|
|
941
1002
|
assert.ok(devices.some(device => /^synthetic-response-/.test(device.LatestTaskResultResponseId)));
|
|
942
|
-
await wait();
|
|
1003
|
+
await wait(20);
|
|
943
1004
|
const liveStartCalls = dotNetCalls.filter(call => call.methodName === 'StartRemoteFleetLiveStreamFromJs');
|
|
944
|
-
|
|
1005
|
+
const subscribeMessages = diagnostics.webSocketSends
|
|
1006
|
+
.map(send => {
|
|
1007
|
+
try {
|
|
1008
|
+
return JSON.parse(send.payload);
|
|
1009
|
+
} catch {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
})
|
|
1013
|
+
.filter(Boolean);
|
|
1014
|
+
const autoLiveSubscribe = subscribeMessages.find(message =>
|
|
1015
|
+
message.type === 'subscribe'
|
|
1016
|
+
&& message.nodeId === 'remote-fleet-render-smoke'
|
|
1017
|
+
&& message.autoStartLive === true);
|
|
1018
|
+
assert.ok(diagnostics.fetchCalls.some(call => call.url.includes('/api/status?remoteFrames=ws')));
|
|
1019
|
+
assert.ok(diagnostics.webSocketConnections.length >= 1, 'expected MDM render to open binary frame WebSocket');
|
|
1020
|
+
assert.ok(autoLiveSubscribe, 'expected MDM render to subscribe with autoStartLive over WebSocket');
|
|
1021
|
+
assert.equal(autoLiveSubscribe.fps, 12);
|
|
1022
|
+
assert.equal(autoLiveSubscribe.maxWidth, 960);
|
|
1023
|
+
assert.equal(autoLiveSubscribe.maxHeight, 540);
|
|
1024
|
+
assert.equal(autoLiveSubscribe.quality, 60);
|
|
1025
|
+
assert.ok(autoLiveSubscribe.deviceIds.length > 1, 'expected visible connected devices in WS subscription');
|
|
1026
|
+
assert.ok(diagnostics.runtimeTrace.some(event => event.type === 'remote.live.wsAutoStartRequested'));
|
|
1027
|
+
assert.equal(liveStartCalls.length, 0, 'WebSocket live path must not call DotNet live-start fallback');
|
|
945
1028
|
await wait(320);
|
|
946
1029
|
assert.ok(dotNetCalls.some(call => call.methodName === 'RefreshRemoteFleetMonitorNodeFromJs'));
|
|
947
1030
|
assert.equal(bodyView.dataset.remoteFleetTaskFollowKey, 'render-smoke-batch');
|
package/server.js
CHANGED
|
@@ -3132,7 +3132,7 @@ const REMOTE_AGENT_EARLY_EXIT_MS = 1200;
|
|
|
3132
3132
|
const REMOTE_AGENT_READY_TIMEOUT_MS = 5000;
|
|
3133
3133
|
const REMOTE_AGENT_DEFAULT_ENGINE = 'auto';
|
|
3134
3134
|
const REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS = 30000;
|
|
3135
|
-
const REMOTE_AGENT_RACE_START_STAGGER_MS =
|
|
3135
|
+
const REMOTE_AGENT_RACE_START_STAGGER_MS = Math.max(0, Number(process.env.MINDEXEC_REMOTE_AGENT_RACE_STAGGER_MS ?? 0) || 0);
|
|
3136
3136
|
const REMOTE_AGENT_MAX_PARALLEL_CANDIDATES = 6;
|
|
3137
3137
|
const REMOTE_AGENT_RECENT_MANAGER_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
3138
3138
|
const REMOTE_AGENT_RECENT_MANAGER_CACHE_LIMIT = 64;
|