@mindexec/cli 0.2.135 → 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.135",
3
+ "version": "0.2.136",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -184,6 +184,20 @@ async function main() {
184
184
  assert.ok(frame.metadata.frameSeq > 0, 'frameSeq should be positive');
185
185
  assert.equal(frame.metadata.mimeType, 'image/png');
186
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));
187
201
  ws.close();
188
202
 
189
203
  console.log('Remote frame WebSocket smoke OK');
package/server.js CHANGED
@@ -2128,6 +2128,35 @@ 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
+ };
2131
2160
  const REMOTE_FRAME_WS_AUTO_START_LIMIT = 120;
2132
2161
  const REMOTE_FRAME_WS_DEFAULT_FPS = 12;
2133
2162
  const REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH = 960;
@@ -2308,9 +2337,20 @@ function maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds = []) {
2308
2337
  }
2309
2338
 
2310
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;
2311
2351
  sendRemoteFrameClientJson(ws, {
2312
2352
  type: 'RemoteFrameLiveAutoStart',
2313
- timestamp: new Date().toISOString(),
2353
+ timestamp: now,
2314
2354
  requested: Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT),
2315
2355
  started,
2316
2356
  skipped: skipped.slice(0, 24),
@@ -2352,20 +2392,36 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
2352
2392
  }
2353
2393
 
2354
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
+
2355
2405
  if (remoteFrameClients.size === 0) {
2406
+ remoteFrameWsDiagnostics.framesNoClients += 1;
2356
2407
  return;
2357
2408
  }
2358
2409
 
2359
- const deviceId = String(frameEvent?.deviceId || frameEvent?.frame?.deviceId || '').trim();
2410
+ const deviceId = remoteFrameWsDiagnostics.lastFrameDeviceId;
2360
2411
  if (!deviceId) {
2412
+ remoteFrameWsDiagnostics.framesInvalid += 1;
2361
2413
  return;
2362
2414
  }
2363
2415
 
2364
2416
  const packet = buildRemoteFrameBinaryPacket(frameEvent);
2365
2417
  if (!packet) {
2418
+ remoteFrameWsDiagnostics.framesInvalid += 1;
2366
2419
  return;
2367
2420
  }
2368
2421
 
2422
+ let matched = 0;
2423
+ let sent = 0;
2424
+ let dropped = 0;
2369
2425
  for (const client of remoteFrameClients) {
2370
2426
  if (client.readyState !== 1) {
2371
2427
  continue;
@@ -2376,23 +2432,85 @@ function broadcastRemoteBinaryFrame(frameEvent) {
2376
2432
  continue;
2377
2433
  }
2378
2434
 
2435
+ matched += 1;
2379
2436
  if (client.bufferedAmount > 8 * 1024 * 1024) {
2380
2437
  client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
2438
+ client.remoteFrameLastDropAt = new Date().toISOString();
2439
+ remoteFrameWsDiagnostics.framesDropped += 1;
2440
+ dropped += 1;
2381
2441
  continue;
2382
2442
  }
2383
2443
 
2384
2444
  try {
2385
2445
  client.send(packet, { binary: true });
2386
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;
2387
2452
  } catch {
2388
2453
  client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
2454
+ client.remoteFrameLastDropAt = new Date().toISOString();
2455
+ remoteFrameWsDiagnostics.framesDropped += 1;
2456
+ dropped += 1;
2389
2457
  }
2390
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
+ };
2391
2503
  }
2392
2504
 
2393
2505
  remoteFrameWss.on('connection', (ws, req) => {
2394
2506
  remoteFrameClients.add(ws);
2395
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;
2396
2514
  try {
2397
2515
  const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
2398
2516
  updateRemoteFrameClientSubscription(ws, {
@@ -2416,12 +2534,21 @@ remoteFrameWss.on('connection', (ws, req) => {
2416
2534
  });
2417
2535
  }
2418
2536
  });
2419
- ws.on('close', () => remoteFrameClients.delete(ws));
2420
- 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
+ });
2421
2547
  sendRemoteFrameClientJson(ws, {
2422
2548
  type: 'RemoteFrameSocketReady',
2423
2549
  timestamp: new Date().toISOString(),
2424
- protocol: 'mindexec.remote.frames.binary.v1'
2550
+ protocol: 'mindexec.remote.frames.binary.v1',
2551
+ clientId: ws.remoteFrameClientId
2425
2552
  });
2426
2553
  });
2427
2554
 
@@ -9286,6 +9413,7 @@ app.get('/api/status', async (req, res) => {
9286
9413
  bridgeTokenHeader,
9287
9414
  bridgeAuthRequired,
9288
9415
  remoteHub: remoteHub.getStatus({ includeSecrets: false }),
9416
+ remoteFrameWs: serializeRemoteFrameWsDiagnostics(),
9289
9417
  remoteAgent: serializeRemoteAgentState(),
9290
9418
  remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
9291
9419
  shellJobsPath: '/api/shell/jobs',