@mindexec/cli 0.2.134 → 0.2.135

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.134",
3
+ "version": "0.2.135",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -161,14 +161,14 @@ async function launchBrowser(chromium) {
161
161
  throw new Error(`Unable to launch a Chromium browser: ${lastError?.message || 'unknown error'}`);
162
162
  }
163
163
 
164
- async function paintRemoteFrameInBrowser(page, wsUrl, deviceId) {
164
+ async function paintRemoteFrameInBrowser(page, wsUrl, deviceId, fps = REQUESTED_FPS) {
165
165
  await page.setContent(`
166
166
  <!doctype html>
167
167
  <meta charset="utf-8">
168
168
  <canvas id="screen" width="320" height="180"></canvas>
169
169
  `);
170
170
 
171
- return await page.evaluate(async ({ wsUrl, deviceId }) => {
171
+ return await page.evaluate(async ({ wsUrl, deviceId, fps }) => {
172
172
  const canvas = document.getElementById('screen');
173
173
  const context = canvas.getContext('2d', { alpha: false, desynchronized: true }) || canvas.getContext('2d');
174
174
  if (!context) {
@@ -203,7 +203,15 @@ async function paintRemoteFrameInBrowser(page, wsUrl, deviceId) {
203
203
  const ws = new WebSocket(wsUrl);
204
204
  ws.binaryType = 'arraybuffer';
205
205
  ws.onopen = () => {
206
- ws.send(JSON.stringify({ type: 'subscribe', deviceIds: [deviceId] }));
206
+ ws.send(JSON.stringify({
207
+ type: 'subscribe',
208
+ deviceIds: [deviceId],
209
+ autoStartLive: true,
210
+ fps,
211
+ maxWidth: 960,
212
+ maxHeight: 540,
213
+ quality: 60
214
+ }));
207
215
  };
208
216
  ws.onerror = () => {
209
217
  clearTimeout(timeout);
@@ -262,7 +270,7 @@ async function paintRemoteFrameInBrowser(page, wsUrl, deviceId) {
262
270
  }
263
271
  };
264
272
  });
265
- }, { wsUrl, deviceId });
273
+ }, { wsUrl, deviceId, fps });
266
274
  }
267
275
 
268
276
  async function main() {
@@ -320,23 +328,11 @@ async function main() {
320
328
  && item.capabilities?.binaryFrames === true) || null;
321
329
  }, 15000, `RemoteFast device registration\n${hostBridge.details()}\n${clientBridge.details()}`);
322
330
 
