@mindexec/cli 0.2.446 → 0.2.448

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.
Files changed (34) hide show
  1. package/README.md +36 -6
  2. package/electron/main.cjs +347 -0
  3. package/electron/source-smoke.mjs +39 -0
  4. package/electron/windows-package-smoke.mjs +147 -0
  5. package/package.json +74 -14
  6. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  7. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  8. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  9. package/remote-hub.js +1 -1
  10. package/scripts/remote-fleet-render-smoke.mjs +74 -36
  11. package/scripts/remote-hub-smoke.mjs +7 -0
  12. package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1 -3
  13. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +199 -100
  14. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-hotpath.js +902 -0
  15. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +56 -385
  16. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +90 -134
  17. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +167 -24
  18. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +92 -50
  19. package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +35 -12
  20. package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +35 -2
  21. package/wwwroot/_framework/{MindExecution.Core.djyzj5mxyk.dll → MindExecution.Core.mswl4wlkm9.dll} +0 -0
  22. package/wwwroot/_framework/{MindExecution.Kernel.6e5cj9aijz.dll → MindExecution.Kernel.zupeldfptg.dll} +0 -0
  23. package/wwwroot/_framework/{MindExecution.Plugins.Admin.siulh9tfrf.dll → MindExecution.Plugins.Admin.hq0vyqnqtn.dll} +0 -0
  24. package/wwwroot/_framework/{MindExecution.Plugins.Business.8cjtrc7qd3.dll → MindExecution.Plugins.Business.pucw3o7rno.dll} +0 -0
  25. package/wwwroot/_framework/{MindExecution.Plugins.Concept.rpp78mc75u.dll → MindExecution.Plugins.Concept.557tzzkrrx.dll} +0 -0
  26. package/wwwroot/_framework/{MindExecution.Plugins.Directory.wfv0ff9v4u.dll → MindExecution.Plugins.Directory.zh1ddg1fn6.dll} +0 -0
  27. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.jdppnbd6g4.dll → MindExecution.Plugins.PlanMaster.0z9pzynpu9.dll} +0 -0
  28. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.a43f2q3pkw.dll → MindExecution.Plugins.YouTube.y8g77r54fa.dll} +0 -0
  29. package/wwwroot/_framework/{MindExecution.Shared.gwtyj0e9lw.dll → MindExecution.Shared.1ymsdbwid2.dll} +0 -0
  30. package/wwwroot/_framework/{MindExecution.Web.j3zaapcxtt.dll → MindExecution.Web.bb1txupuv0.dll} +0 -0
  31. package/wwwroot/_framework/blazor.boot.json +21 -21
  32. package/wwwroot/index.html +2 -1
  33. package/wwwroot/service-worker-assets.js +35 -31
  34. package/wwwroot/service-worker.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.446",
3
+ "version": "0.2.448",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -16,16 +16,21 @@
16
16
  "codex-runtime.js",
17
17
  "codex-model-catalog.js",
18
18
  "port-guard.cjs",
19
- "remote-fast/",
20
- "wwwroot/",
21
- "scripts/",
19
+ "remote-fast/",
20
+ "wwwroot/",
21
+ "electron/",
22
+ "scripts/",
22
23
  "tree-sitter-grammars/",
23
24
  "README.md"
24
25
  ],
