@mindexec/cli 0.2.142 → 0.2.143

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.142",
3
+ "version": "0.2.143",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -442,6 +442,7 @@ async function loadCss3DManager() {
442
442
  this.readyState = MiniWebSocket.CONNECTING;
443
443
  this.binaryType = '';
444
444
  this.sent = [];
445
+ this.closeCount = 0;
445
446
  webSocketConnections.push(this);
446
447
  setTimeout(() => {
447
448
  if (this.readyState !== MiniWebSocket.CONNECTING) return;
@@ -458,6 +459,7 @@ async function loadCss3DManager() {
458
459
 
459
460
  close() {
460
461
  if (this.readyState === MiniWebSocket.CLOSED) return;
462
+ this.closeCount += 1;
461
463
  this.readyState = MiniWebSocket.CLOSED;
462
464
  this.onclose?.({ type: 'close' });
463
465
  }
@@ -490,6 +492,8 @@ async function loadCss3DManager() {
490
492
  Set,
491
493
  WeakMap,
492
494
  Blob,
495
+ TextDecoder,
496
+ TextEncoder,
493
497
  requestAnimationFrame: callback => setTimeout(() => callback(Date.now()), 0),
494
498
  cancelAnimationFrame: id => clearTimeout(id),
495
499
  createImageBitmap: async blob => {
@@ -546,6 +550,18 @@ function wait(ms = 0) {
546
550
  return new Promise(resolve => setTimeout(resolve, ms));
547
551
  }
548
552
 
553
+ function encodeRemoteBinaryFrame(metadata, payload = new Uint8Array([0xff, 0xd8, 0xff, 0xd9])) {
554
+ const metaBytes = new TextEncoder().encode(JSON.stringify(metadata));
555
+ const payloadBytes = payload instanceof Uint8Array ? payload : new Uint8Array(payload);
556
+ const buffer = new ArrayBuffer(4 + metaBytes.byteLength + payloadBytes.byteLength);
557
+ const view = new DataView(buffer);
558
+ view.setUint32(0, metaBytes.byteLength);
559
+ const bytes = new Uint8Array(buffer);
560
+ bytes.set(metaBytes, 4);
561
+ bytes.set(payloadBytes, 4 + metaBytes.byteLength);
562
+ return buffer;
563
+ }
564
+
549
565
  function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTaskBatches = [], lastError = '') {
550
566
  const connected = devices.filter(device => device.Connected).length;
551
567
  const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5197';
@@ -935,6 +951,52 @@ try {
935
951
  binaryDrawCalls.some(call => call.op === 'clearRect' || call.op === 'fillRect'),
936
952
  false,
937
953
  'binary Blob frame paint must not clear/fill the canvas before drawing');
954
+
955
+ await wait(20);
956
+ const liveWs = diagnostics.webSocketConnections[0];
957
+ assert.ok(liveWs, 'expected MDM render to open the binary frame WebSocket');
958
+ assert.equal(liveWs.readyState, 1, 'binary frame WebSocket should be open before rerender');
959
+ const wsCountBeforeRerender = diagnostics.webSocketConnections.length;
960
+ const wsCloseCountBeforeRerender = liveWs.closeCount;
961
+ const websocketFrameSeq = 10001;
962
+ liveWs.onmessage?.({
963
+ data: new Blob([encodeRemoteBinaryFrame({
964
+ type: 'remote.frame.binary',
965
+ deviceId: patchTarget.DeviceId,
966
+ kind: 'live',
967
+ frameSeq: websocketFrameSeq,
968
+ streamId: 'render-smoke-ws-live',
969
+ contentHash: `render-smoke-ws-live-${websocketFrameSeq}`,
970
+ mimeType: 'image/jpeg',
971
+ width: 64,
972
+ height: 36,
973
+ capturedAt: new Date().toISOString(),
974
+ receivedAt: new Date().toISOString()
975
+ })])
976
+ });
977
+ await wait(30);
978
+ assert.equal(
979
+ patchPreview.dataset.remoteFleetFrameSeq,
980
+ String(websocketFrameSeq),
981
+ JSON.stringify(diagnostics.runtimeTrace.slice(-12)));
982
+
983
+ manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
984
+ await wait(30);
985
+ assert.equal(
986
+ liveWs.closeCount,
987
+ wsCloseCountBeforeRerender,
988
+ 'MDM rerender must not close the active binary frame WebSocket');
989
+ assert.equal(
990
+ diagnostics.webSocketConnections.length,
991
+ wsCountBeforeRerender,
992
+ 'MDM rerender must reuse the active binary frame WebSocket instead of opening a replacement');
993
+ const rerenderedPatchPreview = bodyView
994
+ .querySelector(`article[data-device-id="${patchTarget.DeviceId}"]`)
995
+ ?.querySelector('[data-remote-fleet-device-preview="tile"]');
996
+ assert.ok(rerenderedPatchPreview);
997
+ assert.equal(rerenderedPatchPreview.dataset.remoteFleetFrameSeq, String(websocketFrameSeq));
998
+ assert.equal(rerenderedPatchPreview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]')?.style.display, 'block');
999
+
938
1000
  const alternateDevice = devices.find(device =>
939
1001
  device.Connected === true
940
1002
  && device.DeviceId !== focusedDevice.DeviceId);
@@ -13459,13 +13459,15 @@
13459
13459
  const REMOTE_FLEET_FRAME_CANVAS_MAX_DPR = 2;
13460
13460
  const REMOTE_FLEET_FRAME_BLOB_CACHE_MS = 10000;
13461
13461
  const REMOTE_FLEET_FRAME_BLOB_CACHE_LIMIT = 96;
13462
- const REMOTE_FLEET_LIVE_FRAME_DECODE_TIMEOUT_MS = 180;
13462
+ const REMOTE_FLEET_LIVE_FRAME_DECODE_TIMEOUT_MS = 700;
13463
13463
  const REMOTE_FLEET_THUMBNAIL_FRAME_DECODE_TIMEOUT_MS = 1200;
13464
13464
  const REMOTE_FLEET_BINARY_FRAME_MAX_DECODE_IN_FLIGHT = 1;
13465
13465
  const REMOTE_FLEET_BINARY_FRAME_WS_RECONNECT_MS = 1500;
13466
13466
  const REMOTE_FLEET_BINARY_FRAME_SUBSCRIBE_MS = 1000;
13467
13467
  const REMOTE_FLEET_BINARY_FRAME_STALE_FALLBACK_MS = 1400;
13468
13468
  const REMOTE_FLEET_BINARY_FRAME_CONNECT_GRACE_MS = 1600;
13469
+ const REMOTE_FLEET_BINARY_FRAME_CACHE_MS = 10000;
13470
+ const REMOTE_FLEET_BINARY_FRAME_CACHE_LIMIT = 240;
13469
13471
  const REMOTE_FLEET_AUTO_LIVE_START_MAX_PARALLEL = 8;
13470
13472
  const REMOTE_FLEET_AUTO_LIVE_START_COOLDOWN_MS = 3000;
13471
13473
  const REMOTE_FLEET_TASK_FOLLOW_INITIAL_MS = 250;
@@ -13639,9 +13641,11 @@
13639
13641
  return nodeState;
13640
13642
  }
13641
13643
 
13642
- function clearRemoteFleetTimers(bodyView) {
13644
+ function clearRemoteFleetTimers(bodyView, options = {}) {
13643
13645
  if (!bodyView) return;
13644
- releaseRemoteFleetBinaryFrameSocket(bodyView);
13646
+ if (options.keepBinaryFrameSocket !== true) {
13647
+ releaseRemoteFleetBinaryFrameSocket(bodyView);
13648
+ }
13645
13649
  if (bodyView._remoteFleetLiveRefreshTimer) {
13646
13650
  clearInterval(bodyView._remoteFleetLiveRefreshTimer);
13647
13651
  bodyView._remoteFleetLiveRefreshTimer = null;
@@ -13822,6 +13826,57 @@
13822
13826
  .slice(0, 240);
13823
13827
  }
13824
13828
 
13829
+ function pruneRemoteFleetBinaryFrameCache(session) {
13830
+ if (!session?.latestBinaryFrames) {
13831
+ return;
13832
+ }
13833
+
13834
+ const now = getRemoteFleetFrameNow();
13835
+ for (const [deviceId, entry] of session.latestBinaryFrames) {
13836
+ if (!entry
13837
+ || !entry.frame
13838
+ || now - Number(entry.seenAt || 0) > REMOTE_FLEET_BINARY_FRAME_CACHE_MS
13839
+ || session.latestBinaryFrames.size > REMOTE_FLEET_BINARY_FRAME_CACHE_LIMIT) {
13840
+ session.latestBinaryFrames.delete(deviceId);
13841
+ }
13842
+ }
13843
+ }
13844
+
13845
+ function rememberRemoteFleetBinaryFrame(session, frame) {
13846
+ const deviceId = String(frame?.deviceId || '').trim();
13847
+ if (!session || !deviceId || !frame?._remoteFleetPayloadBlob) {
13848
+ return;
13849
+ }
13850
+
13851
+ if (!(session.latestBinaryFrames instanceof Map)) {
13852
+ session.latestBinaryFrames = new Map();
13853
+ }
13854
+
13855
+ pruneRemoteFleetBinaryFrameCache(session);
13856
+ session.latestBinaryFrames.set(deviceId, {
13857
+ seenAt: getRemoteFleetFrameNow(),
13858
+ frame: cloneRemoteFleetFramePatch(frame)
13859
+ });
13860
+ }
13861
+
13862
+ function applyRemoteFleetBinaryFrameCacheToBody(bodyView, session) {
13863
+ if (!bodyView || !(session?.latestBinaryFrames instanceof Map) || session.latestBinaryFrames.size === 0) {
13864
+ return 0;
13865
+ }
13866
+
13867
+ pruneRemoteFleetBinaryFrameCache(session);
13868
+ const patches = [];
13869
+ for (const entry of session.latestBinaryFrames.values()) {
13870
+ if (entry?.frame) {
13871
+ patches.push(cloneRemoteFleetFramePatch(entry.frame));
13872
+ }
13873
+ }
13874
+
13875
+ return patches.length > 0
13876
+ ? applyRemoteFleetFramePatches(bodyView, patches)
13877
+ : 0;
13878
+ }
13879
+
13825
13880
  function refreshRemoteFleetBinaryFrameSubscription(session, force = false) {
13826
13881
  if (!session?.ws || session.ws.readyState !== WebSocket.OPEN) {
13827
13882
  return false;
@@ -13877,7 +13932,11 @@
13877
13932
  refreshRemoteFleetBinaryFrameSubscription(session);
13878
13933
  return true;
13879
13934
  }
13935
+ if (session.wsOpening === true) {
13936
+ return true;
13937
+ }
13880
13938
 
13939
+ session.wsOpening = true;
13881
13940
  try {
13882
13941
  const status = await getRemoteFleetBridgeStatusForFrames();
13883
13942
  const wsUrl = buildRemoteFleetBinaryFrameWsUrl(status);
@@ -13891,6 +13950,7 @@
13891
13950
  session.subscriptionKey = '';
13892
13951
  ws.binaryType = 'arraybuffer';
13893
13952
  ws.onopen = () => {
13953
+ session.wsOpening = false;
13894
13954
  session.wsOpenedAt = getRemoteFleetFrameNow();
13895
13955
  window.RuntimeTrace?.emit?.('remote.frame.wsOpen', { nodeId: session.nodeId });
13896
13956
  refreshRemoteFleetBinaryFrameSubscription(session, true);
@@ -13922,6 +13982,7 @@
13922
13982
  let applied = 0;
13923
13983
  const frameAt = getRemoteFleetFrameNow();
13924
13984
  session.lastBinaryFrameAt = frameAt;
13985
+ rememberRemoteFleetBinaryFrame(session, frame);
13925
13986
  session.bodies.forEach(body => {
13926
13987
  if (document.body.contains(body)) {
13927
13988
  const bodyApplied = applyRemoteFleetFramePatches(body, [frame]);
@@ -13975,6 +14036,7 @@
13975
14036
  });
13976
14037
  };
13977
14038
  ws.onclose = () => {
14039
+ session.wsOpening = false;
13978
14040
  if (session.ws === ws) {
13979
14041
  session.ws = null;
13980
14042
  }
@@ -13990,6 +14052,7 @@
13990
14052
  };
13991
14053
  return true;
13992
14054
  } catch (error) {
14055
+ session.wsOpening = false;
13993
14056
  window.RuntimeTrace?.emit?.('remote.frame.wsOpenFailed', {
13994
14057
  nodeId: session.nodeId,
13995
14058
  error: error?.message || String(error || '')
@@ -14014,6 +14077,7 @@
14014
14077
  reconnectTimer: null,
14015
14078
  subscriptionTimer: null,
14016
14079
  subscriptionKey: '',
14080
+ latestBinaryFrames: new Map(),
14017
14081
  controlSessions: new Set(),
14018
14082
  getDeviceIds: null
14019
14083
  };
@@ -14031,6 +14095,7 @@
14031
14095
  mode: String(options.mode || 'remote-fast')
14032
14096
  };
14033
14097
  bodyView._remoteFleetBinaryFrameSession = session;
14098
+ applyRemoteFleetBinaryFrameCacheToBody(bodyView, session);
14034
14099
  openRemoteFleetBinaryFrameSocket(session).catch(() => undefined);
14035
14100
  return true;
14036
14101
  }
@@ -16043,7 +16108,7 @@
16043
16108
 
16044
16109
  function renderRemoteFleetDevice(bodyView, nodeModel) {
16045
16110
  if (!bodyView) return;
16046
- clearRemoteFleetTimers(bodyView);
16111
+ clearRemoteFleetTimers(bodyView, { keepBinaryFrameSocket: true });
16047
16112
 
16048
16113
  const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
16049
16114
  const device = parseRemoteFleetPinnedDevice(nodeModel);
@@ -16541,7 +16606,7 @@
16541
16606
 
16542
16607
  function renderRemoteFleetMonitor(bodyView, nodeModel) {
16543
16608
  if (!bodyView) return;
16544
- clearRemoteFleetTimers(bodyView);
16609
+ clearRemoteFleetTimers(bodyView, { keepBinaryFrameSocket: true });
16545
16610
 
16546
16611
  const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
16547
16612
  const searchState = bodyView.dataset.remoteFleetSearch || '';
@@ -18038,11 +18103,14 @@
18038
18103
  clearRemoteFleetTimers(bodyView);
18039
18104
  }
18040
18105
  }, REMOTE_FLEET_LIVE_REFRESH_MS);
18041
- } else if (autoMonitorState && hasMonitorTargets) {
18042
- startRemoteFleetFrameLoop(bodyView, REMOTE_FLEET_THUMBNAIL_FRAME_REFRESH_MS, () => refreshRemoteFleetVisibleFrames(true));
18043
- startRemoteFleetMonitorRefreshTimer(REMOTE_FLEET_MONITOR_REFRESH_MS, 'visible-devices');
18044
- } else if (autoMonitorState) {
18045
- startRemoteFleetMonitorRefreshTimer(REMOTE_FLEET_EMPTY_MONITOR_REFRESH_MS, 'empty-discovery');
18106
+ } else {
18107
+ releaseRemoteFleetBinaryFrameSocket(bodyView);
18108
+ if (autoMonitorState && hasMonitorTargets) {
18109
+ startRemoteFleetFrameLoop(bodyView, REMOTE_FLEET_THUMBNAIL_FRAME_REFRESH_MS, () => refreshRemoteFleetVisibleFrames(true));
18110
+ startRemoteFleetMonitorRefreshTimer(REMOTE_FLEET_MONITOR_REFRESH_MS, 'visible-devices');
18111
+ } else if (autoMonitorState) {
18112
+ startRemoteFleetMonitorRefreshTimer(REMOTE_FLEET_EMPTY_MONITOR_REFRESH_MS, 'empty-discovery');
18113
+ }
18046
18114
  }
18047
18115
  }
18048
18116
 
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "7WvF4J3m",
2
+ "version": "Q3LEcx0Y",
3
3
  "assets": [
4
4
  {
5
5
  "hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
@@ -86,7 +86,7 @@
86
86
  "url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
87
87
  },
88
88
  {
89
- "hash": "sha256-8Ndn0qAzhmRH7/OJtB0ve5LG2oN5e+PaAE7c0MyEzHM=",
89
+ "hash": "sha256-ct4yGB6E/ViQSpNNn3fHtt3v+usCW9LYQEbpOmzuRdk=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: 7WvF4J3m */
1
+ /* Manifest version: Q3LEcx0Y */
2
2
  // Hosted deployments should prefer the network over stale offline caches.
3
3
  // This service worker immediately clears old Blazor offline caches and unregisters itself.
4
4