323
- const live = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices/${encodeURIComponent(device.deviceId)}/live/start`, {
324
- method: 'POST',
325
- body: JSON.stringify({
326
- fps: REQUESTED_FPS,
327
- maxWidth: 960,
328
- maxHeight: 540,
329
- quality: 60
330
- })
331
- });
332
- assert.equal(live.ok, true, JSON.stringify(live.payload));
333
- assert.equal(live.payload?.ok, true, JSON.stringify(live.payload));
334
-
335
331
  const chromium = await loadPlaywrightChromium();
336
332
  browser = await launchBrowser(chromium);
337
333
  const page = await browser.newPage();
338
334
  const wsUrl = `${hostBridge.baseUrl.replace(/^http:/, 'ws:')}/api/remote/frames/ws?token=${encodeURIComponent(BRIDGE_TOKEN)}`;
339
- const painted = await paintRemoteFrameInBrowser(page, wsUrl, device.deviceId);
335
+ const painted = await paintRemoteFrameInBrowser(page, wsUrl, device.deviceId, REQUESTED_FPS);
340
336
  assert.ok(painted.frameSeq > 0, JSON.stringify(painted));
341
337
  assert.equal(painted.fps, REQUESTED_FPS, JSON.stringify(painted));
342
338
  assert.ok(painted.width > 0 && painted.height > 0, JSON.stringify(painted));
@@ -151,7 +151,7 @@ async function main() {
151
151
 
152
152
  const seed = await fetchJson(`${bridge.baseUrl}/api/remote/synthetic/seed`, {
153
153
  method: 'POST',
154
- body: JSON.stringify({ count: 4, connectedRatio: 1, liveRatio: 1 })
154
+ body: JSON.stringify({ count: 4, connectedRatio: 1, thumbnailRatio: 1, liveCount: 0 })
155
155
  });
156
156
  assert.equal(seed.ok, true);
157
157
 
@@ -165,20 +165,22 @@ async function main() {
165
165
  ws.once('open', resolve);
166
166
  ws.once('error', reject);
167
167
  });
168
- ws.send(JSON.stringify({ type: 'subscribe', deviceIds: [device.deviceId] }));
168
+ ws.send(JSON.stringify({
169
+ type: 'subscribe',
170
+ deviceIds: [device.deviceId],
171
+ autoStartLive: true,
172
+ fps: 12,
173
+ maxWidth: 960,
174
+ maxHeight: 540,
175
+ quality: 60
176
+ }));
169
177
 
170
178
  const framePromise = waitForBinaryFrame(ws);
171
- const live = await fetchJson(`${bridge.baseUrl}/api/remote/devices/${encodeURIComponent(device.deviceId)}/live/start`, {
172
- method: 'POST',
173
- body: JSON.stringify({ fps: 20 })
174
- });
175
- assert.equal(live.ok, true);
176
-
177
179
  const frame = await framePromise;
178
180
  assert.equal(frame.metadata.type, 'remote.frame.binary');
179
181
  assert.equal(frame.metadata.kind, 'live');
180
182
  assert.equal(frame.metadata.deviceId, device.deviceId);
181
- assert.equal(frame.metadata.fps, 20);
183
+ assert.equal(frame.metadata.fps, 12);
182
184
  assert.ok(frame.metadata.frameSeq > 0, 'frameSeq should be positive');
183
185
  assert.equal(frame.metadata.mimeType, 'image/png');
184
186
  assert.ok(frame.payload.length > 0, 'payload should be non-empty');
package/server.js CHANGED
@@ -2128,6 +2128,11 @@ const remoteFrameWss = new WebSocketServer({ noServer: true });
2128
2128
  const wsClients = new Set();
2129
2129
  const remoteFrameClients = new Set();
2130
2130
  const shellJobs = new Map();
2131
+ const REMOTE_FRAME_WS_AUTO_START_LIMIT = 120;
2132
+ const REMOTE_FRAME_WS_DEFAULT_FPS = 12;
2133
+ const REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH = 960;
2134
+ const REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT = 540;
2135
+ const REMOTE_FRAME_WS_DEFAULT_QUALITY = 60;
2131
2136
 
2132
2137
  httpServer.on('upgrade', (req, socket, head) => {
2133
2138
  try {
@@ -2217,12 +2222,101 @@ function updateRemoteFrameClientSubscription(ws, payload = {}) {
2217
2222
  const deviceIds = normalizeRemoteFrameWsDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
2218
2223
  ws.remoteFrameDeviceIds = new Set(deviceIds);
2219
2224
  ws.remoteFrameSubscriptionUpdatedAt = new Date().toISOString();
2225
+ ws.remoteFrameAutoStartLive = /^(1|true|yes|on|live)$/i.test(String(payload.autoStartLive ?? payload.startLive ?? ''));
2226
+ ws.remoteFrameAutoStartOptions = normalizeRemoteFrameWsLiveOptions(payload);
2220
2227
  sendRemoteFrameClientJson(ws, {
2221
2228
  type: 'RemoteFrameSubscription',
2222
2229
  timestamp: ws.remoteFrameSubscriptionUpdatedAt,
2223
2230
  deviceIds,
2224
- mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices'
2231
+ mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices',
2232
+ autoStartLive: ws.remoteFrameAutoStartLive === true
2225
2233
  });
2234
+ maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds);
2235
+ }
2236
+
2237
+ function clampRemoteFrameWsNumber(value, min, max, fallback) {
2238
+ const number = Number(value);
2239
+ if (!Number.isFinite(number)) {
2240
+ return fallback;
2241
+ }
2242
+
2243
+ return Math.max(min, Math.min(max, Math.round(number)));
2244
+ }
2245
+
2246
+ function normalizeRemoteFrameWsLiveOptions(payload = {}) {
2247
+ return {
2248
+ fps: clampRemoteFrameWsNumber(payload.fps, 1, 24, REMOTE_FRAME_WS_DEFAULT_FPS),
2249
+ maxWidth: clampRemoteFrameWsNumber(payload.maxWidth, 320, 2560, REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH),
2250
+ maxHeight: clampRemoteFrameWsNumber(payload.maxHeight, 180, 1440, REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT),
2251
+ quality: clampRemoteFrameWsNumber(payload.quality, 20, 95, REMOTE_FRAME_WS_DEFAULT_QUALITY),
2252
+ mode: String(payload.mode || 'remote-fast').trim() || 'remote-fast'
2253
+ };
2254
+ }
2255
+
2256
+ function getRemoteFrameWsDeviceLookup() {
2257
+ const devices = remoteHub.listDevices({ includeDataUrl: false });
2258
+ return new Map(devices.map(device => [String(device?.deviceId || '').trim(), device]).filter(([id]) => id));
2259
+ }
2260
+
2261
+ function maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds = []) {
2262
+ if (!ws || ws.remoteFrameAutoStartLive !== true || !Array.isArray(deviceIds) || deviceIds.length === 0) {
2263
+ return;
2264
+ }
2265
+
2266
+ const options = ws.remoteFrameAutoStartOptions || normalizeRemoteFrameWsLiveOptions();
2267
+ const lookup = getRemoteFrameWsDeviceLookup();
2268
+ const started = [];
2269
+ const skipped = [];
2270
+ for (const deviceId of deviceIds.slice(0, REMOTE_FRAME_WS_AUTO_START_LIMIT)) {
2271
+ const device = lookup.get(String(deviceId || '').trim());
2272
+ if (!device?.connected) {
2273
+ skipped.push({ deviceId, reason: 'not-connected' });
2274
+ continue;
2275
+ }
2276
+
2277
+ if (device.liveStreamActive === true) {
2278
+ skipped.push({ deviceId, reason: 'already-live' });
2279
+ continue;
2280
+ }
2281
+
2282
+ const liveCapable = device.liveStreamEnabled === true
2283
+ || device.liveStreamCapable === true
2284
+ || device.capabilities?.liveStream === true
2285
+ || device.capabilities?.stream === true
2286
+ || device.capabilities?.screenStream === true;
2287
+ if (!liveCapable) {
2288
+ skipped.push({ deviceId, reason: 'live-unavailable' });
2289
+ continue;
2290
+ }
2291
+
2292
+ const result = remoteHub.startLiveStream(deviceId, {
2293
+ ...options,
2294
+ streamId: `mdm-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
2295
+ });
2296
+ if (result?.ok === true) {
2297
+ started.push({
2298
+ deviceId,
2299
+ streamId: result.streamId || '',
2300
+ fps: result.fps || options.fps
2301
+ });
2302
+ } else {
2303
+ skipped.push({
2304
+ deviceId,
2305
+ reason: result?.error || 'start-failed'
2306
+ });
2307
+ }
2308
+ }
2309
+
2310
+ if (started.length > 0 || skipped.length > 0) {
2311
+ sendRemoteFrameClientJson(ws, {
2312
+ type: 'RemoteFrameLiveAutoStart',
2313
+ timestamp: new Date().toISOString(),
2314
+ requested: Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT),
2315
+ started,
2316
+ skipped: skipped.slice(0, 24),
2317
+ options
2318
+ });
2319
+ }
2226
2320
  }