25
- "scripts": {
26
- "start": "node launch-bridge.cjs",
27
- "dev": "node launch-bridge.cjs --watch",
28
- "test:syntax": "node --check server.js && node --check remote-hub.js && node --check lzo1x.js && node --check mode4-atlas.js && node --check mode4-atlas-worker.js && node --check codex-runtime.js && node --check codex-model-catalog.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/npm-fresh-install-smoke.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/codex-model-catalog-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
26
+ "scripts": {
27
+ "start": "node launch-bridge.cjs",
28
+ "dev": "node launch-bridge.cjs --watch",
29
+ "desktop:start": "electron electron/main.cjs",
30
+ "desktop:build:win": "electron-builder --win --x64 --publish never",
31
+ "test:desktop": "node electron/source-smoke.mjs",
32
+ "test:desktop:win": "node electron/windows-package-smoke.mjs",
33
+ "test:syntax": "node --check server.js && node --check remote-hub.js && node --check lzo1x.js && node --check mode4-atlas.js && node --check mode4-atlas-worker.js && node --check codex-runtime.js && node --check codex-model-catalog.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check electron/main.cjs && node --check electron/source-smoke.mjs && node --check electron/windows-package-smoke.mjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/npm-fresh-install-smoke.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/codex-model-catalog-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
29
34
  "test:codex-model-catalog": "node scripts/codex-model-catalog-smoke.mjs",
30
35
  "test:codex-model-catalog:live": "node scripts/codex-model-catalog-smoke.mjs --live",
31
36
  "test:auth": "node scripts/auth-session-smoke.mjs",
@@ -53,7 +58,7 @@
53
58
  "local",
54
59
  "ai"
55
60
  ],
56
- "author": "",
61
+ "author": "lovecrdm77",
57
62
  "license": "MIT",
58
63
  "publishConfig": {
59
64
  "access": "public"
@@ -63,7 +68,7 @@
63
68
  },
64
69
  "dependencies": {
65
70
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
66
- "@mindexec/remote": "^0.1.17",
71
+ "@mindexec/remote": "^0.1.18",
67
72
  "@openai/codex-sdk": "^0.144.1",
68
73
  "chokidar": "^3.6.0",
69
74
  "cors": "^2.8.5",
@@ -82,7 +87,62 @@
82
87
  "mindexec": "launch-bridge.cjs",
83
88
  "mind-bridge": "launch-bridge.cjs"
84
89
  },
85
- "devDependencies": {
86
- "tree-sitter-wasms": "^0.1.13"
87
- }
88
- }
90
+ "devDependencies": {
91
+ "electron": "43.2.0",
92
+ "electron-builder": "26.15.3",
93
+ "tree-sitter-wasms": "^0.1.13"
94
+ },
95
+ "build": {
96
+ "appId": "com.mindexec.desktop",
97
+ "productName": "MindExec",
98
+ "copyright": "Copyright © 2026 lovecrdm77",
99
+ "asar": false,
100
+ "extraMetadata": {
101
+ "main": "electron/main.cjs"
102
+ },
103
+ "directories": {
104
+ "output": "../artifacts/electron"
105
+ },
106
+ "files": [
107
+ "*.js",
108
+ "*.cjs",
109
+ "*.json",
110
+ "electron/main.cjs",
111
+ "remote-fast/**/*",
112
+ "scripts/**/*",
113
+ "tree-sitter-grammars/**/*",
114
+ "wwwroot/**/*",
115
+ "node_modules/**/*"
116
+ ],
117
+ "win": {
118
+ "icon": "../MindExecution.Web/wwwroot/icon-512.png",
119
+ "executableName": "MindExec",
120
+ "target": [
121
+ {
122
+ "target": "nsis",
123
+ "arch": [
124
+ "x64"
125
+ ]
126
+ },
127
+ {
128
+ "target": "portable",
129
+ "arch": [
130
+ "x64"
131
+ ]
132
+ }
133
+ ]
134
+ },
135
+ "nsis": {
136
+ "artifactName": "MindExec-Setup-${version}-${arch}.${ext}",
137
+ "oneClick": false,
138
+ "perMachine": false,
139
+ "allowToChangeInstallationDirectory": true,
140
+ "createDesktopShortcut": true,
141
+ "createStartMenuShortcut": true,
142
+ "shortcutName": "MindExec"
143
+ },
144
+ "portable": {
145
+ "artifactName": "MindExec-Portable-${version}-${arch}.${ext}"
146
+ }
147
+ }
148
+ }
package/remote-hub.js CHANGED
@@ -2,7 +2,7 @@ import net from 'net';
2
2
  import os from 'os';
