@mindexec/cli 0.2.134 → 0.2.136

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.136",
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,23 +165,39 @@ 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');
187
+
188
+ const frameStatus = await fetchJson(`${bridge.baseUrl}/api/status?remoteFrames=ws`);
189
+ assert.equal(frameStatus.ok, true);
190
+ assert.ok(frameStatus.payload?.remoteFrameWs?.clientCount >= 1, JSON.stringify(frameStatus.payload?.remoteFrameWs));
191
+ assert.ok(frameStatus.payload?.remoteFrameWs?.framesSent >= 1, JSON.stringify(frameStatus.payload?.remoteFrameWs));
192
+ assert.equal(frameStatus.payload?.remoteFrameWs?.lastFrameDeviceId, device.deviceId);
193
+ assert.equal(frameStatus.payload?.remoteFrameWs?.lastFrameSeq, frame.metadata.frameSeq);
194
+ assert.ok(
195
+ frameStatus.payload?.remoteFrameWs?.clients?.some(client =>
196
+ Array.isArray(client.deviceIds)
197
+ && client.deviceIds.includes(device.deviceId)
198
+ && client.autoStartLive === true
199
+ && client.sent >= 1),
200
+ JSON.stringify(frameStatus.payload?.remoteFrameWs));
185
201
  ws.close();
186
202
 
187
203
  console.log('Remote frame WebSocket smoke OK');
package/server.js CHANGED
@@ -2128,6 +2128,40 @@ 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
+ let remoteFrameClientSeq = 0;
2132
+ const remoteFrameWsDiagnostics = {
2133
+ opened: 0,
2134
+ closed: 0,
2135
+ errors: 0,
2136
+ framesReceived: 0,
2137
+ framesSent: 0,
2138
+ framesDropped: 0,
2139
+ framesInvalid: 0,
2140
+ framesNoClients: 0,
2141
+ framesNoSubscribers: 0,
2142
+ autoStartRequested: 0,
2143
+ autoStartStarted: 0,
2144
+ autoStartSkipped: 0,
2145
+ lastOpenAt: '',
2146
+ lastCloseAt: '',
2147
+ lastErrorAt: '',
2148
+ lastFrameAt: '',
2149
+ lastFrameDeviceId: '',
2150
+ lastFrameSeq: 0,
2151
+ lastFrameBytes: 0,
2152
+ lastFrameMatchedClients: 0,
2153
+ lastFrameSentClients: 0,
2154
+ lastFrameDroppedClients: 0,
2155
+ lastAutoStartAt: '',
2156
+ lastAutoStartRequested: 0,
2157
+ lastAutoStartStarted: 0,
2158
+ lastAutoStartSkipped: 0
2159
+ };
2160
+ const REMOTE_FRAME_WS_AUTO_START_LIMIT = 120;
2161
+ const REMOTE_FRAME_WS_DEFAULT_FPS = 12;
2162
+ const REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH = 960;
2163
+ const REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT = 540;
2164
+ const REMOTE_FRAME_WS_DEFAULT_QUALITY = 60;
2131
2165
 