2227
2321
 
2228
2322
  function buildRemoteFrameBinaryPacket(frameEvent) {
@@ -13828,7 +13828,16 @@
13828
13828
  }
13829
13829
 
13830
13830
  const deviceIds = getRemoteFleetBinaryFrameDeviceIds(session);
13831
- const key = deviceIds.join('\n');
13831
+ const liveOptions = session.liveOptions || {};
13832
+ const optionKey = [
13833
+ liveOptions.autoStartLive === true ? 'auto-live' : 'passive',
13834
+ Number(liveOptions.fps || 0) || 0,
13835
+ Number(liveOptions.maxWidth || 0) || 0,
13836
+ Number(liveOptions.maxHeight || 0) || 0,
13837
+ Number(liveOptions.quality || 0) || 0,
13838
+ String(liveOptions.mode || '')
13839
+ ].join('|');
13840
+ const key = `${deviceIds.join('\n')}::${optionKey}`;
13832
13841
  if (!force && session.subscriptionKey === key) {
13833
13842
  return true;
13834
13843
  }
@@ -13837,7 +13846,13 @@
13837
13846
  session.ws.send(JSON.stringify({
13838
13847
  type: 'subscribe',
13839
13848
  nodeId: session.nodeId,
13840
- deviceIds
13849
+ deviceIds,
13850
+ autoStartLive: liveOptions.autoStartLive === true,
13851
+ fps: liveOptions.fps,
13852
+ maxWidth: liveOptions.maxWidth,
13853
+ maxHeight: liveOptions.maxHeight,
13854
+ quality: liveOptions.quality,
13855
+ mode: liveOptions.mode
13841
13856
  }));