3
3
  import crypto from 'crypto';
4
4
 
5
- const DEFAULT_REMOTE_HUB_PORT = 5197;
5
+ const DEFAULT_REMOTE_HUB_PORT = 5198;
6
6
  const DEFAULT_REMOTE_HUB_HOST = '0.0.0.0';
7
7
  const DEFAULT_HEARTBEAT_MS = 5000;
8
8
  const DEFAULT_AGENT_TASK_TIMEOUT_MS = 120000;
@@ -435,7 +435,9 @@ function projectDevice(device) {
435
435
 
436
436
  async function loadCss3DManager() {
437
437
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
438
+ const hotPathModulePath = path.resolve(__dirname, '../../MindExecution.Shared/wwwroot/js/mind-map-css3d-hotpath.js');
438
439
  const managerPath = path.resolve(__dirname, '../../MindExecution.Shared/wwwroot/js/mind-map-css3d-manager.js');
440
+ const hotPathSource = await fs.readFile(hotPathModulePath, 'utf8');
439
441
  const source = await fs.readFile(managerPath, 'utf8');
440
442
  const document = new MiniDocument();
441
443
  const objectUrlCalls = [];
@@ -558,11 +560,18 @@ async function loadCss3DManager() {
558
560
  textOverlayInvalidations.push({ module, nodeId, reason, fallback: true });
559
561
  }
560
562
  },
563
+ MindExecRemoteCodecs: {
564
+ async decodeH264Frame(canvas, frame, onFrame) {
565
+ onFrame?.(canvas, frame);
566
+ return true;
567
+ }
568
+ },
561
569
  Promise
562
570
  };
563
571
  context.window = context;
564
572
  context.self = context;
565
573
 
574
+ vm.runInNewContext(hotPathSource, context, { filename: hotPathModulePath });
566
575
  vm.runInNewContext(source, context, { filename: managerPath });