2132
2166
  httpServer.on('upgrade', (req, socket, head) => {
2133
2167
  try {
@@ -2217,12 +2251,112 @@ function updateRemoteFrameClientSubscription(ws, payload = {}) {
2217
2251
  const deviceIds = normalizeRemoteFrameWsDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
2218
2252
  ws.remoteFrameDeviceIds = new Set(deviceIds);
2219
2253
  ws.remoteFrameSubscriptionUpdatedAt = new Date().toISOString();
2254
+ ws.remoteFrameAutoStartLive = /^(1|true|yes|on|live)$/i.test(String(payload.autoStartLive ?? payload.startLive ?? ''));
2255
+ ws.remoteFrameAutoStartOptions = normalizeRemoteFrameWsLiveOptions(payload);
2220
2256
  sendRemoteFrameClientJson(ws, {
2221
2257
  type: 'RemoteFrameSubscription',
2222
2258
  timestamp: ws.remoteFrameSubscriptionUpdatedAt,
2223
2259
  deviceIds,
2224
- mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices'
2260
+ mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices',
2261
+ autoStartLive: ws.remoteFrameAutoStartLive === true
2225
2262
  });
2263
+ maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds);
2264
+ }
2265
+
2266
+ function clampRemoteFrameWsNumber(value, min, max, fallback) {
2267
+ const number = Number(value);
2268
+ if (!Number.isFinite(number)) {
2269
+ return fallback;
2270
+ }
2271
+
2272
+ return Math.max(min, Math.min(max, Math.round(number)));
2273
+ }
2274
+
2275
+ function normalizeRemoteFrameWsLiveOptions(payload = {}) {
2276
+ return {
2277
+ fps: clampRemoteFrameWsNumber(payload.fps, 1, 24, REMOTE_FRAME_WS_DEFAULT_FPS),
2278
+ maxWidth: clampRemoteFrameWsNumber(payload.maxWidth, 320, 2560, REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH),
2279
+ maxHeight: clampRemoteFrameWsNumber(payload.maxHeight, 180, 1440, REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT),
2280
+ quality: clampRemoteFrameWsNumber(payload.quality, 20, 95, REMOTE_FRAME_WS_DEFAULT_QUALITY),
2281
+ mode: String(payload.mode || 'remote-fast').trim() || 'remote-fast'
2282
+ };
2283
+ }
2284
+
2285
+ function getRemoteFrameWsDeviceLookup() {
2286
+ const devices = remoteHub.listDevices({ includeDataUrl: false });
2287
+ return new Map(devices.map(device => [String(device?.deviceId || '').trim(), device]).filter(([id]) => id));
2288
+ }
2289
+
2290
+ function maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds = []) {
2291
+ if (!ws || ws.remoteFrameAutoStartLive !== true || !Array.isArray(deviceIds) || deviceIds.length === 0) {
2292
+ return;
2293
+ }
2294
+
2295
+ const options = ws.remoteFrameAutoStartOptions || normalizeRemoteFrameWsLiveOptions();
2296
+ const lookup = getRemoteFrameWsDeviceLookup();
2297
+ const started = [];
2298
+ const skipped = [];
2299
+ for (const deviceId of deviceIds.slice(0, REMOTE_FRAME_WS_AUTO_START_LIMIT)) {
2300
+ const device = lookup.get(String(deviceId || '').trim());
2301
+ if (!device?.connected) {
2302
+ skipped.push({ deviceId, reason: 'not-connected' });
2303
+ continue;
2304
+ }
2305
+
2306
+ if (device.liveStreamActive === true) {
2307
+ skipped.push({ deviceId, reason: 'already-live' });
2308
+ continue;
2309
+ }
2310
+
2311
+ const liveCapable = device.liveStreamEnabled === true
2312
+ || device.liveStreamCapable === true
2313
+ || device.capabilities?.liveStream === true
2314
+ || device.capabilities?.stream === true
2315
+ || device.capabilities?.screenStream === true;
2316
+ if (!liveCapable) {
2317
+ skipped.push({ deviceId, reason: 'live-unavailable' });
2318
+ continue;
2319
+ }
2320
+
2321
+ const result = remoteHub.startLiveStream(deviceId, {
2322
+ ...options,
2323
+ streamId: `mdm-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
2324
+ });
2325
+ if (result?.ok === true) {
2326
+ started.push({
2327
+ deviceId,
2328
+ streamId: result.streamId || '',
2329
+ fps: result.fps || options.fps
2330
+ });
2331
+ } else {
2332
+ skipped.push({
2333
+ deviceId,
2334
+ reason: result?.error || 'start-failed'
2335
+ });
2336
+ }
2337
+ }
2338
+
2339
+ if (started.length > 0 || skipped.length > 0) {
2340
+ const now = new Date().toISOString();
2341
+ ws.remoteFrameLastAutoStartAt = now;
2342
+ ws.remoteFrameLastAutoStartStarted = started.length;
2343
+ ws.remoteFrameLastAutoStartSkipped = skipped.length;
2344
+ remoteFrameWsDiagnostics.autoStartRequested += Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT);
2345
+ remoteFrameWsDiagnostics.autoStartStarted += started.length;
2346
+ remoteFrameWsDiagnostics.autoStartSkipped += skipped.length;
2347
+ remoteFrameWsDiagnostics.lastAutoStartAt = now;
2348
+ remoteFrameWsDiagnostics.lastAutoStartRequested = Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT);
2349
+ remoteFrameWsDiagnostics.lastAutoStartStarted = started.length;
2350
+ remoteFrameWsDiagnostics.lastAutoStartSkipped = skipped.length;
2351
+ sendRemoteFrameClientJson(ws, {
2352
+ type: 'RemoteFrameLiveAutoStart',
2353
+ timestamp: now,
2354
+ requested: Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT),
2355
+ started,
2356
+ skipped: skipped.slice(0, 24),
2357
+ options
2358
+ });
2359
+ }
2226
2360
  }
2227
2361
 
2228
2362
  function buildRemoteFrameBinaryPacket(frameEvent) {
@@ -2258,20 +2392,36 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
2258
2392
  }
2259
2393
 
2260
2394
  function broadcastRemoteBinaryFrame(frameEvent) {
2395
+ const frame = frameEvent?.frame || {};
2396
+ remoteFrameWsDiagnostics.framesReceived += 1;
2397
+ remoteFrameWsDiagnostics.lastFrameAt = new Date().toISOString();
2398
+ remoteFrameWsDiagnostics.lastFrameDeviceId = String(frameEvent?.deviceId || frame.deviceId || '').trim();
2399
+ remoteFrameWsDiagnostics.lastFrameSeq = Number(frame.frameSeq || 0) || 0;
2400
+ remoteFrameWsDiagnostics.lastFrameBytes = Number(frameEvent?.byteLength || frameEvent?.payload?.length || 0) || 0;
2401
+ remoteFrameWsDiagnostics.lastFrameMatchedClients = 0;
2402
+ remoteFrameWsDiagnostics.lastFrameSentClients = 0;
2403
+ remoteFrameWsDiagnostics.lastFrameDroppedClients = 0;
2404
+
2261
2405
  if (remoteFrameClients.size === 0) {
2406
+ remoteFrameWsDiagnostics.framesNoClients += 1;
2262
2407
  return;
2263
2408
  }
2264
2409
 
2265
- const deviceId = String(frameEvent?.deviceId || frameEvent?.frame?.deviceId || '').trim();
2410
+ const deviceId = remoteFrameWsDiagnostics.lastFrameDeviceId;
2266
2411
  if (!deviceId) {
2412
+ remoteFrameWsDiagnostics.framesInvalid += 1;
2267
2413
  return;
2268
2414
  }
2269
2415
 
2270
2416
  const packet = buildRemoteFrameBinaryPacket(frameEvent);
2271
2417
  if (!packet) {
2418
+ remoteFrameWsDiagnostics.framesInvalid += 1;
2272
2419
  return;
2273
2420
  }
2274
2421
 
2422
+ let matched = 0;
2423
+ let sent = 0;
2424
+ let dropped = 0;
2275
2425
  for (const client of remoteFrameClients) {
2276
2426
  if (client.readyState !== 1) {
2277
2427
  continue;
@@ -2282,23 +2432,85 @@ function broadcastRemoteBinaryFrame(frameEvent) {
2282
2432
  continue;
2283
2433
  }
2284
2434
 
2435
+ matched += 1;
2285
2436
  if (client.bufferedAmount > 8 * 1024 * 1024) {
2286
2437
  client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
2438
+ client.remoteFrameLastDropAt = new Date().toISOString();
2439
+ remoteFrameWsDiagnostics.framesDropped += 1;
2440
+ dropped += 1;
2287
2441
  continue;
2288
2442
  }
2289
2443
 
2290
2444
  try {
2291
2445
  client.send(packet, { binary: true });
2292
2446
  client.remoteFrameSentCount = (client.remoteFrameSentCount || 0) + 1;
2447
+ client.remoteFrameLastSentAt = new Date().toISOString();
2448
+ client.remoteFrameLastDeviceId = deviceId;
2449
+ client.remoteFrameLastSeq = remoteFrameWsDiagnostics.lastFrameSeq;
2450
+ remoteFrameWsDiagnostics.framesSent += 1;
2451
+ sent += 1;
2293
2452
  } catch {
2294
2453
  client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
2454
+ client.remoteFrameLastDropAt = new Date().toISOString();
2455
+ remoteFrameWsDiagnostics.framesDropped += 1;
2456
+ dropped += 1;
2295
2457
  }
2296
2458
  }
2459
+
2460
+ remoteFrameWsDiagnostics.lastFrameMatchedClients = matched;
2461
+ remoteFrameWsDiagnostics.lastFrameSentClients = sent;
2462
+ remoteFrameWsDiagnostics.lastFrameDroppedClients = dropped;
2463
+ if (matched === 0) {
2464
+ remoteFrameWsDiagnostics.framesNoSubscribers += 1;
2465
+ }
2466
+ }
2467
+
2468
+ function serializeRemoteFrameWsDiagnostics() {
2469
+ const clients = [];
2470
+ for (const client of remoteFrameClients) {
2471
+ const deviceIds = client.remoteFrameDeviceIds instanceof Set
2472
+ ? Array.from(client.remoteFrameDeviceIds)
2473
+ : [];
2474
+ clients.push({
2475
+ id: client.remoteFrameClientId || '',
2476
+ readyState: client.readyState,
2477
+ connectedAt: client.remoteFrameConnectedAt || '',
2478
+ subscribedAt: client.remoteFrameSubscriptionUpdatedAt || '',
2479
+ deviceCount: deviceIds.length,
2480
+ deviceIds: deviceIds.slice(0, 24),
2481
+ autoStartLive: client.remoteFrameAutoStartLive === true,
2482
+ autoStartOptions: client.remoteFrameAutoStartOptions || null,
2483
+ sent: Number(client.remoteFrameSentCount || 0),
2484
+ dropped: Number(client.remoteFrameDroppedCount || 0),
2485
+ bufferedAmount: Number(client.bufferedAmount || 0),
2486
+ lastSentAt: client.remoteFrameLastSentAt || '',
2487
+ lastDropAt: client.remoteFrameLastDropAt || '',
2488
+ lastDeviceId: client.remoteFrameLastDeviceId || '',
2489
+ lastFrameSeq: Number(client.remoteFrameLastSeq || 0) || 0,
2490
+ lastAutoStartAt: client.remoteFrameLastAutoStartAt || '',
2491
+ lastAutoStartStarted: Number(client.remoteFrameLastAutoStartStarted || 0),
2492
+ lastAutoStartSkipped: Number(client.remoteFrameLastAutoStartSkipped || 0)
2493
+ });
2494
+ }
2495
+
2496
+ return {
2497
+ protocol: 'mindexec.remote.frames.binary.v1',
2498
+ path: '/api/remote/frames/ws',
2499
+ clientCount: remoteFrameClients.size,
2500
+ ...remoteFrameWsDiagnostics,
2501
+ clients
2502
+ };
2297
2503
  }
2298
2504
 
2299
2505
  remoteFrameWss.on('connection', (ws, req) => {
2300
2506
  remoteFrameClients.add(ws);
2301
2507
  ws.binaryType = 'arraybuffer';
2508
+ ws.remoteFrameClientId = `rfws-${++remoteFrameClientSeq}`;
2509
+ ws.remoteFrameConnectedAt = new Date().toISOString();
2510
+ ws.remoteFrameSentCount = 0;
2511
+ ws.remoteFrameDroppedCount = 0;
2512
+ remoteFrameWsDiagnostics.opened += 1;
2513
+ remoteFrameWsDiagnostics.lastOpenAt = ws.remoteFrameConnectedAt;
2302
2514
  try {
2303
2515
  const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
2304
2516
  updateRemoteFrameClientSubscription(ws, {
@@ -2322,12 +2534,21 @@ remoteFrameWss.on('connection', (ws, req) => {
2322
2534
  });
2323
2535
  }
2324
2536
  });
2325
- ws.on('close', () => remoteFrameClients.delete(ws));
2326
- ws.on('error', () => remoteFrameClients.delete(ws));
2537
+ ws.on('close', () => {
2538
+ remoteFrameClients.delete(ws);
2539
+ remoteFrameWsDiagnostics.closed += 1;
2540
+ remoteFrameWsDiagnostics.lastCloseAt = new Date().toISOString();
2541
+ });
2542
+ ws.on('error', () => {
2543
+ remoteFrameClients.delete(ws);
2544
+ remoteFrameWsDiagnostics.errors += 1;
2545
+ remoteFrameWsDiagnostics.lastErrorAt = new Date().toISOString();
2546
+ });
2327
2547
  sendRemoteFrameClientJson(ws, {
2328
2548
  type: 'RemoteFrameSocketReady',
2329
2549
  timestamp: new Date().toISOString(),
2330
- protocol: 'mindexec.remote.frames.binary.v1'
2550
+ protocol: 'mindexec.remote.frames.binary.v1',
2551
+ clientId: ws.remoteFrameClientId
2331
2552
  });
2332
2553
  });
2333
2554
 
@@ -9192,6 +9413,7 @@ app.get('/api/status', async (req, res) => {
9192
9413
  bridgeTokenHeader,
9193
9414
  bridgeAuthRequired,
9194
9415
  remoteHub: remoteHub.getStatus({ includeSecrets: false }),
9416
+ remoteFrameWs: serializeRemoteFrameWsDiagnostics(),
9195
9417
  remoteAgent: serializeRemoteAgentState(),
9196
9418
  remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
9197
9419
  shellJobsPath: '/api/shell/jobs',
@@ -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