@mindexec/cli 0.2.39 → 0.2.41
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 +1 -1
- package/remote-hub.js +210 -103
- package/scripts/remote-http-smoke.mjs +4 -1
- package/scripts/remote-hub-smoke.mjs +60 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +1 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +20 -12
- package/wwwroot/index.html +1 -1
- package/wwwroot/service-worker-assets.js +4 -4
- package/wwwroot/service-worker.js +1 -1
package/package.json
CHANGED
package/remote-hub.js
CHANGED
|
@@ -9,6 +9,8 @@ const DEFAULT_AGENT_TASK_TIMEOUT_MS = 120000;
|
|
|
9
9
|
const MAX_LINE_CHARS = 4 * 1024 * 1024;
|
|
10
10
|
const MAX_THUMBNAIL_BASE64_CHARS = 3 * 1024 * 1024;
|
|
11
11
|
const MAX_STREAM_BASE64_CHARS = 3 * 1024 * 1024;
|
|
12
|
+
const MAX_THUMBNAIL_BINARY_BYTES = Math.floor(MAX_THUMBNAIL_BASE64_CHARS * 3 / 4);
|
|
13
|
+
const MAX_STREAM_BINARY_BYTES = Math.floor(MAX_STREAM_BASE64_CHARS * 3 / 4);
|
|
12
14
|
const MAX_AGENT_TASK_CHARS = 4000;
|
|
13
15
|
const MAX_AGENT_TASK_RESULT_CHARS = 3000;
|
|
14
16
|
const RECENT_TASK_LIMIT = 12;
|
|
@@ -1075,6 +1077,161 @@ export function createRemoteHub(options = {}) {
|
|
|
1075
1077
|
return task;
|
|
1076
1078
|
}
|
|
1077
1079
|
|
|
1080
|
+
function normalizeFrameByteLength(framePayload, frameData = '') {
|
|
1081
|
+
if (Buffer.isBuffer(framePayload)) {
|
|
1082
|
+
return framePayload.length;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return Math.floor(String(frameData || '').length * 3 / 4);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function buildFrameDataUrl(framePayload, frameData, mimeType) {
|
|
1089
|
+
if (Buffer.isBuffer(framePayload)) {
|
|
1090
|
+
return `data:${mimeType};base64,${framePayload.toString('base64')}`;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
return String(frameData || '').startsWith('data:')
|
|
1094
|
+
? String(frameData || '')
|
|
1095
|
+
: `data:${mimeType};base64,${frameData}`;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function applyThumbnailFrame(device, message, framePayload, transport = 'json-base64') {
|
|
1099
|
+
const frameData = Buffer.isBuffer(framePayload)
|
|
1100
|
+
? ''
|
|
1101
|
+
: safeString(framePayload, MAX_THUMBNAIL_BASE64_CHARS + 1);
|
|
1102
|
+
const frameSeq = Number(message.frameSeq);
|
|
1103
|
+
const byteLength = normalizeFrameByteLength(framePayload, frameData);
|
|
1104
|
+
if ((!Buffer.isBuffer(framePayload) && !frameData)
|
|
1105
|
+
|| byteLength > MAX_THUMBNAIL_BINARY_BYTES
|
|
1106
|
+
|| !Number.isFinite(frameSeq)) {
|
|
1107
|
+
device.counters.thumbnailFramesDropped += 1;
|
|
1108
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1109
|
+
reason: 'invalid-thumbnail-frame',
|
|
1110
|
+
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
1111
|
+
transport
|
|
1112
|
+
});
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
1117
|
+
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
1118
|
+
device.latestThumbnail = {
|
|
1119
|
+
streamId: safeString(message.streamId, 128) || 'thumbnail',
|
|
1120
|
+
frameSeq,
|
|
1121
|
+
commandId: safeString(message.commandId, 128),
|
|
1122
|
+
width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
|
|
1123
|
+
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
1124
|
+
mimeType,
|
|
1125
|
+
format: mimeType,
|
|
1126
|
+
capturedAt,
|
|
1127
|
+
receivedAt: device.lastSeenAt,
|
|
1128
|
+
byteLength,
|
|
1129
|
+
transport,
|
|
1130
|
+
dataUrl: buildFrameDataUrl(framePayload, frameData, mimeType)
|
|
1131
|
+
};
|
|
1132
|
+
device.counters.thumbnailFramesReceived += 1;
|
|
1133
|
+
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
1134
|
+
streamId: device.latestThumbnail.streamId,
|
|
1135
|
+
frameSeq,
|
|
1136
|
+
width: device.latestThumbnail.width,
|
|
1137
|
+
height: device.latestThumbnail.height,
|
|
1138
|
+
transport
|
|
1139
|
+
});
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function applyLiveFrame(device, message, framePayload, transport = 'json-base64') {
|
|
1144
|
+
const frameData = Buffer.isBuffer(framePayload)
|
|
1145
|
+
? ''
|
|
1146
|
+
: safeString(framePayload, MAX_STREAM_BASE64_CHARS + 1);
|
|
1147
|
+
const frameSeq = Number(message.frameSeq);
|
|
1148
|
+
const streamId = safeString(message.streamId, 128) || 'live';
|
|
1149
|
+
if (!device.activeLiveStream?.active || device.activeLiveStream.streamId !== streamId) {
|
|
1150
|
+
device.counters.liveFramesDropped += 1;
|
|
1151
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1152
|
+
reason: 'stale-live-stream-frame',
|
|
1153
|
+
streamId,
|
|
1154
|
+
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
1155
|
+
transport
|
|
1156
|
+
});
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const byteLength = normalizeFrameByteLength(framePayload, frameData);
|
|
1161
|
+
if ((!Buffer.isBuffer(framePayload) && !frameData)
|
|
1162
|
+
|| byteLength > MAX_STREAM_BINARY_BYTES
|
|
1163
|
+
|| !Number.isFinite(frameSeq)) {
|
|
1164
|
+
device.counters.liveFramesDropped += 1;
|
|
1165
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1166
|
+
reason: 'invalid-live-frame',
|
|
1167
|
+
streamId,
|
|
1168
|
+
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
1169
|
+
transport
|
|
1170
|
+
});
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
1175
|
+
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
1176
|
+
device.latestLiveFrame = {
|
|
1177
|
+
streamId,
|
|
1178
|
+
frameSeq,
|
|
1179
|
+
commandId: safeString(message.commandId, 128),
|
|
1180
|
+
width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
|
|
1181
|
+
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
1182
|
+
mimeType,
|
|
1183
|
+
format: mimeType,
|
|
1184
|
+
mode: safeString(message.mode || device.activeLiveStream.mode || 'remote-fast', 80),
|
|
1185
|
+
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) : device.activeLiveStream.fps,
|
|
1186
|
+
capturedAt,
|
|
1187
|
+
receivedAt: device.lastSeenAt,
|
|
1188
|
+
byteLength,
|
|
1189
|
+
transport,
|
|
1190
|
+
dataUrl: buildFrameDataUrl(framePayload, frameData, mimeType)
|
|
1191
|
+
};
|
|
1192
|
+
device.activeLiveStream.lastFrameAt = device.lastSeenAt;
|
|
1193
|
+
device.activeLiveStream.lastFrameSeq = frameSeq;
|
|
1194
|
+
device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
|
|
1195
|
+
device.counters.liveFramesReceived += 1;
|
|
1196
|
+
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
1197
|
+
streamId,
|
|
1198
|
+
frameSeq,
|
|
1199
|
+
width: device.latestLiveFrame.width,
|
|
1200
|
+
height: device.latestLiveFrame.height,
|
|
1201
|
+
mode: device.latestLiveFrame.mode,
|
|
1202
|
+
transport
|
|
1203
|
+
});
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function handleAgentBinaryFrame(socket, state, header, framePayload) {
|
|
1208
|
+
if (!state.authenticated || !state.device) {
|
|
1209
|
+
writeJsonLine(socket, { type: 'error', error: 'hello-required' });
|
|
1210
|
+
socket.destroy();
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const device = state.device;
|
|
1215
|
+
device.counters.messagesReceived += 1;
|
|
1216
|
+
device.lastSeenAt = new Date().toISOString();
|
|
1217
|
+
|
|
1218
|
+
const frameKind = safeString(header.frameKind || header.kind || header.frameType || '', 40).toLowerCase();
|
|
1219
|
+
if (frameKind === 'thumbnail' || header.type === 'thumbnail.binary') {
|
|
1220
|
+
applyThumbnailFrame(device, header, framePayload, 'binary');
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (frameKind === 'stream' || frameKind === 'live' || header.type === 'stream.binary') {
|
|
1225
|
+
applyLiveFrame(device, header, framePayload, 'binary');
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
emitRemoteEvent('RemoteAgentMessageIgnored', device, {
|
|
1230
|
+
messageType: safeString(header.type, 80),
|
|
1231
|
+
frameKind
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1078
1235
|
function handleAgentMessage(socket, state, message) {
|
|
1079
1236
|
if (!message || typeof message !== 'object') {
|
|
1080
1237
|
return;
|
|
@@ -1139,97 +1296,11 @@ export function createRemoteHub(options = {}) {
|
|
|
1139
1296
|
});
|
|
1140
1297
|
break;
|
|
1141
1298
|
case 'thumbnail.frame': {
|
|
1142
|
-
|
|
1143
|
-
const frameSeq = Number(message.frameSeq);
|
|
1144
|
-
if (!frameData || frameData.length > MAX_THUMBNAIL_BASE64_CHARS || !Number.isFinite(frameSeq)) {
|
|
1145
|
-
device.counters.thumbnailFramesDropped += 1;
|
|
1146
|
-
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1147
|
-
reason: 'invalid-thumbnail-frame',
|
|
1148
|
-
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null
|
|
1149
|
-
});
|
|
1150
|
-
break;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
1154
|
-
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
1155
|
-
device.latestThumbnail = {
|
|
1156
|
-
streamId: safeString(message.streamId, 128) || 'thumbnail',
|
|
1157
|
-
frameSeq,
|
|
1158
|
-
commandId: safeString(message.commandId, 128),
|
|
1159
|
-
width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
|
|
1160
|
-
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
1161
|
-
mimeType,
|
|
1162
|
-
format: mimeType,
|
|
1163
|
-
capturedAt,
|
|
1164
|
-
receivedAt: device.lastSeenAt,
|
|
1165
|
-
byteLength: Math.floor(frameData.length * 3 / 4),
|
|
1166
|
-
dataUrl: frameData.startsWith('data:')
|
|
1167
|
-
? frameData
|
|
1168
|
-
: `data:${mimeType};base64,${frameData}`
|
|
1169
|
-
};
|
|
1170
|
-
device.counters.thumbnailFramesReceived += 1;
|
|
1171
|
-
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
1172
|
-
streamId: device.latestThumbnail.streamId,
|
|
1173
|
-
frameSeq,
|
|
1174
|
-
width: device.latestThumbnail.width,
|
|
1175
|
-
height: device.latestThumbnail.height
|
|
1176
|
-
});
|
|
1299
|
+
applyThumbnailFrame(device, message, message.data, 'json-base64');
|
|
1177
1300
|
break;
|
|
1178
1301
|
}
|
|
1179
1302
|
case 'stream.frame': {
|
|
1180
|
-
|
|
1181
|
-
const frameSeq = Number(message.frameSeq);
|
|
1182
|
-
const streamId = safeString(message.streamId, 128) || 'live';
|
|
1183
|
-
if (!device.activeLiveStream?.active || device.activeLiveStream.streamId !== streamId) {
|
|
1184
|
-
device.counters.liveFramesDropped += 1;
|
|
1185
|
-
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1186
|
-
reason: 'stale-live-stream-frame',
|
|
1187
|
-
streamId,
|
|
1188
|
-
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null
|
|
1189
|
-
});
|
|
1190
|
-
break;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
if (!frameData || frameData.length > MAX_STREAM_BASE64_CHARS || !Number.isFinite(frameSeq)) {
|
|
1194
|
-
device.counters.liveFramesDropped += 1;
|
|
1195
|
-
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1196
|
-
reason: 'invalid-live-frame',
|
|
1197
|
-
streamId,
|
|
1198
|
-
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null
|
|
1199
|
-
});
|
|
1200
|
-
break;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
1204
|
-
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
1205
|
-
device.latestLiveFrame = {
|
|
1206
|
-
streamId,
|
|
1207
|
-
frameSeq,
|
|
1208
|
-
commandId: safeString(message.commandId, 128),
|
|
1209
|
-
width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
|
|
1210
|
-
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
1211
|
-
mimeType,
|
|
1212
|
-
format: mimeType,
|
|
1213
|
-
mode: safeString(message.mode || device.activeLiveStream.mode || 'remote-fast', 80),
|
|
1214
|
-
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) : device.activeLiveStream.fps,
|
|
1215
|
-
capturedAt,
|
|
1216
|
-
receivedAt: device.lastSeenAt,
|
|
1217
|
-
byteLength: Math.floor(frameData.length * 3 / 4),
|
|
1218
|
-
dataUrl: frameData.startsWith('data:')
|
|
1219
|
-
? frameData
|
|
1220
|
-
: `data:${mimeType};base64,${frameData}`
|
|
1221
|
-
};
|
|
1222
|
-
device.activeLiveStream.lastFrameAt = device.lastSeenAt;
|
|
1223
|
-
device.activeLiveStream.lastFrameSeq = frameSeq;
|
|
1224
|
-
device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
|
|
1225
|
-
device.counters.liveFramesReceived += 1;
|
|
1226
|
-
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
1227
|
-
streamId,
|
|
1228
|
-
frameSeq,
|
|
1229
|
-
width: device.latestLiveFrame.width,
|
|
1230
|
-
height: device.latestLiveFrame.height,
|
|
1231
|
-
mode: device.latestLiveFrame.mode
|
|
1232
|
-
});
|
|
1303
|
+
applyLiveFrame(device, message, message.data, 'json-base64');
|
|
1233
1304
|
break;
|
|
1234
1305
|
}
|
|
1235
1306
|
default:
|
|
@@ -1242,14 +1313,14 @@ export function createRemoteHub(options = {}) {
|
|
|
1242
1313
|
|
|
1243
1314
|
function handleSocket(socket) {
|
|
1244
1315
|
allSockets.add(socket);
|
|
1245
|
-
socket.setEncoding('utf8');
|
|
1246
1316
|
socket.setNoDelay(true);
|
|
1247
1317
|
socket.setKeepAlive(true, heartbeatMs);
|
|
1248
1318
|
|
|
1249
1319
|
const state = {
|
|
1250
1320
|
authenticated: false,
|
|
1251
1321
|
device: null,
|
|
1252
|
-
buffer:
|
|
1322
|
+
buffer: Buffer.alloc(0),
|
|
1323
|
+
pendingBinaryFrame: null
|
|
1253
1324
|
};
|
|
1254
1325
|
|
|
1255
1326
|
const helloTimer = setTimeout(() => {
|
|
@@ -1260,27 +1331,63 @@ export function createRemoteHub(options = {}) {
|
|
|
1260
1331
|
}, 10000);
|
|
1261
1332
|
|
|
1262
1333
|
socket.on('data', chunk => {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1334
|
+
const incoming = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1335
|
+
state.buffer = state.buffer.length > 0
|
|
1336
|
+
? Buffer.concat([state.buffer, incoming])
|
|
1337
|
+
: incoming;
|
|
1338
|
+
|
|
1339
|
+
while (!socket.destroyed) {
|
|
1340
|
+
if (state.pendingBinaryFrame) {
|
|
1341
|
+
const { header, byteLength } = state.pendingBinaryFrame;
|
|
1342
|
+
if (state.buffer.length < byteLength) {
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
const framePayload = state.buffer.subarray(0, byteLength);
|
|
1347
|
+
state.buffer = state.buffer.subarray(byteLength);
|
|
1348
|
+
state.pendingBinaryFrame = null;
|
|
1349
|
+
handleAgentBinaryFrame(socket, state, header, framePayload);
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
const newlineIndex = state.buffer.indexOf(0x0a);
|
|
1354
|
+
if (newlineIndex < 0) {
|
|
1355
|
+
if (state.buffer.length > MAX_LINE_CHARS) {
|
|
1356
|
+
writeJsonLine(socket, { type: 'error', error: 'message-too-large' });
|
|
1357
|
+
socket.destroy();
|
|
1358
|
+
}
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1269
1361
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const line = state.buffer.slice(0, newlineIndex);
|
|
1273
|
-
state.buffer = state.buffer.slice(newlineIndex + 1);
|
|
1362
|
+
const lineBuffer = state.buffer.subarray(0, newlineIndex);
|
|
1363
|
+
state.buffer = state.buffer.subarray(newlineIndex + 1);
|
|
1274
1364
|
|
|
1275
1365
|
try {
|
|
1276
|
-
const message = parseJsonLine(
|
|
1366
|
+
const message = parseJsonLine(lineBuffer.toString('utf8'));
|
|
1367
|
+
if (message?.type === 'frame.binary') {
|
|
1368
|
+
const frameKind = safeString(message.frameKind || message.kind || message.frameType, 40).toLowerCase();
|
|
1369
|
+
const maxBytes = frameKind === 'thumbnail'
|
|
1370
|
+
? MAX_THUMBNAIL_BINARY_BYTES
|
|
1371
|
+
: MAX_STREAM_BINARY_BYTES;
|
|
1372
|
+
const byteLength = Number(message.byteLength ?? message.payloadBytes ?? message.dataLength);
|
|
1373
|
+
if (!Number.isFinite(byteLength) || byteLength < 1 || byteLength > maxBytes) {
|
|
1374
|
+
writeJsonLine(socket, { type: 'error', error: 'invalid-binary-frame' });
|
|
1375
|
+
logWarn('remote', 'invalid binary frame header from agent.');
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
state.pendingBinaryFrame = {
|
|
1380
|
+
header: message,
|
|
1381
|
+
byteLength: Math.floor(byteLength)
|
|
1382
|
+
};
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1277
1386
|
handleAgentMessage(socket, state, message);
|
|
1278
1387
|
} catch (err) {
|
|
1279
1388
|
writeJsonLine(socket, { type: 'error', error: 'invalid-json' });
|
|
1280
1389
|
logWarn('remote', `invalid agent message: ${err?.message || err}`);
|
|
1281
1390
|
}
|
|
1282
|
-
|
|
1283
|
-
newlineIndex = state.buffer.indexOf('\n');
|
|
1284
1391
|
}
|
|
1285
1392
|
});
|
|
1286
1393
|
|
|
@@ -5,10 +5,13 @@ import net from 'node:net';
|
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
8
9
|
|
|
9
10
|
const BRIDGE_TOKEN = 'remote-http-smoke-token';
|
|
10
11
|
const PAIR_TOKEN = 'remote-http-pair-token';
|
|
11
12
|
const SYNTHETIC_COUNT = Number(process.env.REMOTE_HTTP_SMOKE_COUNT || 500);
|
|
13
|
+
const LOCAL_BRIDGE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
|
+
const LOCAL_BRIDGE_PACKAGE = JSON.parse(readFileSync(path.join(LOCAL_BRIDGE_DIR, 'package.json'), 'utf8'));
|
|
12
15
|
|
|
13
16
|
function wait(ms) {
|
|
14
17
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
@@ -155,7 +158,7 @@ async function runSyntheticEnabledSmoke() {
|
|
|
155
158
|
assert.equal(status.started, true);
|
|
156
159
|
assert.equal(status.port, remoteHubPort);
|
|
157
160
|
assert.equal(status.managerPackage, '@mindexec/cli');
|
|
158
|
-
assert.equal(status.managerVersion,
|
|
161
|
+
assert.equal(status.managerVersion, LOCAL_BRIDGE_PACKAGE.version);
|
|
159
162
|
assert.equal(status.agentPackage, '@mindexec/remote');
|
|
160
163
|
assert.equal(status.canvasPagination, 'none');
|
|
161
164
|
assert.equal(status.canvasDeviceListMode, 'all-devices');
|
|
@@ -8,6 +8,16 @@ function writeJsonLine(socket, payload) {
|
|
|
8
8
|
socket.write(`${JSON.stringify(payload)}\n`);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function writeBinaryFrame(socket, header, payload) {
|
|
12
|
+
const framePayload = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
13
|
+
socket.write(`${JSON.stringify({
|
|
14
|
+
...header,
|
|
15
|
+
type: 'frame.binary',
|
|
16
|
+
byteLength: framePayload.length
|
|
17
|
+
})}\n`);
|
|
18
|
+
socket.write(framePayload);
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
function wait(ms) {
|
|
12
22
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
13
23
|
}
|
|
@@ -34,6 +44,7 @@ const hub = createRemoteHub({
|
|
|
34
44
|
REMOTE_HUB_TASK_TIMEOUT_MS: '120'
|
|
35
45
|
}
|
|
36
46
|
});
|
|
47
|
+
const smokePngFrame = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC', 'base64');
|
|
37
48
|
|
|
38
49
|
try {
|
|
39
50
|
await hub.start();
|
|
@@ -115,6 +126,33 @@ try {
|
|
|
115
126
|
assert.equal(thumbnailDevice.latestThumbnail.streamId, 'smoke-thumb');
|
|
116
127
|
assert.equal(thumbnailDevice.counters.thumbnailFramesReceived, 1);
|
|
117
128
|
|
|
129
|
+
const binaryThumbnailCommand = hub.requestThumbnail('smoke-device', {
|
|
130
|
+
streamId: 'smoke-thumb-binary',
|
|
131
|
+
maxWidth: 320,
|
|
132
|
+
maxHeight: 180,
|
|
133
|
+
quality: 50
|
|
134
|
+
});
|
|
135
|
+
assert.equal(binaryThumbnailCommand.ok, true);
|
|
136
|
+
writeBinaryFrame(socket, {
|
|
137
|
+
frameKind: 'thumbnail',
|
|
138
|
+
commandId: binaryThumbnailCommand.commandId,
|
|
139
|
+
streamId: 'smoke-thumb-binary',
|
|
140
|
+
frameSeq: 4,
|
|
141
|
+
width: 2,
|
|
142
|
+
height: 1,
|
|
143
|
+
mimeType: 'image/png',
|
|
144
|
+
capturedAt: new Date().toISOString()
|
|
145
|
+
}, smokePngFrame);
|
|
146
|
+
|
|
147
|
+
const binaryThumbnailDevice = await waitFor(() => {
|
|
148
|
+
const current = hub.listDevices();
|
|
149
|
+
return current[0]?.latestThumbnail?.frameSeq === 4 ? current[0] : null;
|
|
150
|
+
});
|
|
151
|
+
assert.equal(binaryThumbnailDevice.latestThumbnail.streamId, 'smoke-thumb-binary');
|
|
152
|
+
assert.equal(binaryThumbnailDevice.latestThumbnail.transport, 'binary');
|
|
153
|
+
assert.equal(binaryThumbnailDevice.latestThumbnail.byteLength, smokePngFrame.length);
|
|
154
|
+
assert.equal(binaryThumbnailDevice.counters.thumbnailFramesReceived, 2);
|
|
155
|
+
|
|
118
156
|
const liveCommand = hub.startLiveStream('smoke-device', {
|
|
119
157
|
streamId: 'smoke-live',
|
|
120
158
|
fps: 5,
|
|
@@ -145,6 +183,28 @@ try {
|
|
|
145
183
|
assert.equal(liveDevice.latestLiveFrame.mode, 'remote-fast');
|
|
146
184
|
assert.equal(liveDevice.counters.liveFramesReceived, 1);
|
|
147
185
|
|
|
186
|
+
writeBinaryFrame(socket, {
|
|
187
|
+
frameKind: 'stream',
|
|
188
|
+
commandId: liveCommand.commandId,
|
|
189
|
+
streamId: 'smoke-live',
|
|
190
|
+
frameSeq: 5,
|
|
191
|
+
width: 2,
|
|
192
|
+
height: 1,
|
|
193
|
+
mimeType: 'image/png',
|
|
194
|
+
mode: 'remote-fast',
|
|
195
|
+
fps: 5,
|
|
196
|
+
capturedAt: new Date().toISOString()
|
|
197
|
+
}, smokePngFrame);
|
|
198
|
+
|
|
199
|
+
const binaryLiveDevice = await waitFor(() => {
|
|
200
|
+
const current = hub.listDevices();
|
|
201
|
+
return current[0]?.latestLiveFrame?.frameSeq === 5 ? current[0] : null;
|
|
202
|
+
});
|
|
203
|
+
assert.equal(binaryLiveDevice.latestLiveFrame.streamId, 'smoke-live');
|
|
204
|
+
assert.equal(binaryLiveDevice.latestLiveFrame.transport, 'binary');
|
|
205
|
+
assert.equal(binaryLiveDevice.latestLiveFrame.byteLength, smokePngFrame.length);
|
|
206
|
+
assert.equal(binaryLiveDevice.counters.liveFramesReceived, 2);
|
|
207
|
+
|
|
148
208
|
const staleFrameBefore = liveDevice.counters.liveFramesDropped;
|
|
149
209
|
writeJsonLine(socket, {
|
|
150
210
|
type: 'stream.frame',
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const DEBUG = false;
|
|
6
6
|
const FPS_DEBUG = false;
|
|
7
7
|
const FRAME_PERF_DEBUG = false;
|
|
8
|
-
const MINDMAP_CORE_BUILD_ID = '20260613-
|
|
8
|
+
const MINDMAP_CORE_BUILD_ID = '20260613-remote-monitor-gap-v214';
|
|
9
9
|
const CanvasPhase = Object.freeze({
|
|
10
10
|
Booting: 'booting',
|
|
11
11
|
BoardFileLoading: 'board-file-loading',
|
|
@@ -3405,6 +3405,9 @@
|
|
|
3405
3405
|
const REMOTE_FLEET_SEMANTIC_TYPE = 'RemoteFleetMonitor';
|
|
3406
3406
|
const REMOTE_FLEET_DEVICE_SEMANTIC_TYPE = 'RemoteFleetDevice';
|
|
3407
3407
|
const REMOTE_FLEET_CENTER_NUDGE_PX = 4;
|
|
3408
|
+
const REMOTE_FLEET_MONITOR_TILE_GAP_PX = 8;
|
|
3409
|
+
const REMOTE_FLEET_EMPTY_SCREEN_MIN_WIDTH = 132;
|
|
3410
|
+
const REMOTE_FLEET_EMPTY_SCREEN_MIN_HEIGHT = 72;
|
|
3408
3411
|
const AUTOMATION_NODE_KIND_METADATA_KEY = 'AutomationNodeKind';
|
|
3409
3412
|
const AUTOMATION_NODE_LABEL_METADATA_KEY = 'AutomationNodeLabel';
|
|
3410
3413
|
const AUTOMATION_NODE_DESCRIPTION_METADATA_KEY = 'AutomationNodeDescription';
|
|
@@ -12475,8 +12478,8 @@
|
|
|
12475
12478
|
const height = Number(nodeModel?.height ?? nodeModel?.Height ?? 620);
|
|
12476
12479
|
const usableWidth = Number.isFinite(width) ? Math.max(360, width - 44) : 916;
|
|
12477
12480
|
const usableHeight = Number.isFinite(height) ? Math.max(260, height - 128) : 492;
|
|
12478
|
-
const columns = Math.max(3, Math.floor(usableWidth /
|
|
12479
|
-
const rows = Math.max(3, Math.ceil(usableHeight /
|
|
12481
|
+
const columns = Math.max(3, Math.floor((usableWidth + REMOTE_FLEET_MONITOR_TILE_GAP_PX) / (REMOTE_FLEET_EMPTY_SCREEN_MIN_WIDTH + REMOTE_FLEET_MONITOR_TILE_GAP_PX)));
|
|
12482
|
+
const rows = Math.max(3, Math.ceil((usableHeight + REMOTE_FLEET_MONITOR_TILE_GAP_PX) / (REMOTE_FLEET_EMPTY_SCREEN_MIN_HEIGHT + REMOTE_FLEET_MONITOR_TILE_GAP_PX)));
|
|
12480
12483
|
return Math.max(12, Math.min(80, columns * rows));
|
|
12481
12484
|
}
|
|
12482
12485
|
|
|
@@ -12486,19 +12489,20 @@
|
|
|
12486
12489
|
shell.style.cssText = `
|
|
12487
12490
|
flex: 1 1 auto;
|
|
12488
12491
|
min-height: 0;
|
|
12489
|
-
overflow:
|
|
12492
|
+
overflow-y: auto;
|
|
12493
|
+
overflow-x: hidden;
|
|
12490
12494
|
display: grid;
|
|
12491
|
-
grid-template-columns: repeat(auto-fill, minmax(
|
|
12492
|
-
grid-auto-rows:
|
|
12495
|
+
grid-template-columns: repeat(auto-fill, minmax(${REMOTE_FLEET_EMPTY_SCREEN_MIN_WIDTH}px, 1fr));
|
|
12496
|
+
grid-auto-rows: max-content;
|
|
12493
12497
|
align-content: start;
|
|
12498
|
+
align-items: start;
|
|
12494
12499
|
justify-content: center;
|
|
12495
12500
|
justify-items: stretch;
|
|
12496
12501
|
align-self: center;
|
|
12497
12502
|
width: calc(100% - ${REMOTE_FLEET_CENTER_NUDGE_PX * 2}px);
|
|
12498
12503
|
max-width: 100%;
|
|
12499
12504
|
transform: translateX(${REMOTE_FLEET_CENTER_NUDGE_PX}px);
|
|
12500
|
-
|
|
12501
|
-
row-gap: 8px;
|
|
12505
|
+
gap: ${REMOTE_FLEET_MONITOR_TILE_GAP_PX}px;
|
|
12502
12506
|
box-sizing: border-box;
|
|
12503
12507
|
padding: 2px 4px 6px 4px;
|
|
12504
12508
|
`;
|
|
@@ -12508,8 +12512,10 @@
|
|
|
12508
12512
|
const screen = document.createElement('div');
|
|
12509
12513
|
screen.dataset.remoteFleetEmptyScreen = 'true';
|
|
12510
12514
|
screen.style.cssText = `
|
|
12515
|
+
width: 100%;
|
|
12516
|
+
height: auto;
|
|
12511
12517
|
aspect-ratio: 16 / 9;
|
|
12512
|
-
min-height:
|
|
12518
|
+
min-height: ${REMOTE_FLEET_EMPTY_SCREEN_MIN_HEIGHT}px;
|
|
12513
12519
|
box-sizing: border-box;
|
|
12514
12520
|
border-radius: 8px;
|
|
12515
12521
|
border: 1px solid rgba(203, 213, 225, 0.72);
|
|
@@ -14287,7 +14293,7 @@
|
|
|
14287
14293
|
|
|
14288
14294
|
const tileMinWidth = densityState === 'dense' ? 104 : 132;
|
|
14289
14295
|
const tileMinHeight = densityState === 'dense' ? 66 : 84;
|
|
14290
|
-
const tileGap =
|
|
14296
|
+
const tileGap = REMOTE_FLEET_MONITOR_TILE_GAP_PX;
|
|
14291
14297
|
|
|
14292
14298
|
const monitorWorkspace = document.createElement('div');
|
|
14293
14299
|
monitorWorkspace.dataset.remoteFleetMonitorWorkspace = 'true';
|
|
@@ -14310,17 +14316,17 @@
|
|
|
14310
14316
|
overflow-x: hidden;
|
|
14311
14317
|
display: grid;
|
|
14312
14318
|
grid-template-columns: repeat(auto-fill, minmax(${tileMinWidth}px, 1fr));
|
|
14313
|
-
grid-auto-rows:
|
|
14319
|
+
grid-auto-rows: max-content;
|
|
14314
14320
|
grid-auto-flow: row;
|
|
14315
14321
|
align-content: start;
|
|
14322
|
+
align-items: start;
|
|
14316
14323
|
justify-content: center;
|
|
14317
14324
|
justify-items: stretch;
|
|
14318
14325
|
justify-self: center;
|
|
14319
14326
|
width: calc(100% - ${REMOTE_FLEET_CENTER_NUDGE_PX * 2}px);
|
|
14320
14327
|
max-width: 100%;
|
|
14321
14328
|
transform: translateX(${REMOTE_FLEET_CENTER_NUDGE_PX}px);
|
|
14322
|
-
|
|
14323
|
-
row-gap: ${tileGap}px;
|
|
14329
|
+
gap: ${tileGap}px;
|
|
14324
14330
|
box-sizing: border-box;
|
|
14325
14331
|
padding: 0 4px;
|
|
14326
14332
|
`;
|
|
@@ -14415,6 +14421,8 @@
|
|
|
14415
14421
|
display: flex;
|
|
14416
14422
|
align-items: stretch;
|
|
14417
14423
|
min-width: 0;
|
|
14424
|
+
width: 100%;
|
|
14425
|
+
height: auto;
|
|
14418
14426
|
min-height: ${tileMinHeight}px;
|
|
14419
14427
|
aspect-ratio: 16 / 9;
|
|
14420
14428
|
box-sizing: border-box;
|
package/wwwroot/index.html
CHANGED
|
@@ -558,7 +558,7 @@
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
const base = '_content/MindExecution.Shared/js/';
|
|
561
|
-
const scriptVersion = '20260613-
|
|
561
|
+
const scriptVersion = '20260613-remote-monitor-gap-v508';
|
|
562
562
|
const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
|
|
563
563
|
console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
|
|
564
564
|
const criticalScripts = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
self.assetsManifest = {
|
|
2
|
-
"version": "
|
|
2
|
+
"version": "P5rJ7sPB",
|
|
3
3
|
"assets": [
|
|
4
4
|
{
|
|
5
5
|
"hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"url": "_content/MindExecution.Shared/js/marked.min.js"
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
|
-
"hash": "sha256-
|
|
81
|
+
"hash": "sha256-tS2NkdGFK7jWKDH5NTbXqmJ+vgtdIuHB7akcpMxvC84=",
|
|
82
82
|
"url": "_content/MindExecution.Shared/js/mind-map-core.js"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
"hash": "sha256-
|
|
89
|
+
"hash": "sha256-jrzXaQSlY2BHJW062PzZuylbAEYe02+uQrMQyJt7FJA=",
|
|
90
90
|
"url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
|
|
91
91
|
},
|
|
92
92
|
{
|
|
@@ -834,7 +834,7 @@
|
|
|
834
834
|
"url": "image-manifest.json"
|
|
835
835
|
},
|
|
836
836
|
{
|
|
837
|
-
"hash": "sha256-
|
|
837
|
+
"hash": "sha256-bjvQNTq7IFWd435tpUl5xUScla/vHBE+JHSWSDH464c=",
|
|
838
838
|
"url": "index.html"
|
|
839
839
|
},
|
|
840
840
|
{
|