567
576
  return {
568
577
  manager: context.window.MindMapCss3DManager,
@@ -584,6 +593,15 @@ function wait(ms = 0) {
584
593
  return new Promise(resolve => setTimeout(resolve, ms));
585
594
  }
586
595
 
596
+ async function waitFor(predicate, timeoutMs = 500, intervalMs = 10) {
597
+ const deadline = Date.now() + Math.max(1, Number(timeoutMs || 500));
598
+ while (Date.now() < deadline) {
599
+ if (predicate()) return true;
600
+ await wait(intervalMs);
601
+ }
602
+ return predicate();
603
+ }
604
+
587
605
  function encodeRemoteBinaryFrame(metadata, payload = new Uint8Array([0xff, 0xd8, 0xff, 0xd9])) {
588
606
  const metaBytes = new TextEncoder().encode(JSON.stringify(metadata));
589
607
  const payloadBytes = payload instanceof Uint8Array ? payload : new Uint8Array(payload);
@@ -598,7 +616,7 @@ function encodeRemoteBinaryFrame(metadata, payload = new Uint8Array([0xff, 0xd8,
598
616
 
599
617
  function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTaskBatches = [], lastError = '', nodeId = 'remote-fleet-render-smoke') {
600
618
  const connected = devices.filter(device => device.Connected).length;
601
- const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5197';
619
+ const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5198';
602
620
  const pairToken = hubStatus.pairToken || 'render-smoke-token';
603
621
  const hostTargetState = hubStatus.hostTargetActive
604
622
  ? (hubStatus.hostTargetNodeId === nodeId ? 'hosting' : 'other-monitor')
@@ -665,7 +683,7 @@ function createRemoteFleetTemplateShell(document, focusDeviceId = '', nodeId = '
665
683
  }
666
684
 
667
685
  function buildDeviceNode(device, hubStatus) {
668
- const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5197';
686
+ const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5198';
669
687
  return {
670
688
  id: 'remote-device-render-smoke',
671
689
  contentType: 'memo',
@@ -1060,6 +1078,13 @@ try {
1060
1078
  binaryDrawCalls.some(call => call.op === 'clearRect' || call.op === 'fillRect'),
1061
1079
  false,
1062
1080
  'binary Blob frame paint must not clear/fill the canvas before drawing');
1081
+ assert.ok(
1082
+ Number(moduleRef._forceUpdateFrames || 0) >= 2,
1083
+ 'binary live frame commits must wake the passive MDM CSS3D frame loop');
1084
+ assert.equal(moduleRef._lastAnimationWakeReason, 'remote-live-css3d-frame');
1085
+ assert.ok(
1086
+ diagnostics.runtimeTrace.some(event => event.type === 'remote.frame.css3dWake'),
1087
+ 'binary live frame commits must trace the MDM CSS3D wake path');
1063
1088
  const binaryCanvasBeforeRerender = binaryCanvas;
1064
1089
 
1065
1090
  const liveOverlayCard = document.createElement('div');
@@ -1073,12 +1098,28 @@ try {
1073
1098
  moduleRef._lastAnimationWakeReason = '';
1074
1099
 
1075
1100
  await wait(20);
1076
- const liveWs = diagnostics.webSocketConnections[0];
1077
- assert.ok(liveWs, 'expected MDM render to open the binary frame WebSocket');
1078
- assert.equal(liveWs.readyState, 1, 'binary frame WebSocket should be open before rerender');
1101
+ const liveWs = diagnostics.webSocketConnections.find(connection =>
1102
+ String(connection?.url || '').includes('/api/remote/atlas/ws'));
1103
+ assert.ok(
1104
+ liveWs,
1105
+ `expected MDM render to open the Mode 4 Atlas WebSocket: ${diagnostics.webSocketConnections.map(connection => connection.url).join(', ')}`
1106
+ );
1107
+ assert.equal(liveWs.readyState, 1, 'Mode 4 Atlas WebSocket should be open before rerender');
1079
1108
  const wsCountBeforeRerender = diagnostics.webSocketConnections.length;
1080
1109
  const wsCloseCountBeforeRerender = liveWs.closeCount;
1081
1110
  const websocketFrameSeq = 10001;
1111
+ liveWs.onmessage?.({
1112
+ data: JSON.stringify({
1113
+ type: 'Mode4AtlasLayout',
1114
+ tiles: [{
1115
+ deviceId: patchTarget.DeviceId,
1116
+ x: 0,
1117
+ y: 0,
1118
+ width: 64,
1119
+ height: 36
1120
+ }]
1121
+ })
1122
+ });
1082
1123
  liveWs.onmessage?.({
1083
1124
  data: new Blob([encodeRemoteBinaryFrame({
1084
1125
  type: 'remote.frame.binary',
@@ -1088,36 +1129,36 @@ try {
1088
1129
  streamId: 'render-smoke-ws-live',
1089
1130
  contentHash: `render-smoke-ws-live-${websocketFrameSeq}`,
1090
1131
  mimeType: 'image/jpeg',
1132
+ frameMode: 'mode4-h264-atlas',
1091
1133
  width: 64,
1092
1134
  height: 36,
1093
1135
  capturedAt: new Date().toISOString(),
1094
1136
  receivedAt: new Date().toISOString()
1095
1137
  })])
1096
1138
  });
1097
- await wait(30);
1139
+ await waitFor(
1140
+ () => patchPreview.dataset.remoteFleetFrameSeq === String(websocketFrameSeq)
1141
+ );
1098
1142
  assert.equal(
1099
1143
  patchPreview.dataset.remoteFleetFrameSeq,
1100
1144
  String(websocketFrameSeq),
1101
1145
  JSON.stringify(diagnostics.runtimeTrace.slice(-12)));
1102
- await wait(10);
1103
- assert.ok(
1104
- Number(moduleRef._forceUpdateFrames || 0) >= 2,
1105
- 'binary live frame commits must wake the passive MDM CSS3D frame loop');
1106
- assert.equal(moduleRef._lastAnimationWakeReason, 'remote-live-css3d-frame');
1107
1146
  assert.ok(
1108
- diagnostics.runtimeTrace.some(event => event.type === 'remote.frame.css3dWake'),
1109
- 'binary live frame commits must trace the MDM CSS3D wake path');
1147
+ diagnostics.runtimeTrace.some(event =>
1148
+ event.type === 'remote.atlas.painted' &&
1149
+ Number(event.frameSeq || 0) === websocketFrameSeq),
1150
+ 'Mode 4 Atlas frame must paint its configured device tile');
1110
1151
 
1111
1152
  manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
1112
1153
  await wait(30);
1113
1154
  assert.equal(
1114
1155
  liveWs.closeCount,
1115
1156
  wsCloseCountBeforeRerender,
1116
- 'MDM rerender must not close the active binary frame WebSocket');
1157
+ 'MDM rerender must not close the active Mode 4 Atlas WebSocket');
1117
1158
  assert.equal(
1118
1159
  diagnostics.webSocketConnections.length,
1119
1160
  wsCountBeforeRerender,
1120
- 'MDM rerender must reuse the active binary frame WebSocket instead of opening a replacement');
1161
+ 'MDM rerender must reuse the active Mode 4 Atlas WebSocket instead of opening a replacement');
1121
1162
  const rerenderedPatchPreview = bodyView
1122
1163
  .querySelector(`article[data-device-id="${patchTarget.DeviceId}"]`)
1123
1164
  ?.querySelector('[data-remote-fleet-device-preview="tile"]');
@@ -1148,15 +1189,11 @@ try {
1148
1189
  .querySelector(`article[data-device-id="${patchTarget.DeviceId}"]`)
1149
1190
  ?.querySelector('[data-remote-fleet-device-preview="tile"]');
1150
1191
  assert.ok(otherBoardPatchPreview);
1151
- assert.equal(
1152
- otherBoardPatchPreview.dataset.remoteFleetFrameSeq,
1153
- String(websocketFrameSeq),
1154
- 'a different-board MDM should prime from the device-global latest binary frame cache');
1155
- assert.equal(
1156
- otherBoardPatchPreview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]')?.style.display,
1157
- 'block');
1192
+ assert.ok(
1193
+ diagnostics.webSocketConnections.length > wsCountBeforeRerender,
1194
+ 'a different-board MDM must own a separate Mode 4 Atlas session');
1158
1195
  assert.ok(diagnostics.runtimeTrace.some(event =>
1159
- (event.type === 'remote.frame.wsCachePrimed' || event.type === 'remote.frame.surfaceCachePrimed')
1196
+ event.type === 'remote.atlas.wsOpen'
1160
1197
  && event.nodeId === otherBoardNodeId));
1161
1198
  otherBoardShell.remove();
1162
1199
 
@@ -1260,18 +1297,17 @@ try {
1260
1297
  }
1261
1298
  })
1262
1299
  .filter(Boolean);
1263
- const autoLiveSubscribe = subscribeMessages.find(message =>
1264
- message.type === 'subscribe'
1265
- && message.nodeId === 'remote-fleet-render-smoke'
1266
- && message.autoStartLive === true);
1300
+ const atlasConfigure = subscribeMessages.find(message =>
1301
+ message.type === 'configure');
1267
1302
  assert.ok(diagnostics.fetchCalls.some(call => call.url.includes('/api/status?remoteFrames=ws')));
1268
- assert.ok(diagnostics.webSocketConnections.length >= 1, 'expected MDM render to open binary frame WebSocket');
1269
- assert.ok(autoLiveSubscribe, 'expected MDM render to subscribe with autoStartLive over WebSocket');
1270
- assert.equal(autoLiveSubscribe.fps, 12);
1271
- assert.equal(autoLiveSubscribe.maxWidth, 960);
1272
- assert.equal(autoLiveSubscribe.maxHeight, 540);
1273
- assert.equal(autoLiveSubscribe.quality, 60);
1274
- assert.ok(autoLiveSubscribe.deviceIds.length > 1, 'expected visible connected devices in WS subscription');
1303
+ assert.ok(diagnostics.webSocketConnections.length >= 1, 'expected MDM render to open Mode 4 Atlas WebSocket');
1304
+ assert.ok(atlasConfigure, 'expected MDM render to configure the Mode 4 Atlas WebSocket');
1305
+ assert.equal(atlasConfigure.fps, 20);
1306
+ assert.equal(atlasConfigure.width, 1920);
1307
+ assert.equal(atlasConfigure.height, 1080);
1308
+ assert.equal(atlasConfigure.tileWidth, 320);
1309
+ assert.equal(atlasConfigure.tileHeight, 180);
1310
+ assert.ok(atlasConfigure.deviceIds.length > 1, 'expected visible connected devices in Atlas configuration');
1275
1311
  assert.ok(diagnostics.runtimeTrace.some(event => event.type === 'remote.live.wsAutoStartRequested'));
1276
1312
  assert.equal(liveStartCalls.length, 0, 'WebSocket live path must not call DotNet live-start fallback');
1277
1313
  await wait(320);
@@ -1373,10 +1409,12 @@ try {
1373
1409
  && message.type === 'subscribe'
1374
1410
  && message.nodeId === 'remote-device-render-smoke'
1375
1411
  && message.autoStartLive === true
1376
- && message.fps === 20
1412
+ && message.fps === 30
1413
+ && message.mode === 'mode3-h264-hw'
1414
+ && message.streamPurpose === 'control'
1377
1415
  && Array.isArray(message.deviceIds)
1378
1416
  && message.deviceIds.includes(focusedDevice.DeviceId));
1379
- assert.ok(deviceControlSubscribe, 'RemoteFleetDevice node should subscribe to 20fps binary frames');
1417
+ assert.ok(deviceControlSubscribe, 'RemoteFleetDevice node should subscribe to 30fps Mode 3 control frames');
1380
1418
  const deviceControlWs = diagnostics.webSocketConnections
1381
1419
  .filter(ws => ws.url.includes('/api/remote/frames/ws'))
1382
1420
  .at(-1);
@@ -22,6 +22,13 @@ function wait(ms) {
22
22
  return new Promise(resolve => setTimeout(resolve, ms));
23
23
  }
24
24
 
25
+ const defaultHub = createRemoteHub({
26
+ env: {
27
+ MINDEXEC_REMOTE_HUB: '0'
28
+ }
29
+ });
30
+ assert.equal(defaultHub.getStatus().port, 5198, 'MindExecution RemoteHub must not reuse the LiveDesk 5197 port');
31
+
25
32
  async function waitFor(predicate, timeoutMs = 3000) {
26
33
  const startedAt = Date.now();
27
34
  while (Date.now() - startedAt < timeoutMs) {
@@ -302,10 +302,8 @@
302
302
  themeGroup.userData.localSnapGridStyle = options.localSnapGridStyle || 'line';
303
303
  themeGroup.userData.localSnapGridRadiusPx = 500;
304
304
  themeGroup.userData.themeAlias = options.themeAlias || 'LocalSnapGrid';
305
+ themeGroup.userData.localSnapGridBackgroundColor = options.backgroundColor;
305
306
  themeGroup.animate = () => { };
306
- if (scene && options.backgroundColor !== undefined) {
307
- scene.background = new THREE.Color(options.backgroundColor);
308
- }
309
307
  return setThemeAnimationMode(themeGroup, THEME_ANIMATION_MODE.None);
310
308
  }
311
309