@mindexec/cli 0.2.9 → 0.2.11

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.9",
3
+ "version": "0.2.11",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -20,8 +20,9 @@
20
20
  "scripts": {
21
21
  "start": "node launch-bridge.cjs",
22
22
  "dev": "node launch-bridge.cjs --watch",
23
- "test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/remote-hub-smoke.mjs",
23
+ "test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs",
24
24
  "test:remote": "node scripts/remote-hub-smoke.mjs",
25
+ "test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
25
26
  "pack:dry": "npm pack --dry-run",
26
27
  "setup:grammars": "node scripts/setup-tree-sitter-grammars.mjs",
27
28
  "postinstall": "npm run setup:grammars"
package/remote-hub.js CHANGED
@@ -12,6 +12,8 @@ const MAX_AGENT_TASK_CHARS = 4000;
12
12
  const MAX_AGENT_TASK_RESULT_CHARS = 3000;
13
13
  const RECENT_TASK_LIMIT = 12;
14
14
  const REMOTE_PROTOCOL_VERSION = 1;
15
+ const MAX_SYNTHETIC_DEVICES = 1000;
16
+ const SYNTHETIC_FRAME_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC';
15
17
 
16
18
  function isEnabledValue(value, fallback = true) {
17
19
  if (value === undefined || value === null || value === '') {
@@ -62,6 +64,25 @@ function readCapabilityFlag(capabilities, key) {
62
64
  return /^(1|true|yes|on)$/i.test(String(value || '').trim());
63
65
  }
64
66
 
67
+ function createDeviceCounters(overrides = {}) {
68
+ return {
69
+ messagesReceived: 0,
70
+ statusReceived: 0,
71
+ commandsSent: 0,
72
+ commandResultsReceived: 0,
73
+ thumbnailFramesReceived: 0,
74
+ thumbnailFramesDropped: 0,
75
+ liveFramesReceived: 0,
76
+ liveFramesDropped: 0,
77
+ liveStreamsStarted: 0,
78
+ liveStreamsStopped: 0,
79
+ tasksQueued: 0,
80
+ taskResultsReceived: 0,
81
+ taskResultsFailed: 0,
82
+ ...overrides
83
+ };
84
+ }
85
+
65
86
  function normalizeDeviceId(value) {
66
87
  const id = safeString(value, 128).replace(/[^a-zA-Z0-9_.:-]/g, '-');
67
88
  return id || crypto.randomUUID();
@@ -122,6 +143,7 @@ function serializeDevice(device) {
122
143
  latestLiveFrame: device.latestLiveFrame ? { ...device.latestLiveFrame } : null,
123
144
  activeLiveStream: device.activeLiveStream ? { ...device.activeLiveStream } : null,
124
145
  latestTask: device.latestTask ? { ...device.latestTask } : null,
146
+ synthetic: device.synthetic === true,
125
147
  recentTasks: Array.isArray(device.recentTasks)
126
148
  ? device.recentTasks.map(task => ({ ...task }))
127
149
  : [],
@@ -207,6 +229,77 @@ export function createRemoteHub(options = {}) {
207
229
  });
208
230
  }
209
231
 
232
+ function makeSyntheticFrame(device, streamId, mode = 'thumbnail', options = {}) {
233
+ const now = new Date().toISOString();
234
+ const frameSeq = Number(device?.counters?.thumbnailFramesReceived || 0)
235
+ + Number(device?.counters?.liveFramesReceived || 0)
236
+ + 1;
237
+ return {
238
+ streamId: safeString(streamId, 128) || `${mode}-${Date.now()}`,
239
+ frameSeq,
240
+ commandId: safeString(options.commandId, 128),
241
+ width: clampNumber(options.width, 2, 2560, mode === 'remote-fast' ? 960 : 360),
242
+ height: clampNumber(options.height, 1, 1440, mode === 'remote-fast' ? 540 : 220),
243
+ mimeType: 'image/png',
244
+ format: 'image/png',
245
+ mode,
246
+ fps: clampNumber(options.fps, 1, 24, mode === 'remote-fast' ? 12 : 1),
247
+ capturedAt: now,
248
+ receivedAt: now,
249
+ byteLength: 68,
250
+ dataUrl: SYNTHETIC_FRAME_DATA_URL
251
+ };
252
+ }
253
+
254
+ function applySyntheticThumbnail(device, command) {
255
+ if (!device) {
256
+ return null;
257
+ }
258
+
259
+ const payload = command?.payload || {};
260
+ const frame = makeSyntheticFrame(device, payload.streamId || 'synthetic-thumb', 'thumbnail', {
261
+ commandId: command?.commandId,
262
+ width: payload.maxWidth || 360,
263
+ height: payload.maxHeight || 220
264
+ });
265
+ delete frame.mode;
266
+ delete frame.fps;
267
+ device.latestThumbnail = frame;
268
+ device.lastSeenAt = frame.receivedAt;
269
+ device.counters.thumbnailFramesReceived += 1;
270
+ emitRemoteEvent('RemoteFrameReceived', device, {
271
+ streamId: frame.streamId,
272
+ frameSeq: frame.frameSeq,
273
+ synthetic: true
274
+ });
275
+ return frame;
276
+ }
277
+
278
+ function applySyntheticLiveFrame(device, streamId, options = {}) {
279
+ if (!device?.activeLiveStream?.active) {
280
+ return null;
281
+ }
282
+
283
+ const frame = makeSyntheticFrame(device, streamId || device.activeLiveStream.streamId, 'remote-fast', {
284
+ commandId: options.commandId || device.activeLiveStream.commandId,
285
+ width: options.maxWidth || 960,
286
+ height: options.maxHeight || 540,
287
+ fps: options.fps || device.activeLiveStream.fps
288
+ });
289
+ device.latestLiveFrame = frame;
290
+ device.activeLiveStream.lastFrameAt = frame.receivedAt;
291
+ device.activeLiveStream.lastFrameSeq = frame.frameSeq;
292
+ device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
293
+ device.lastSeenAt = frame.receivedAt;
294
+ device.counters.liveFramesReceived += 1;
295
+ emitRemoteEvent('RemoteFrameReceived', device, {
296
+ streamId: frame.streamId,
297
+ frameSeq: frame.frameSeq,
298
+ synthetic: true
299
+ });
300
+ return frame;
301
+ }
302
+
210
303
  function closeExistingDeviceSocket(deviceId, nextSessionId) {
211
304
  const existing = devices.get(deviceId);
212
305
  if (!existing?.socket || existing.socket.destroyed) {
@@ -222,6 +315,186 @@ export function createRemoteHub(options = {}) {
222
315
  existing.socket.destroy();
223
316
  }
224
317
 
318
+ function clearSyntheticFleet() {
319
+ let removed = 0;
320
+ for (const [deviceId, device] of devices.entries()) {
321
+ if (device.synthetic === true) {
322
+ devices.delete(deviceId);
323
+ removed += 1;
324
+ }
325
+ }
326
+
327
+ return { ok: true, removed, total: devices.size };
328
+ }
329
+
330
+ function seedSyntheticFleet(options = {}) {
331
+ const count = clampNumber(options.count, 1, MAX_SYNTHETIC_DEVICES, 250);
332
+ const connectedRatio = Math.max(0, Math.min(1, Number(options.connectedRatio ?? 0.88)));
333
+ const thumbnailRatio = Math.max(0, Math.min(1, Number(options.thumbnailRatio ?? 0.7)));
334
+ const aiAssistRatio = Math.max(0, Math.min(1, Number(options.aiAssistRatio ?? 0.35)));
335
+ const liveCount = clampNumber(options.liveCount, 0, Math.min(count, 24), Math.min(6, count));
336
+ const replace = options.replace !== false;
337
+ const now = new Date();
338
+ const platforms = ['win32', 'linux', 'darwin'];
339
+
340
+ if (replace) {
341
+ clearSyntheticFleet();
342
+ }
343
+
344
+ let connected = 0;
345
+ let aiAssist = 0;
346
+ let live = 0;
347
+ for (let index = 0; index < count; index += 1) {
348
+ const ordinal = index + 1;
349
+ const deviceId = `synthetic-${String(ordinal).padStart(4, '0')}`;
350
+ const isConnected = index / count < connectedRatio;
351
+ const hasThumbnail = index / count < thumbnailRatio;
352
+ const hasAiAssist = index / count < aiAssistRatio;
353
+ const isLive = isConnected && index < liveCount;
354
+ const seenAt = new Date(now.getTime() - index * 1350).toISOString();
355
+ const connectedAt = new Date(now.getTime() - (index + 8) * 60000).toISOString();
356
+ const platform = platforms[index % platforms.length];
357
+ const usedMemRatio = Number((0.18 + (index % 71) / 100).toFixed(2));
358
+ const load1 = Number(((index % 19) / 3).toFixed(2));
359
+ const taskStatus = index % 17 === 0
360
+ ? 'failed'
361
+ : index % 5 === 0
362
+ ? 'completed'
363
+ : index % 7 === 0
364
+ ? 'queued'
365
+ : '';
366
+ const latestTask = taskStatus
367
+ ? {
368
+ taskId: `synthetic-task-${ordinal}`,
369
+ commandId: `synthetic-command-${ordinal}`,
370
+ title: taskStatus === 'failed' ? 'Synthetic issue check' : 'Synthetic status task',
371
+ instructionPreview: 'Synthetic fleet scale validation task.',
372
+ status: taskStatus,
373
+ approvalLevel: hasAiAssist ? 'ai-assist' : 'task-only',
374
+ requestedAt: seenAt,
375
+ sentAt: seenAt,
376
+ updatedAt: seenAt,
377
+ completedAt: taskStatus === 'completed' || taskStatus === 'failed' ? seenAt : '',
378
+ error: taskStatus === 'failed' ? 'Synthetic task failure sample.' : '',
379
+ resultKind: 'synthetic-agent-task',
380
+ resultSummary: taskStatus === 'failed'
381
+ ? 'Synthetic task reported a sample issue.'
382
+ : 'Synthetic task completed for scale validation.'
383
+ }
384
+ : null;
385
+ const device = {
386
+ socket: null,
387
+ synthetic: true,
388
+ deviceId,
389
+ sessionId: `synthetic-session-${ordinal}`,
390
+ deviceName: `Synthetic PC ${String(ordinal).padStart(4, '0')}`,
391
+ hostname: `synthetic-host-${String(ordinal).padStart(4, '0')}`,
392
+ platform,
393
+ arch: index % 4 === 0 ? 'arm64' : 'x64',
394
+ pid: 0,
395
+ agentVersion: 'synthetic-scale',
396
+ capabilities: {
397
+ status: true,
398
+ thumbnail: hasThumbnail,
399
+ control: false,
400
+ liveStream: true,
401
+ computerAgent: true,
402
+ taskDispatch: true,
403
+ aiAssist: hasAiAssist,
404
+ aiModel: hasAiAssist ? 'synthetic-ai' : '',
405
+ aiProvider: hasAiAssist ? 'synthetic' : ''
406
+ },
407
+ connected: isConnected,
408
+ connectedAt: isConnected ? connectedAt : '',
409
+ disconnectedAt: isConnected ? '' : seenAt,
410
+ lastSeenAt: seenAt,
411
+ lastStatusAt: seenAt,
412
+ lastDisconnectReason: isConnected ? '' : 'synthetic-offline',
413
+ remoteAddress: 'synthetic.local',
414
+ remotePort: 0,
415
+ status: {
416
+ platform,
417
+ release: platform === 'win32' ? 'Windows 11' : platform === 'darwin' ? 'macOS' : 'Linux',
418
+ uptimeSec: (ordinal * 791) % 1209600,
419
+ usedMemRatio,
420
+ loadavg: [load1, Math.max(0, load1 - 0.2), Math.max(0, load1 - 0.4)]
421
+ },
422
+ latestThumbnail: null,
423
+ latestLiveFrame: null,
424
+ activeLiveStream: null,
425
+ latestTask,
426
+ recentTasks: latestTask ? [latestTask] : [],
427
+ pendingTaskCommands: new Map(),
428
+ counters: createDeviceCounters({
429
+ messagesReceived: 1,
430
+ statusReceived: 1,
431
+ tasksQueued: latestTask ? 1 : 0,
432
+ taskResultsReceived: latestTask && ['completed', 'failed'].includes(latestTask.status) ? 1 : 0,
433
+ taskResultsFailed: latestTask?.status === 'failed' ? 1 : 0
434
+ })
435
+ };
436
+
437
+ if (hasThumbnail) {
438
+ const frame = makeSyntheticFrame(device, `synthetic-thumb-${ordinal}`, 'thumbnail', {
439
+ width: 360,
440
+ height: 220
441
+ });
442
+ delete frame.mode;
443
+ delete frame.fps;
444
+ frame.receivedAt = seenAt;
445
+ frame.capturedAt = seenAt;
446
+ device.latestThumbnail = frame;
447
+ device.counters.thumbnailFramesReceived = 1;
448
+ }
449
+
450
+ if (isLive) {
451
+ device.activeLiveStream = {
452
+ streamId: `synthetic-live-${ordinal}`,
453
+ commandId: `synthetic-live-command-${ordinal}`,
454
+ active: true,
455
+ mode: 'remote-fast',
456
+ fps: 12,
457
+ startedAt: seenAt,
458
+ stoppedAt: '',
459
+ stopReason: '',
460
+ lastFrameAt: '',
461
+ lastFrameSeq: 0,
462
+ framesReceived: 0
463
+ };
464
+ applySyntheticLiveFrame(device, device.activeLiveStream.streamId, {
465
+ commandId: device.activeLiveStream.commandId,
466
+ fps: 12
467
+ });
468
+ live += 1;
469
+ }
470
+
471
+ if (isConnected) {
472
+ connected += 1;
473
+ }
474
+ if (hasAiAssist) {
475
+ aiAssist += 1;
476
+ }
477
+ devices.set(deviceId, device);
478
+ }
479
+
480
+ emitRemoteEvent('RemoteSyntheticFleetSeeded', null, {
481
+ count,
482
+ connected,
483
+ aiAssist,
484
+ live
485
+ });
486
+ return {
487
+ ok: true,
488
+ synthetic: true,
489
+ seeded: count,
490
+ connected,
491
+ aiAssist,
492
+ live,
493
+ total: devices.size,
494
+ max: MAX_SYNTHETIC_DEVICES
495
+ };
496
+ }
497
+
225
498
  function attachDevice(socket, hello) {
226
499
  const now = new Date().toISOString();
227
500
  const sessionId = crypto.randomUUID();
@@ -257,21 +530,8 @@ export function createRemoteHub(options = {}) {
257
530
  latestTask: null,
258
531
  recentTasks: [],
259
532
  pendingTaskCommands: new Map(),
260
- counters: {
261
- messagesReceived: 1,
262
- statusReceived: 0,
263
- commandsSent: 0,
264
- commandResultsReceived: 0,
265
- thumbnailFramesReceived: 0,
266
- thumbnailFramesDropped: 0,
267
- liveFramesReceived: 0,
268
- liveFramesDropped: 0,
269
- liveStreamsStarted: 0,
270
- liveStreamsStopped: 0,
271
- tasksQueued: 0,
272
- taskResultsReceived: 0,
273
- taskResultsFailed: 0
274
- }
533
+ synthetic: false,
534
+ counters: createDeviceCounters({ messagesReceived: 1 })
275
535
  };
276
536
 
277
537
  devices.set(deviceId, device);
@@ -685,6 +945,19 @@ export function createRemoteHub(options = {}) {
685
945
 
686
946
  function disconnectDevice(deviceId, reason = 'manager-disconnect') {
687
947
  const device = devices.get(String(deviceId || ''));
948
+ if (device?.synthetic === true) {
949
+ device.connected = false;
950
+ device.disconnectedAt = new Date().toISOString();
951
+ device.lastDisconnectReason = reason;
952
+ if (device.activeLiveStream) {
953
+ device.activeLiveStream.active = false;
954
+ device.activeLiveStream.stoppedAt = device.disconnectedAt;
955
+ device.activeLiveStream.stopReason = reason;
956
+ }
957
+ emitRemoteEvent('RemoteDeviceDisconnected', device, { reason, synthetic: true });
958
+ return true;
959
+ }
960
+
688
961
  if (!device?.socket || device.socket.destroyed) {
689
962
  return false;
690
963
  }
@@ -696,6 +969,29 @@ export function createRemoteHub(options = {}) {
696
969
 
697
970
  function sendCommand(deviceId, command) {
698
971
  const device = devices.get(String(deviceId || ''));
972
+ if (device?.synthetic === true && device.connected) {
973
+ const commandId = safeString(command?.commandId, 128) || crypto.randomUUID();
974
+ device.counters.commandsSent += 1;
975
+ device.lastSeenAt = new Date().toISOString();
976
+ if (command?.command === 'thumbnail.capture') {
977
+ applySyntheticThumbnail(device, {
978
+ commandId,
979
+ payload: command?.payload || {}
980
+ });
981
+ }
982
+ emitRemoteEvent('RemoteCommandQueued', device, {
983
+ commandId,
984
+ command: safeString(command?.command || 'ping', 80),
985
+ synthetic: true
986
+ });
987
+ emitRemoteEvent('RemoteCommandResult', device, {
988
+ commandId,
989
+ result: { ok: true, synthetic: true },
990
+ error: ''
991
+ });
992
+ return { ok: true, commandId, synthetic: true };
993
+ }
994
+
699
995
  if (!device?.socket || device.socket.destroyed || !device.connected) {
700
996
  return { ok: false, error: 'device-not-connected' };
701
997
  }
@@ -720,6 +1016,65 @@ export function createRemoteHub(options = {}) {
720
1016
 
721
1017
  function requestAgentTask(deviceId, options = {}) {
722
1018
  const device = devices.get(String(deviceId || ''));
1019
+ if (device?.synthetic === true && device.connected) {
1020
+ const instruction = safeText(options.instruction, MAX_AGENT_TASK_CHARS);
1021
+ if (!instruction) {
1022
+ return { ok: false, error: 'missing-instruction' };
1023
+ }
1024
+
1025
+ const now = new Date().toISOString();
1026
+ const approvalLevel = normalizeApprovalLevel(options.approvalLevel);
1027
+ if (approvalLevel === 'ai-assist' && !readCapabilityFlag(device.capabilities, 'aiAssist')) {
1028
+ return { ok: false, error: 'device-ai-assist-unavailable' };
1029
+ }
1030
+
1031
+ const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
1032
+ const taskId = safeString(options.taskId, 128) || crypto.randomUUID();
1033
+ const title = safeString(options.title, 120)
1034
+ || safeString(instruction.split(/\r?\n/)[0], 120)
1035
+ || 'Remote task';
1036
+ const task = {
1037
+ taskId,
1038
+ commandId,
1039
+ title,
1040
+ instructionPreview: safeText(instruction, 320),
1041
+ status: 'completed',
1042
+ approvalLevel,
1043
+ requestedAt: now,
1044
+ sentAt: now,
1045
+ updatedAt: now,
1046
+ completedAt: now,
1047
+ error: '',
1048
+ resultKind: approvalLevel === 'ai-assist' ? 'synthetic-ai-assist' : 'synthetic-agent-task',
1049
+ resultSummary: approvalLevel === 'ai-assist'
1050
+ ? `Synthetic AI assist completed for ${device.deviceName}.`
1051
+ : `Synthetic task accepted by ${device.deviceName}.`
1052
+ };
1053
+
1054
+ device.counters.commandsSent += 1;
1055
+ device.counters.commandResultsReceived += 1;
1056
+ device.counters.tasksQueued += 1;
1057
+ device.counters.taskResultsReceived += 1;
1058
+ device.lastSeenAt = now;
1059
+ rememberDeviceTask(device, task);
1060
+ device.pendingTaskCommands.delete(commandId);
1061
+ emitRemoteEvent('RemoteTaskQueued', device, {
1062
+ commandId,
1063
+ taskId,
1064
+ title,
1065
+ approvalLevel,
1066
+ synthetic: true
1067
+ });
1068
+ emitRemoteEvent('RemoteTaskResult', device, {
1069
+ commandId,
1070
+ taskId,
1071
+ status: task.status,
1072
+ error: '',
1073
+ synthetic: true
1074
+ });
1075
+ return { ok: true, commandId, taskId, approvalLevel, synthetic: true };
1076
+ }
1077
+
723
1078
  if (!device?.socket || device.socket.destroyed || !device.connected) {
724
1079
  return { ok: false, error: 'device-not-connected' };
725
1080
  }
@@ -803,6 +1158,45 @@ export function createRemoteHub(options = {}) {
803
1158
 
804
1159
  function startLiveStream(deviceId, options = {}) {
805
1160
  const device = devices.get(String(deviceId || ''));
1161
+ if (device?.synthetic === true && device.connected) {
1162
+ if (!readCapabilityFlag(device.capabilities, 'liveStream')) {
1163
+ return { ok: false, error: 'device-live-stream-unavailable' };
1164
+ }
1165
+
1166
+ const now = new Date().toISOString();
1167
+ const streamId = safeString(options.streamId, 128) || `live-${Date.now()}`;
1168
+ const fps = clampNumber(options.fps, 1, 24, 12);
1169
+ const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
1170
+ device.counters.commandsSent += 1;
1171
+ device.activeLiveStream = {
1172
+ streamId,
1173
+ commandId,
1174
+ active: true,
1175
+ mode: 'remote-fast',
1176
+ fps,
1177
+ startedAt: now,
1178
+ stoppedAt: '',
1179
+ stopReason: '',
1180
+ lastFrameAt: '',
1181
+ lastFrameSeq: 0,
1182
+ framesReceived: 0
1183
+ };
1184
+ device.counters.liveStreamsStarted += 1;
1185
+ applySyntheticLiveFrame(device, streamId, {
1186
+ commandId,
1187
+ maxWidth: options.maxWidth,
1188
+ maxHeight: options.maxHeight,
1189
+ fps
1190
+ });
1191
+ emitRemoteEvent('RemoteLiveStreamStarted', device, {
1192
+ streamId,
1193
+ commandId,
1194
+ fps,
1195
+ synthetic: true
1196
+ });
1197
+ return { ok: true, commandId, streamId, fps, synthetic: true };
1198
+ }
1199
+
806
1200
  if (!device?.socket || device.socket.destroyed || !device.connected) {
807
1201
  return { ok: false, error: 'device-not-connected' };
808
1202
  }
@@ -857,6 +1251,24 @@ export function createRemoteHub(options = {}) {
857
1251
 
858
1252
  function stopLiveStream(deviceId, options = {}) {
859
1253
  const device = devices.get(String(deviceId || ''));
1254
+ if (device?.synthetic === true && device.connected) {
1255
+ const streamId = safeString(options.streamId, 128) || device.activeLiveStream?.streamId || '';
1256
+ const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
1257
+ device.counters.commandsSent += 1;
1258
+ if (device.activeLiveStream && (!streamId || device.activeLiveStream.streamId === streamId)) {
1259
+ device.activeLiveStream.active = false;
1260
+ device.activeLiveStream.stoppedAt = new Date().toISOString();
1261
+ device.activeLiveStream.stopReason = 'manager-request';
1262
+ }
1263
+ device.counters.liveStreamsStopped += 1;
1264
+ emitRemoteEvent('RemoteLiveStreamStopped', device, {
1265
+ streamId,
1266
+ commandId,
1267
+ synthetic: true
1268
+ });
1269
+ return { ok: true, commandId, streamId, synthetic: true };
1270
+ }
1271
+
860
1272
  if (!device?.socket || device.socket.destroyed || !device.connected) {
861
1273
  return { ok: false, error: 'device-not-connected' };
862
1274
  }
@@ -913,6 +1325,8 @@ export function createRemoteHub(options = {}) {
913
1325
  stopLiveStream,
914
1326
  getDeviceLiveFrame,
915
1327
  getDeviceThumbnail,
1328
+ seedSyntheticFleet,
1329
+ clearSyntheticFleet,
916
1330
  getPairToken: () => pairToken
917
1331
  };
918
1332
  }
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+
3
+ import assert from 'assert/strict';
4
+ import { createRemoteHub } from '../remote-hub.js';
5
+
6
+ const SYNTHETIC_COUNT = Number(process.env.REMOTE_HUB_SCALE_SMOKE_COUNT || 250);
7
+
8
+ const hub = createRemoteHub({
9
+ env: {
10
+ MINDEXEC_REMOTE_HUB: '1',
11
+ REMOTE_HUB_HOST: '127.0.0.1',
12
+ REMOTE_HUB_PORT: '0',
13
+ REMOTE_HUB_PAIR_TOKEN: 'scale-smoke-token'
14
+ }
15
+ });
16
+
17
+ try {
18
+ await hub.start();
19
+
20
+ const seeded = hub.seedSyntheticFleet({
21
+ count: SYNTHETIC_COUNT,
22
+ connectedRatio: 0.84,
23
+ thumbnailRatio: 0.72,
24
+ aiAssistRatio: 0.42,
25
+ liveCount: 8
26
+ });
27
+ assert.equal(seeded.ok, true);
28
+ assert.equal(seeded.seeded, SYNTHETIC_COUNT);
29
+
30
+ const status = hub.getStatus();
31
+ assert.equal(status.started, true);
32
+ assert.equal(status.canvasPagination, 'none');
33
+ assert.equal(status.deviceCount, SYNTHETIC_COUNT);
34
+ assert.equal(status.connectedDeviceCount, seeded.connected);
35
+
36
+ const devices = hub.listDevices();
37
+ assert.equal(devices.length, SYNTHETIC_COUNT);
38
+ assert.equal(devices.filter(device => device.synthetic === true).length, SYNTHETIC_COUNT);
39
+ assert.equal(devices.some(device => device.connected === false), true);
40
+ assert.equal(devices.some(device => device.latestThumbnail?.dataUrl), true);
41
+ assert.equal(devices.some(device => device.activeLiveStream?.active === true), true);
42
+
43
+ const deviceListPayload = {
44
+ total: devices.length,
45
+ pagination: 'none',
46
+ canvasDeviceListMode: 'all-devices',
47
+ devices
48
+ };
49
+ assert.ok(JSON.stringify(deviceListPayload).length < 8 * 1024 * 1024);
50
+
51
+ const thumbnailTarget = devices.find(device => device.connected && device.capabilities?.thumbnail);
52
+ assert.ok(thumbnailTarget);
53
+ const thumbnailResult = hub.requestThumbnail(thumbnailTarget.deviceId, {
54
+ streamId: 'scale-thumb',
55
+ maxWidth: 360,
56
+ maxHeight: 220,
57
+ quality: 50
58
+ });
59
+ assert.equal(thumbnailResult.ok, true);
60
+ assert.equal(hub.getDeviceThumbnail(thumbnailTarget.deviceId).streamId, 'scale-thumb');
61
+
62
+ const liveTarget = devices.find(device => device.connected && device.capabilities?.liveStream);
63
+ assert.ok(liveTarget);
64
+ const liveResult = hub.startLiveStream(liveTarget.deviceId, {
65
+ streamId: 'scale-live',
66
+ fps: 12,
67
+ maxWidth: 960,
68
+ maxHeight: 540,
69
+ quality: 60
70
+ });
71
+ assert.equal(liveResult.ok, true);
72
+ assert.equal(hub.getDeviceLiveFrame(liveTarget.deviceId).streamId, 'scale-live');
73
+ const stopResult = hub.stopLiveStream(liveTarget.deviceId, { streamId: 'scale-live' });
74
+ assert.equal(stopResult.ok, true);
75
+
76
+ const connectedTargets = hub.listDevices()
77
+ .filter(device => device.connected && device.capabilities?.taskDispatch)
78
+ .slice(0, 200);
79
+ assert.ok(connectedTargets.length >= 100);
80
+ const taskResults = connectedTargets.map(device =>
81
+ hub.requestAgentTask(device.deviceId, {
82
+ instruction: 'Synthetic scale task: report current status to the manager.',
83
+ title: 'Scale task'
84
+ }));
85
+ assert.equal(taskResults.every(result => result.ok === true), true);
86
+ assert.equal(taskResults.length, connectedTargets.length);
87
+
88
+ const aiTarget = hub.listDevices().find(device =>
89
+ device.connected && device.capabilities?.taskDispatch && device.capabilities?.aiAssist);
90
+ assert.ok(aiTarget);
91
+ const aiResult = hub.requestAgentTask(aiTarget.deviceId, {
92
+ instruction: 'Synthetic AI assist: summarize fleet condition.',
93
+ title: 'Scale AI task',
94
+ approvalLevel: 'ai-assist'
95
+ });
96
+ assert.equal(aiResult.ok, true);
97
+ assert.equal(aiResult.approvalLevel, 'ai-assist');
98
+ assert.equal(hub.listDevices().find(device => device.deviceId === aiTarget.deviceId)
99
+ ?.latestTask?.approvalLevel, 'ai-assist');
100
+
101
+ const cleared = hub.clearSyntheticFleet();
102
+ assert.equal(cleared.ok, true);
103
+ assert.equal(cleared.removed, SYNTHETIC_COUNT);
104
+ assert.equal(hub.listDevices().length, 0);
105
+
106
+ console.log(`RemoteHub scale smoke OK (${SYNTHETIC_COUNT} synthetic devices)`);
107
+ } finally {
108
+ await hub.close();
109
+ }
package/server.js CHANGED
@@ -6973,6 +6973,38 @@ app.get('/api/remote/devices', (req, res) => {
6973
6973
  });
6974
6974
  });
6975
6975
 
6976
+ function isSyntheticRemoteFleetEnabled() {
6977
+ return /^(1|true|yes|on)$/i.test(String(
6978
+ process.env.MINDEXEC_REMOTE_SYNTHETIC_FLEET
6979
+ || process.env.REMOTE_HUB_SYNTHETIC_FLEET
6980
+ || ''));
6981
+ }
6982
+
6983
+ app.post('/api/remote/synthetic/seed', (req, res) => {
6984
+ if (!isSyntheticRemoteFleetEnabled()) {
6985
+ res.status(403).json({ ok: false, error: 'synthetic-fleet-disabled' });
6986
+ return;
6987
+ }
6988
+
6989
+ res.json(remoteHub.seedSyntheticFleet({
6990
+ count: req.body?.count,
6991
+ connectedRatio: req.body?.connectedRatio,
6992
+ thumbnailRatio: req.body?.thumbnailRatio,
6993
+ aiAssistRatio: req.body?.aiAssistRatio,
6994
+ liveCount: req.body?.liveCount,
6995
+ replace: req.body?.replace
6996
+ }));
6997
+ });
6998
+
6999
+ app.delete('/api/remote/synthetic', (req, res) => {
7000
+ if (!isSyntheticRemoteFleetEnabled()) {
7001
+ res.status(403).json({ ok: false, error: 'synthetic-fleet-disabled' });
7002
+ return;
7003
+ }
7004
+
7005
+ res.json(remoteHub.clearSyntheticFleet());
7006
+ });
7007
+
6976
7008
  app.post('/api/remote/devices/:deviceId/disconnect', (req, res) => {
6977
7009
  const disconnected = remoteHub.disconnectDevice(req.params.deviceId, 'manager-request');
6978
7010
  res.json({ ok: disconnected });