@mindexec/cli 0.2.40 → 0.2.42
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/css/mind-map-overrides.css +18 -5
- package/wwwroot/_framework/{MindExecution.Shared.eemv6jbvss.dll → MindExecution.Shared.araen3jk1e.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +3 -3
- package/wwwroot/index.html +2 -2
- package/wwwroot/service-worker-assets.js +6 -6
- 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',
|
|
@@ -2282,10 +2282,16 @@ body.is-panning .mind-map-text-overlay-v2-card.is-passive .mind-map-text-overlay
|
|
|
2282
2282
|
}
|
|
2283
2283
|
|
|
2284
2284
|
.template-card__title-block {
|
|
2285
|
+
display: grid;
|
|
2286
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
2287
|
+
column-gap: 8px;
|
|
2288
|
+
row-gap: 2px;
|
|
2289
|
+
align-items: baseline;
|
|
2285
2290
|
min-width: 0;
|
|
2286
2291
|
}
|
|
2287
2292
|
|
|
2288
2293
|
.template-card__eyebrow {
|
|
2294
|
+
grid-column: 1 / -1;
|
|
2289
2295
|
color: #64748b;
|
|
2290
2296
|
font-size: 10px;
|
|
2291
2297
|
font-weight: 800;
|
|
@@ -2294,20 +2300,27 @@ body.is-panning .mind-map-text-overlay-v2-card.is-passive .mind-map-text-overlay
|
|
|
2294
2300
|
}
|
|
2295
2301
|
|
|
2296
2302
|
.template-card__title {
|
|
2297
|
-
|
|
2303
|
+
min-width: 0;
|
|
2304
|
+
margin-top: 0;
|
|
2298
2305
|
color: #0f172a;
|
|
2299
2306
|
font-size: 20px;
|
|
2300
2307
|
font-weight: 800;
|
|
2301
2308
|
line-height: 1.15;
|
|
2302
|
-
overflow
|
|
2309
|
+
overflow: hidden;
|
|
2310
|
+
text-overflow: ellipsis;
|
|
2311
|
+
white-space: nowrap;
|
|
2303
2312
|
}
|
|
2304
2313
|
|
|
2305
2314
|
.template-card__summary {
|
|
2306
|
-
|
|
2315
|
+
min-width: 0;
|
|
2316
|
+
margin-top: 0;
|
|
2307
2317
|
color: #475569;
|
|
2308
2318
|
font-size: 12px;
|
|
2309
|
-
|
|
2310
|
-
|
|
2319
|
+
font-weight: 700;
|
|
2320
|
+
line-height: 1.2;
|
|
2321
|
+
overflow: hidden;
|
|
2322
|
+
text-overflow: ellipsis;
|
|
2323
|
+
white-space: nowrap;
|
|
2311
2324
|
}
|
|
2312
2325
|
|
|
2313
2326
|
.template-card__prompt {
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"mainAssemblyName": "MindExecution.Web",
|
|
3
3
|
"resources": {
|
|
4
|
-
"hash": "sha256-
|
|
4
|
+
"hash": "sha256-mHCtlUxUtnRLOtm/6e2rao/b9Nu5+xYIG8Cxx8aFLNI=",
|
|
5
5
|
"fingerprinting": {
|
|
6
6
|
"Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
|
|
7
7
|
"Markdig.d1j7v41cl1.dll": "Markdig.dll",
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"MindExecution.Plugins.Directory.y74f55e8x3.dll": "MindExecution.Plugins.Directory.dll",
|
|
132
132
|
"MindExecution.Plugins.PlanMaster.wbclikss3p.dll": "MindExecution.Plugins.PlanMaster.dll",
|
|
133
133
|
"MindExecution.Plugins.YouTube.fwync0c60s.dll": "MindExecution.Plugins.YouTube.dll",
|
|
134
|
-
"MindExecution.Shared.
|
|
134
|
+
"MindExecution.Shared.araen3jk1e.dll": "MindExecution.Shared.dll",
|
|
135
135
|
"MindExecution.Web.3x7btjkiy5.dll": "MindExecution.Web.dll",
|
|
136
136
|
"dotnet.js": "dotnet.js",
|
|
137
137
|
"dotnet.native.qc8g39g30v.js": "dotnet.native.js",
|
|
@@ -282,7 +282,7 @@
|
|
|
282
282
|
"MindExecution.Kernel.z56elxihok.dll": "sha256-STATJelRGcW9SDGgsw6YmQi6tkQje52dy4lyT3QU4qs=",
|
|
283
283
|
"MindExecution.Plugins.Concept.tkxbh2hokn.dll": "sha256-vYcH7WM686ptbay3iIx5k+zChLgpxvgKxpvHGaDv0kY=",
|
|
284
284
|
"MindExecution.Plugins.PlanMaster.wbclikss3p.dll": "sha256-rBPdSa0fGB1Y73RXymqsLzyGXwfr5BQRFZS8wyAmiSw=",
|
|
285
|
-
"MindExecution.Shared.
|
|
285
|
+
"MindExecution.Shared.araen3jk1e.dll": "sha256-9sBLG9hGRCI2aJVwzd1tbHfl+LMk3hc9oVp5EubnQdo=",
|
|
286
286
|
"MindExecution.Web.3x7btjkiy5.dll": "sha256-1yXNI95k4vlq26Y8YlgIi69ufjSYk2OffuR6A864dUQ="
|
|
287
287
|
},
|
|
288
288
|
"lazyAssembly": {
|
package/wwwroot/index.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<title>MindExec | Run your ideas as AI task graphs</title>
|
|
8
8
|
<meta name="description" content="MindExec is an AI execution canvas for solo builders, researchers, developers, and creators. Start with free browser tools, then move serious work into saved MindCanvas projects." />
|
|
9
9
|
<base href="/" />
|
|
10
|
-
<link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=
|
|
11
|
-
<link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=
|
|
10
|
+
<link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260613-template-card-inline-summary-v461" />
|
|
11
|
+
<link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260613-template-card-inline-summary-v461" />
|
|
12
12
|
<!-- ?쇄뼹??Font Awesome (local) ?쇄뼹??-->
|
|
13
13
|
<link rel="stylesheet" href="_content/MindExecution.Shared/lib/font-awesome/css/all.min.css" />
|
|
14
14
|
<!-- ?꿎뼯??-->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
self.assetsManifest = {
|
|
2
|
-
"version": "
|
|
2
|
+
"version": "aXuUUujO",
|
|
3
3
|
"assets": [
|
|
4
4
|
{
|
|
5
5
|
"hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"url": "_content/MindExecution.Shared/css/app.css"
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
|
-
"hash": "sha256-
|
|
45
|
+
"hash": "sha256-BQdCDRJQX7PiT/7AaeT11SXri099O+sl5lQoyjpQ+Q0=",
|
|
46
46
|
"url": "_content/MindExecution.Shared/css/mind-map-overrides.css"
|
|
47
47
|
},
|
|
48
48
|
{
|
|
@@ -442,8 +442,8 @@
|
|
|
442
442
|
"url": "_framework/MindExecution.Plugins.YouTube.fwync0c60s.dll"
|
|
443
443
|
},
|
|
444
444
|
{
|
|
445
|
-
"hash": "sha256-
|
|
446
|
-
"url": "_framework/MindExecution.Shared.
|
|
445
|
+
"hash": "sha256-9sBLG9hGRCI2aJVwzd1tbHfl+LMk3hc9oVp5EubnQdo=",
|
|
446
|
+
"url": "_framework/MindExecution.Shared.araen3jk1e.dll"
|
|
447
447
|
},
|
|
448
448
|
{
|
|
449
449
|
"hash": "sha256-1yXNI95k4vlq26Y8YlgIi69ufjSYk2OffuR6A864dUQ=",
|
|
@@ -770,7 +770,7 @@
|
|
|
770
770
|
"url": "_framework/Websocket.Client.vapounvmnl.dll"
|
|
771
771
|
},
|
|
772
772
|
{
|
|
773
|
-
"hash": "sha256-
|
|
773
|
+
"hash": "sha256-0+5TBCVcw2eTOUeLr7CCWVwpXjz7zRWuHpJoskMb2jI=",
|
|
774
774
|
"url": "_framework/blazor.boot.json"
|
|
775
775
|
},
|
|
776
776
|
{
|
|
@@ -834,7 +834,7 @@
|
|
|
834
834
|
"url": "image-manifest.json"
|
|
835
835
|
},
|
|
836
836
|
{
|
|
837
|
-
"hash": "sha256-
|
|
837
|
+
"hash": "sha256-2DrUTlHi/psz7d4TwdXV/IjMTlDk12gJ0x81ae3fV2U=",
|
|
838
838
|
"url": "index.html"
|
|
839
839
|
},
|
|
840
840
|
{
|