@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.139",
3
+ "version": "0.2.141",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -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
- assert.ok(liveStartCalls.length > 1, `expected multiple visible live starts, got ${liveStartCalls.length}`);
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 = 120;
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;