13842
13857
  return true;
13843
13858
  }
@@ -13984,7 +13999,7 @@
13984
13999
  }
13985
14000
  }
13986
14001
 
13987
- function startRemoteFleetBinaryFrameSocket(bodyView, getDeviceIds) {
14002
+ function startRemoteFleetBinaryFrameSocket(bodyView, getDeviceIds, options = {}) {
13988
14003
  const nodeId = String(bodyView?.dataset?.nodeId || '').trim();
13989
14004
  if (!nodeId || typeof WebSocket !== 'function') {
13990
14005
  return false;
@@ -14007,6 +14022,14 @@
14007
14022
 
14008
14023
  session.bodies.add(bodyView);
14009
14024
  session.getDeviceIds = getDeviceIds;
14025
+ session.liveOptions = {
14026
+ autoStartLive: options.autoStartLive === true,
14027
+ fps: Number(options.fps || REMOTE_FLEET_MONITOR_LIVE_FPS) || REMOTE_FLEET_MONITOR_LIVE_FPS,
14028
+ maxWidth: Number(options.maxWidth || 960) || 960,
14029
+ maxHeight: Number(options.maxHeight || 540) || 540,
14030
+ quality: Number(options.quality || 60) || 60,
14031
+ mode: String(options.mode || 'remote-fast')
14032
+ };
14010
14033
  bodyView._remoteFleetBinaryFrameSession = session;
14011
14034
  openRemoteFleetBinaryFrameSocket(session).catch(() => undefined);
14012
14035
  return true;
@@ -15304,7 +15327,14 @@
15304
15327
 
15305
15328
  let binarySession = bodyView._remoteFleetBinaryFrameSession || null;
15306
15329
  if (!binarySession) {
15307
- if (!startRemoteFleetBinaryFrameSocket(bodyView, () => [])) {
15330
+ if (!startRemoteFleetBinaryFrameSocket(bodyView, () => [], {
15331
+ autoStartLive: true,
15332
+ fps: REMOTE_FLEET_CONTROL_LIVE_FPS,
15333
+ maxWidth: 1280,
15334
+ maxHeight: 720,
15335
+ quality: 65,
15336
+ mode: 'remote-fast-control'
15337
+ })) {
15308
15338
  return false;
15309
15339
  }
15310
15340
  binarySession = bodyView._remoteFleetBinaryFrameSession || null;
@@ -15319,6 +15349,15 @@
15319
15349
  }
15320
15350
 
15321
15351
  binarySession.controlSessions.add(controlSession);
15352
+ binarySession.liveOptions = {
15353
+ ...(binarySession.liveOptions || {}),
15354
+ autoStartLive: true,
15355
+ fps: REMOTE_FLEET_CONTROL_LIVE_FPS,
15356
+ maxWidth: 1280,
15357
+ maxHeight: 720,
15358
+ quality: 65,
15359
+ mode: 'remote-fast-control'
15360
+ };
15322
15361
  controlSession.binaryFrameSession = binarySession;
15323
15362
  controlSession.binaryFramePreferred = true;
15324
15363
  refreshRemoteFleetBinaryFrameSubscription(binarySession, true);
@@ -17970,6 +18009,13 @@
17970
18009
  const binaryFrameSocketStarted = startRemoteFleetBinaryFrameSocket(bodyView, () => {
17971
18010
  const liveIds = getVisibleLiveFrameDeviceIds();
17972
18011
  return liveIds.length > 0 ? liveIds : getVisibleFrameDeviceIds();
18012
+ }, {
18013
+ autoStartLive: true,
18014
+ fps: REMOTE_FLEET_MONITOR_LIVE_FPS,
18015
+ maxWidth: 960,
18016
+ maxHeight: 540,
18017
+ quality: 60,
18018
+ mode: 'remote-fast'
17973
18019
  });
17974
18020
  if (!binaryFrameSocketStarted) {
17975
18021
  window.RuntimeTrace?.emit?.('remote.frame.wsUnavailable', {
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "yUK/iR3X",
2
+ "version": "IfRQGIi2",
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-RLe5Ta1um44TU9QYuOpzKHrl8t1tOWQdkL+zwH79MFA=",
89
+ "hash": "sha256-WBIjGLmIxv1h8qPdh1KvY1XXHi1slyoTo2BVqXis+hQ=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: yUK/iR3X */
1
+ /* Manifest version: IfRQGIi2 */
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