@mindexec/cli 0.2.119 → 0.2.120
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 +5 -7
- package/scripts/remote-http-smoke.mjs +14 -2
- package/scripts/remote-hub-scale-smoke.mjs +6 -1
- package/server.js +531 -50
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +39 -48
- package/wwwroot/_framework/{MindExecution.Shared.e8lipb57xv.dll → MindExecution.Shared.uwkrc41o75.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +3 -3
- package/wwwroot/index.html +3 -3
- package/wwwroot/service-worker-assets.js +6 -6
- package/wwwroot/service-worker.js +1 -1
package/package.json
CHANGED
package/remote-hub.js
CHANGED
|
@@ -372,8 +372,8 @@ function serializeRemoteFrame(frame, deviceId, frameKind, options = {}) {
|
|
|
372
372
|
framePath,
|
|
373
373
|
frameUrl: framePath
|
|
374
374
|
};
|
|
375
|
-
if (options.includeDataUrl
|
|
376
|
-
serialized.dataUrl = dataUrl;
|
|
375
|
+
if (options.includeDataUrl === true) {
|
|
376
|
+
serialized.dataUrl = dataUrl || buildFrameDataUrl(payload, '', publicFrame.mimeType || publicFrame.format || 'image/jpeg');
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
return serialized;
|
|
@@ -1114,7 +1114,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1114
1114
|
|
|
1115
1115
|
function listDevices(options = {}) {
|
|
1116
1116
|
const serializeOptions = {
|
|
1117
|
-
includeDataUrl: options.includeDataUrl
|
|
1117
|
+
includeDataUrl: options.includeDataUrl === true
|
|
1118
1118
|
};
|
|
1119
1119
|
return [...devices.values()]
|
|
1120
1120
|
.map(device => serializeDevice(device, serializeOptions))
|
|
@@ -1969,7 +1969,6 @@ export function createRemoteHub(options = {}) {
|
|
|
1969
1969
|
contentHash,
|
|
1970
1970
|
captureMs: readFrameCaptureMs(message),
|
|
1971
1971
|
sameContentStreak,
|
|
1972
|
-
dataUrl: buildFrameDataUrl(framePayload, frameData, mimeType),
|
|
1973
1972
|
payload,
|
|
1974
1973
|
accessToken: createFrameAccessToken()
|
|
1975
1974
|
};
|
|
@@ -2040,7 +2039,6 @@ export function createRemoteHub(options = {}) {
|
|
|
2040
2039
|
contentHash,
|
|
2041
2040
|
captureMs: readFrameCaptureMs(message),
|
|
2042
2041
|
sameContentStreak,
|
|
2043
|
-
dataUrl: buildFrameDataUrl(framePayload, frameData, mimeType),
|
|
2044
2042
|
payload,
|
|
2045
2043
|
accessToken: createFrameAccessToken()
|
|
2046
2044
|
};
|
|
@@ -2961,14 +2959,14 @@ export function createRemoteHub(options = {}) {
|
|
|
2961
2959
|
function getDeviceLiveFrame(deviceId, options = {}) {
|
|
2962
2960
|
const device = devices.get(String(deviceId || ''));
|
|
2963
2961
|
return serializeRemoteFrame(device?.latestLiveFrame, device?.deviceId, 'live', {
|
|
2964
|
-
includeDataUrl: options.includeDataUrl
|
|
2962
|
+
includeDataUrl: options.includeDataUrl === true
|
|
2965
2963
|
});
|
|
2966
2964
|
}
|
|
2967
2965
|
|
|
2968
2966
|
function getDeviceThumbnail(deviceId, options = {}) {
|
|
2969
2967
|
const device = devices.get(String(deviceId || ''));
|
|
2970
2968
|
return serializeRemoteFrame(device?.latestThumbnail, device?.deviceId, 'thumbnail', {
|
|
2971
|
-
includeDataUrl: options.includeDataUrl
|
|
2969
|
+
includeDataUrl: options.includeDataUrl === true
|
|
2972
2970
|
});
|
|
2973
2971
|
}
|
|
2974
2972
|
|
|
@@ -373,10 +373,16 @@ async function runSyntheticEnabledSmoke() {
|
|
|
373
373
|
});
|
|
374
374
|
assert.equal(thumbnail.ok, true, JSON.stringify(thumbnail.payload));
|
|
375
375
|
assert.equal(thumbnail.payload?.thumbnail?.streamId, 'http-smoke-thumb');
|
|
376
|
-
assert.ok(
|
|
376
|
+
assert.ok(!('dataUrl' in thumbnail.payload.thumbnail));
|
|
377
377
|
assert.ok(String(thumbnail.payload?.thumbnail?.framePath || '').includes(`/api/remote/devices/${encodeURIComponent(thumbnailTarget.deviceId)}/thumbnail?`));
|
|
378
378
|
assert.equal(thumbnail.payload?.thumbnail?.frameUrl, thumbnail.payload?.thumbnail?.framePath);
|
|
379
379
|
|
|
380
|
+
const thumbnailWithData = await fetchJson(`${baseUrl}/api/remote/devices/${encodeURIComponent(thumbnailTarget.deviceId)}/thumbnail?includeDataUrl=true`, {
|
|
381
|
+
token: BRIDGE_TOKEN
|
|
382
|
+
});
|
|
383
|
+
assert.equal(thumbnailWithData.ok, true, JSON.stringify(thumbnailWithData.payload));
|
|
384
|
+
assert.ok(String(thumbnailWithData.payload?.thumbnail?.dataUrl || '').startsWith('data:image/png;base64,'));
|
|
385
|
+
|
|
380
386
|
const thumbnailBinary = await fetchBinary(`${baseUrl}${thumbnail.payload.thumbnail.framePath}`);
|
|
381
387
|
assert.equal(thumbnailBinary.ok, true, `${thumbnailBinary.status} ${thumbnailBinary.contentType}`);
|
|
382
388
|
assert.equal(thumbnailBinary.contentType, 'image/png');
|
|
@@ -433,9 +439,15 @@ async function runSyntheticEnabledSmoke() {
|
|
|
433
439
|
assert.equal(liveFrame.payload?.frame?.streamId, 'http-smoke-live');
|
|
434
440
|
assert.equal(liveFrame.payload?.frame?.mode, 'remote-fast');
|
|
435
441
|
assert.equal(liveFrame.payload?.frame?.fps, 20);
|
|
436
|
-
assert.ok(
|
|
442
|
+
assert.ok(!('dataUrl' in liveFrame.payload.frame));
|
|
437
443
|
assert.ok(String(liveFrame.payload?.frame?.framePath || '').includes(`/api/remote/devices/${encodeURIComponent(liveTarget.deviceId)}/live/frame?`));
|
|
438
444
|
|
|
445
|
+
const liveFrameWithData = await fetchJson(`${baseUrl}/api/remote/devices/${encodeURIComponent(liveTarget.deviceId)}/live/frame?includeDataUrl=true`, {
|
|
446
|
+
token: BRIDGE_TOKEN
|
|
447
|
+
});
|
|
448
|
+
assert.equal(liveFrameWithData.ok, true, JSON.stringify(liveFrameWithData.payload));
|
|
449
|
+
assert.ok(String(liveFrameWithData.payload?.frame?.dataUrl || '').startsWith('data:image/png;base64,'));
|
|
450
|
+
|
|
439
451
|
const liveBinary = await fetchBinary(`${baseUrl}${liveFrame.payload.frame.framePath}`);
|
|
440
452
|
assert.equal(liveBinary.ok, true, `${liveBinary.status} ${liveBinary.contentType}`);
|
|
441
453
|
assert.equal(liveBinary.contentType, 'image/png');
|
|
@@ -37,9 +37,12 @@ try {
|
|
|
37
37
|
assert.equal(devices.length, SYNTHETIC_COUNT);
|
|
38
38
|
assert.equal(devices.filter(device => device.synthetic === true).length, SYNTHETIC_COUNT);
|
|
39
39
|
assert.equal(devices.some(device => device.connected === false), true);
|
|
40
|
-
assert.equal(devices.some(device => device.latestThumbnail
|
|
40
|
+
assert.equal(devices.some(device => device.latestThumbnail && 'dataUrl' in device.latestThumbnail), false);
|
|
41
41
|
assert.equal(devices.some(device => device.activeLiveStream?.active === true), true);
|
|
42
42
|
|
|
43
|
+
const devicesWithDataUrl = hub.listDevices({ includeDataUrl: true });
|
|
44
|
+
assert.equal(devicesWithDataUrl.some(device => device.latestThumbnail?.dataUrl), true);
|
|
45
|
+
|
|
43
46
|
const deviceListPayload = {
|
|
44
47
|
total: devices.length,
|
|
45
48
|
pagination: 'none',
|
|
@@ -58,6 +61,8 @@ try {
|
|
|
58
61
|
});
|
|
59
62
|
assert.equal(thumbnailResult.ok, true);
|
|
60
63
|
assert.equal(hub.getDeviceThumbnail(thumbnailTarget.deviceId).streamId, 'scale-thumb');
|
|
64
|
+
assert.equal('dataUrl' in hub.getDeviceThumbnail(thumbnailTarget.deviceId), false);
|
|
65
|
+
assert.ok(hub.getDeviceThumbnail(thumbnailTarget.deviceId, { includeDataUrl: true }).dataUrl);
|
|
61
66
|
|
|
62
67
|
const liveTarget = devices.find(device => device.connected && device.capabilities?.liveStream);
|
|
63
68
|
assert.ok(liveTarget);
|
package/server.js
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import express from 'express';
|
|
10
10
|
import cors from 'cors';
|
|
11
|
-
import { promises as fs, readFileSync, statSync, createReadStream } from 'fs';
|
|
11
|
+
import { promises as fs, readFileSync, statSync, createReadStream, chmodSync } from 'fs';
|
|
12
12
|
import path from 'path';
|
|
13
|
-
import { exec, spawn, execFile } from 'child_process';
|
|
13
|
+
import { exec, spawn, spawnSync, execFile } from 'child_process';
|
|
14
14
|
import { promisify } from 'util';
|
|
15
15
|
import os from 'os';
|
|
16
16
|
import multer from 'multer';
|
|
@@ -2907,10 +2907,14 @@ const REMOTE_AGENT_EARLY_EXIT_MS = 1200;
|
|
|
2907
2907
|
const REMOTE_AGENT_READY_TIMEOUT_MS = 5000;
|
|
2908
2908
|
const REMOTE_AGENT_DEFAULT_ENGINE = 'auto';
|
|
2909
2909
|
const REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS = 30000;
|
|
2910
|
+
const REMOTE_AGENT_RACE_START_STAGGER_MS = 120;
|
|
2911
|
+
const REMOTE_AGENT_MAX_PARALLEL_CANDIDATES = 6;
|
|
2910
2912
|
let remoteAgentState = createRemoteAgentIdleState();
|
|
2911
2913
|
let remoteAgentSyncReportState = null;
|
|
2912
2914
|
let remoteAgentSyncReportLogKey = '';
|
|
2913
2915
|
let remoteAgentSyncReportLogAt = 0;
|
|
2916
|
+
let remoteAgentConnectPromise = null;
|
|
2917
|
+
const remoteAgentRecentSuccessfulManagers = new Map();
|
|
2914
2918
|
|
|
2915
2919
|
function createRemoteAgentIdleState(overrides = {}) {
|
|
2916
2920
|
return {
|
|
@@ -3211,6 +3215,46 @@ function createRemoteAgentConnectionKey(manager, leaseId) {
|
|
|
3211
3215
|
.slice(0, 24);
|
|
3212
3216
|
}
|
|
3213
3217
|
|
|
3218
|
+
function getRemoteAgentRecentManagerKey(leaseId = '') {
|
|
3219
|
+
return safeRemoteAgentField(leaseId || 'default', 128) || 'default';
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
function rememberRemoteAgentSuccessfulManager(manager, leaseId = '') {
|
|
3223
|
+
const endpoint = normalizeRemoteManagerEndpoint(manager);
|
|
3224
|
+
if (!endpoint) {
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
remoteAgentRecentSuccessfulManagers.set(getRemoteAgentRecentManagerKey(leaseId), {
|
|
3229
|
+
endpoint,
|
|
3230
|
+
updatedAt: Date.now()
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
function prioritizeRemoteAgentManagers(managers, leaseId = '') {
|
|
3235
|
+
const normalized = normalizeRemoteManagerEndpointList(managers);
|
|
3236
|
+
if (normalized.length <= 1) {
|
|
3237
|
+
return normalized;
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
const recent = remoteAgentRecentSuccessfulManagers.get(getRemoteAgentRecentManagerKey(leaseId));
|
|
3241
|
+
const recentEndpoint = normalizeRemoteManagerEndpoint(recent?.endpoint);
|
|
3242
|
+
if (!recentEndpoint) {
|
|
3243
|
+
return normalized;
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
const recentIndex = normalized.findIndex(item => item.toLowerCase() === recentEndpoint.toLowerCase());
|
|
3247
|
+
if (recentIndex <= 0) {
|
|
3248
|
+
return normalized;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
return [
|
|
3252
|
+
normalized[recentIndex],
|
|
3253
|
+
...normalized.slice(0, recentIndex),
|
|
3254
|
+
...normalized.slice(recentIndex + 1)
|
|
3255
|
+
];
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3214
3258
|
function isRemoteAgentProcessRunning() {
|
|
3215
3259
|
const proc = remoteAgentState.proc;
|
|
3216
3260
|
return remoteAgentState.status === 'running'
|
|
@@ -3225,19 +3269,183 @@ function isLocalRemoteHostTargetActive() {
|
|
|
3225
3269
|
&& (!status.hostTargetHostInstanceId || status.hostTargetHostInstanceId === BRIDGE_INSTANCE_ID);
|
|
3226
3270
|
}
|
|
3227
3271
|
|
|
3228
|
-
function
|
|
3229
|
-
const
|
|
3230
|
-
|
|
3231
|
-
|
|
3272
|
+
function getRemoteFastRuntimeId() {
|
|
3273
|
+
const platform = process.platform;
|
|
3274
|
+
const arch = process.arch;
|
|
3275
|
+
if (platform === 'win32' && arch === 'x64') {
|
|
3276
|
+
return 'win-x64';
|
|
3277
|
+
}
|
|
3278
|
+
if (platform === 'darwin' && arch === 'arm64') {
|
|
3279
|
+
return 'osx-arm64';
|
|
3280
|
+
}
|
|
3281
|
+
if (platform === 'darwin' && arch === 'x64') {
|
|
3282
|
+
return 'osx-x64';
|
|
3283
|
+
}
|
|
3284
|
+
return '';
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
function getRemoteFastRuntimeExecutableName() {
|
|
3288
|
+
return process.platform === 'win32'
|
|
3289
|
+
? 'mindexec-remote-fast.exe'
|
|
3290
|
+
: 'mindexec-remote-fast';
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
function getBundledRemoteFastRuntimeDirs() {
|
|
3294
|
+
const rid = getRemoteFastRuntimeId();
|
|
3295
|
+
if (!rid) {
|
|
3296
|
+
return [];
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
return [
|
|
3300
|
+
path.join(BRIDGE_ROOT, 'remote-fast', rid),
|
|
3301
|
+
path.join(BRIDGE_ROOT, 'remote', 'fast', rid),
|
|
3302
|
+
path.join(BRIDGE_ROOT, 'node_modules', '@mindexec', 'remote', 'fast', rid),
|
|
3303
|
+
path.resolve(BRIDGE_ROOT, '..', 'remote', 'fast', rid),
|
|
3304
|
+
path.resolve(BRIDGE_ROOT, '..', 'RemoteAgent', 'fast', rid)
|
|
3305
|
+
];
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
function resolveRemoteFastLauncher() {
|
|
3309
|
+
const rid = getRemoteFastRuntimeId();
|
|
3310
|
+
if (!rid) {
|
|
3311
|
+
return {
|
|
3312
|
+
ok: false,
|
|
3313
|
+
error: `remote-fast-not-packaged:${process.platform}-${process.arch}`
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
const failures = [];
|
|
3318
|
+
const seen = new Set();
|
|
3319
|
+
for (const dir of getBundledRemoteFastRuntimeDirs()) {
|
|
3320
|
+
const resolvedDir = path.resolve(dir);
|
|
3321
|
+
const key = resolvedDir.toLowerCase();
|
|
3322
|
+
if (seen.has(key)) {
|
|
3323
|
+
continue;
|
|
3324
|
+
}
|
|
3325
|
+
seen.add(key);
|
|
3326
|
+
|
|
3327
|
+
const executable = path.join(resolvedDir, getRemoteFastRuntimeExecutableName());
|
|
3328
|
+
try {
|
|
3329
|
+
if (statSync(executable).isFile()) {
|
|
3330
|
+
if (process.platform !== 'win32') {
|
|
3331
|
+
try {
|
|
3332
|
+
const currentMode = statSync(executable).mode;
|
|
3333
|
+
chmodSync(executable, currentMode | 0o755);
|
|
3334
|
+
} catch {
|
|
3335
|
+
// best effort only
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
const preflight = spawnSync(executable, ['--version'], {
|
|
3340
|
+
encoding: 'utf8',
|
|
3341
|
+
windowsHide: true,
|
|
3342
|
+
timeout: 2500
|
|
3343
|
+
});
|
|
3344
|
+
if (preflight.status === 0) {
|
|
3345
|
+
return {
|
|
3346
|
+
ok: true,
|
|
3347
|
+
command: executable,
|
|
3348
|
+
argsPrefix: [],
|
|
3349
|
+
launcher: executable,
|
|
3350
|
+
usingNpx: false,
|
|
3351
|
+
directFast: true,
|
|
3352
|
+
rid
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
failures.push(`${executable}:exit:${preflight.status}`);
|
|
3356
|
+
}
|
|
3357
|
+
} catch {
|
|
3358
|
+
// Try DLL or next directory.
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
const dll = path.join(resolvedDir, 'mindexec-remote-fast.dll');
|
|
3362
|
+
try {
|
|
3363
|
+
if (statSync(dll).isFile()) {
|
|
3364
|
+
const preflight = spawnSync('dotnet', [dll, '--version'], {
|
|
3365
|
+
encoding: 'utf8',
|
|
3366
|
+
windowsHide: true,
|
|
3367
|
+
timeout: 2500
|
|
3368
|
+
});
|
|
3369
|
+
if (preflight.status === 0) {
|
|
3370
|
+
return {
|
|
3371
|
+
ok: true,
|
|
3372
|
+
command: 'dotnet',
|
|
3373
|
+
argsPrefix: [dll],
|
|
3374
|
+
launcher: dll,
|
|
3375
|
+
usingNpx: false,
|
|
3376
|
+
directFast: true,
|
|
3377
|
+
rid
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
failures.push(`${dll}:exit:${preflight.status}`);
|
|
3381
|
+
}
|
|
3382
|
+
} catch {
|
|
3383
|
+
// Try next directory.
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
return {
|
|
3388
|
+
ok: false,
|
|
3389
|
+
error: failures.length > 0
|
|
3390
|
+
? failures.join('; ')
|
|
3391
|
+
: `remote-fast-runtime-missing:${rid}`
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
function resolveLocalRemotePackageLaunchers() {
|
|
3396
|
+
return [
|
|
3397
|
+
path.join(BRIDGE_ROOT, 'remote', 'bin', 'mindexec-remote.js'),
|
|
3398
|
+
path.join(BRIDGE_ROOT, 'node_modules', '@mindexec', 'remote', 'bin', 'mindexec-remote.js'),
|
|
3399
|
+
path.resolve(BRIDGE_ROOT, '..', 'remote', 'bin', 'mindexec-remote.js'),
|
|
3400
|
+
path.resolve(BRIDGE_ROOT, '..', 'RemoteAgent', 'bin', 'mindexec-remote.js')
|
|
3401
|
+
];
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
function resolveRemotePackageLauncher() {
|
|
3405
|
+
const seen = new Set();
|
|
3406
|
+
for (const candidate of resolveLocalRemotePackageLaunchers()) {
|
|
3407
|
+
const resolved = path.resolve(candidate);
|
|
3408
|
+
const key = resolved.toLowerCase();
|
|
3409
|
+
if (seen.has(key)) {
|
|
3410
|
+
continue;
|
|
3411
|
+
}
|
|
3412
|
+
seen.add(key);
|
|
3413
|
+
try {
|
|
3414
|
+
if (statSync(resolved).isFile()) {
|
|
3415
|
+
return {
|
|
3416
|
+
command: process.execPath,
|
|
3417
|
+
argsPrefix: [resolved],
|
|
3418
|
+
launcher: resolved,
|
|
3419
|
+
usingNpx: false,
|
|
3420
|
+
directFast: false
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
} catch {
|
|
3424
|
+
// Try next package layout.
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
return null;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
function resolveRemoteAgentLauncher(engine = REMOTE_AGENT_DEFAULT_ENGINE) {
|
|
3431
|
+
const normalizedEngine = normalizeRemoteAgentEngine(engine);
|
|
3432
|
+
if (normalizedEngine !== 'node') {
|
|
3433
|
+
const fast = resolveRemoteFastLauncher();
|
|
3434
|
+
if (fast.ok) {
|
|
3435
|
+
return fast;
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
if (normalizedEngine === 'fast') {
|
|
3232
3439
|
return {
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
launcher: localLauncher,
|
|
3236
|
-
usingNpx: false
|
|
3440
|
+
ok: false,
|
|
3441
|
+
error: fast.error || 'remote-fast-unavailable'
|
|
3237
3442
|
};
|
|
3238
3443
|
}
|
|
3239
|
-
}
|
|
3240
|
-
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
const localPackage = resolveRemotePackageLauncher();
|
|
3447
|
+
if (localPackage) {
|
|
3448
|
+
return localPackage;
|
|
3241
3449
|
}
|
|
3242
3450
|
|
|
3243
3451
|
const npxCli = resolveNpxCliLauncher();
|
|
@@ -3246,7 +3454,8 @@ function resolveRemoteAgentLauncher() {
|
|
|
3246
3454
|
command: process.execPath,
|
|
3247
3455
|
argsPrefix: [npxCli, '-y', '@mindexec/remote@latest'],
|
|
3248
3456
|
launcher: npxCli,
|
|
3249
|
-
usingNpx: true
|
|
3457
|
+
usingNpx: true,
|
|
3458
|
+
directFast: false
|
|
3250
3459
|
};
|
|
3251
3460
|
}
|
|
3252
3461
|
|
|
@@ -3255,7 +3464,8 @@ function resolveRemoteAgentLauncher() {
|
|
|
3255
3464
|
command: process.env.ComSpec || 'cmd.exe',
|
|
3256
3465
|
argsPrefix: ['/d', '/s', '/c', 'npx.cmd', '-y', '@mindexec/remote@latest'],
|
|
3257
3466
|
launcher: 'cmd.exe /c npx.cmd',
|
|
3258
|
-
usingNpx: true
|
|
3467
|
+
usingNpx: true,
|
|
3468
|
+
directFast: false
|
|
3259
3469
|
};
|
|
3260
3470
|
}
|
|
3261
3471
|
|
|
@@ -3263,7 +3473,8 @@ function resolveRemoteAgentLauncher() {
|
|
|
3263
3473
|
command: 'npx',
|
|
3264
3474
|
argsPrefix: ['-y', '@mindexec/remote@latest'],
|
|
3265
3475
|
launcher: 'npx',
|
|
3266
|
-
usingNpx: true
|
|
3476
|
+
usingNpx: true,
|
|
3477
|
+
directFast: false
|
|
3267
3478
|
};
|
|
3268
3479
|
}
|
|
3269
3480
|
|
|
@@ -3343,11 +3554,11 @@ async function startRemoteAgentConnection(options = {}) {
|
|
|
3343
3554
|
const nodeId = safeRemoteAgentField(options.nodeId, 128);
|
|
3344
3555
|
const source = safeRemoteAgentField(options.source || 'registry', 64);
|
|
3345
3556
|
const engine = normalizeRemoteAgentEngine(options.engine);
|
|
3346
|
-
const managers = normalizeRemoteManagerEndpointList(
|
|
3557
|
+
const managers = prioritizeRemoteAgentManagers(normalizeRemoteManagerEndpointList(
|
|
3347
3558
|
options.manager || options.managerEndpoint || options.endpoint,
|
|
3348
3559
|
options.managerCandidates,
|
|
3349
3560
|
options.endpointCandidates,
|
|
3350
|
-
options.candidates);
|
|
3561
|
+
options.candidates), leaseId);
|
|
3351
3562
|
|
|
3352
3563
|
if (managers.length === 0) {
|
|
3353
3564
|
logWarn('remote', 'managed RemoteAgent connect rejected: invalid manager endpoint');
|
|
@@ -3385,45 +3596,321 @@ async function startRemoteAgentConnection(options = {}) {
|
|
|
3385
3596
|
await stopRemoteAgentConnection('replaced-by-new-target');
|
|
3386
3597
|
}
|
|
3387
3598
|
|
|
3388
|
-
|
|
3599
|
+
if (remoteAgentConnectPromise) {
|
|
3600
|
+
return await remoteAgentConnectPromise;
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
remoteAgentConnectPromise = startRemoteAgentConnectionRace({
|
|
3604
|
+
managers,
|
|
3605
|
+
pairToken,
|
|
3606
|
+
leaseId,
|
|
3607
|
+
nodeId,
|
|
3608
|
+
source,
|
|
3609
|
+
engine
|
|
3610
|
+
}).finally(() => {
|
|
3611
|
+
remoteAgentConnectPromise = null;
|
|
3612
|
+
});
|
|
3613
|
+
return await remoteAgentConnectPromise;
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
function buildRemoteAgentLaunchArgs(launcher, manager, pairToken, engine) {
|
|
3617
|
+
const args = [
|
|
3618
|
+
...launcher.argsPrefix,
|
|
3619
|
+
'connect',
|
|
3620
|
+
'--manager',
|
|
3621
|
+
manager,
|
|
3622
|
+
'--pair',
|
|
3623
|
+
pairToken
|
|
3624
|
+
];
|
|
3625
|
+
|
|
3626
|
+
if (launcher.directFast === true) {
|
|
3627
|
+
args.push('--transport', 'ws');
|
|
3628
|
+
return args;
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
args.push('--engine', engine);
|
|
3632
|
+
return args;
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
function appendRemoteAgentAttemptOutput(attempt, stream, chunk) {
|
|
3636
|
+
const text = chunk.toString();
|
|
3637
|
+
const key = stream === 'stderr' ? 'stderrTail' : 'stdoutTail';
|
|
3638
|
+
attempt.state[key] = String((attempt.state[key] || '') + text).slice(-REMOTE_AGENT_STDIO_TAIL_CHARS);
|
|
3639
|
+
if (/Connected to RemoteHub/i.test(text)) {
|
|
3640
|
+
attempt.state.ready = true;
|
|
3641
|
+
attempt.state.connectedAt = new Date().toISOString();
|
|
3642
|
+
}
|
|
3643
|
+
attempt.state.updatedAt = new Date().toISOString();
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
function summarizeRemoteAgentAttempt(attempt) {
|
|
3647
|
+
return formatRemoteAgentFailureSummary(attempt?.state || {});
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
function createRemoteAgentAttempt(options = {}) {
|
|
3651
|
+
const manager = normalizeRemoteManagerEndpoint(options.manager);
|
|
3652
|
+
const pairToken = safeRemoteAgentField(options.pairToken || options.pair, 512);
|
|
3653
|
+
const leaseId = safeRemoteAgentField(options.leaseId, 128);
|
|
3654
|
+
const nodeId = safeRemoteAgentField(options.nodeId, 128);
|
|
3655
|
+
const source = safeRemoteAgentField(options.source || 'registry', 64);
|
|
3656
|
+
const engine = normalizeRemoteAgentEngine(options.engine);
|
|
3657
|
+
const managerCandidates = normalizeRemoteManagerEndpointList(options.managerCandidates, manager);
|
|
3658
|
+
|
|
3659
|
+
const connectionKey = createRemoteAgentConnectionKey(manager, leaseId);
|
|
3660
|
+
const launcher = options.launcher || resolveRemoteAgentLauncher(engine);
|
|
3661
|
+
if (launcher?.ok === false) {
|
|
3662
|
+
return {
|
|
3663
|
+
manager,
|
|
3664
|
+
failed: true,
|
|
3665
|
+
error: launcher.error || 'remote-agent-launcher-unavailable',
|
|
3666
|
+
state: createRemoteAgentIdleState({
|
|
3667
|
+
status: 'failed',
|
|
3668
|
+
manager,
|
|
3669
|
+
managerCandidates,
|
|
3670
|
+
leaseId,
|
|
3671
|
+
nodeId,
|
|
3672
|
+
engine,
|
|
3673
|
+
source,
|
|
3674
|
+
connectionKey,
|
|
3675
|
+
lastError: launcher.error || 'remote-agent-launcher-unavailable'
|
|
3676
|
+
})
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
const state = createRemoteAgentIdleState({
|
|
3681
|
+
status: 'starting',
|
|
3682
|
+
manager,
|
|
3683
|
+
managerCandidates,
|
|
3684
|
+
leaseId,
|
|
3685
|
+
nodeId,
|
|
3686
|
+
engine,
|
|
3687
|
+
source,
|
|
3688
|
+
connectionKey,
|
|
3689
|
+
startedAt: new Date().toISOString(),
|
|
3690
|
+
ready: false,
|
|
3691
|
+
connectedAt: '',
|
|
3692
|
+
launcher: launcher.launcher,
|
|
3693
|
+
usingNpx: launcher.usingNpx
|
|
3694
|
+
});
|
|
3695
|
+
|
|
3696
|
+
const args = buildRemoteAgentLaunchArgs(launcher, manager, pairToken, engine);
|
|
3697
|
+
const attempt = {
|
|
3698
|
+
manager,
|
|
3699
|
+
pairToken,
|
|
3700
|
+
leaseId,
|
|
3701
|
+
nodeId,
|
|
3702
|
+
source,
|
|
3703
|
+
engine,
|
|
3704
|
+
launcher,
|
|
3705
|
+
args,
|
|
3706
|
+
state,
|
|
3707
|
+
child: null,
|
|
3708
|
+
failed: false,
|
|
3709
|
+
error: ''
|
|
3710
|
+
};
|
|
3711
|
+
|
|
3712
|
+
try {
|
|
3713
|
+
const child = spawn(launcher.command, args, {
|
|
3714
|
+
cwd: BRIDGE_ROOT,
|
|
3715
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
3716
|
+
windowsHide: true,
|
|
3717
|
+
env: {
|
|
3718
|
+
...process.env,
|
|
3719
|
+
MINDEXEC_REMOTE_AGENT_MANAGED: '1'
|
|
3720
|
+
}
|
|
3721
|
+
});
|
|
3722
|
+
|
|
3723
|
+
attempt.child = child;
|
|
3724
|
+
state.proc = child;
|
|
3725
|
+
state.pid = Number(child.pid || 0);
|
|
3726
|
+
state.status = 'running';
|
|
3727
|
+
state.updatedAt = new Date().toISOString();
|
|
3728
|
+
|
|
3729
|
+
child.stdout.on('data', chunk => appendRemoteAgentAttemptOutput(attempt, 'stdout', chunk));
|
|
3730
|
+
child.stderr.on('data', chunk => appendRemoteAgentAttemptOutput(attempt, 'stderr', chunk));
|
|
3731
|
+
child.once('error', err => {
|
|
3732
|
+
attempt.failed = true;
|
|
3733
|
+
attempt.error = err?.message || String(err);
|
|
3734
|
+
state.status = 'failed';
|
|
3735
|
+
state.lastError = attempt.error;
|
|
3736
|
+
state.updatedAt = new Date().toISOString();
|
|
3737
|
+
});
|
|
3738
|
+
child.once('exit', (code, signal) => {
|
|
3739
|
+
if (state.status === 'running' && state.ready === true) {
|
|
3740
|
+
state.status = code === 0 ? 'exited' : 'failed';
|
|
3741
|
+
} else {
|
|
3742
|
+
attempt.failed = true;
|
|
3743
|
+
state.status = code === 0 ? 'exited' : 'failed';
|
|
3744
|
+
}
|
|
3745
|
+
state.exitCode = Number.isFinite(code) ? code : null;
|
|
3746
|
+
state.signal = signal || '';
|
|
3747
|
+
state.exitedAt = new Date().toISOString();
|
|
3748
|
+
state.updatedAt = state.exitedAt;
|
|
3749
|
+
if (code !== 0 && !state.lastError) {
|
|
3750
|
+
state.lastError = signal ? `signal:${signal}` : `exit:${code}`;
|
|
3751
|
+
}
|
|
3752
|
+
attempt.error = attempt.error || state.lastError || `exit:${code ?? 'unknown'}`;
|
|
3753
|
+
});
|
|
3754
|
+
} catch (err) {
|
|
3755
|
+
attempt.failed = true;
|
|
3756
|
+
attempt.error = err?.message || String(err);
|
|
3757
|
+
state.status = 'failed';
|
|
3758
|
+
state.lastError = attempt.error;
|
|
3759
|
+
state.updatedAt = new Date().toISOString();
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
return attempt;
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
async function stopRemoteAgentAttempt(attempt, reason = 'race-lost') {
|
|
3766
|
+
if (!attempt?.child) {
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
if (attempt.child.exitCode !== null || attempt.child.killed) {
|
|
3771
|
+
return;
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
try {
|
|
3775
|
+
await terminateProcessTree(attempt.child);
|
|
3776
|
+
} catch (err) {
|
|
3777
|
+
logWarn('remote', `managed RemoteAgent cleanup failed ${formatKeyValue('manager', attempt.manager || '-')} ${formatKeyValue('reason', reason)} ${formatKeyValue('error', err?.message || err)}`);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
async function startRemoteAgentConnectionRace(options = {}) {
|
|
3782
|
+
const managers = normalizeRemoteManagerEndpointList(options.managers).slice(0, REMOTE_AGENT_MAX_PARALLEL_CANDIDATES);
|
|
3783
|
+
const pairToken = safeRemoteAgentField(options.pairToken || options.pair, 512);
|
|
3784
|
+
const leaseId = safeRemoteAgentField(options.leaseId, 128);
|
|
3785
|
+
const nodeId = safeRemoteAgentField(options.nodeId, 128);
|
|
3786
|
+
const source = safeRemoteAgentField(options.source || 'registry', 64);
|
|
3787
|
+
const engine = normalizeRemoteAgentEngine(options.engine);
|
|
3788
|
+
const launcher = resolveRemoteAgentLauncher(engine);
|
|
3789
|
+
if (launcher?.ok === false) {
|
|
3790
|
+
const error = launcher.error || 'remote-agent-launcher-unavailable';
|
|
3791
|
+
remoteAgentState = createRemoteAgentIdleState({
|
|
3792
|
+
status: 'failed',
|
|
3793
|
+
manager: managers[0] || '',
|
|
3794
|
+
managerCandidates: managers,
|
|
3795
|
+
leaseId,
|
|
3796
|
+
nodeId,
|
|
3797
|
+
engine,
|
|
3798
|
+
source,
|
|
3799
|
+
lastError: error,
|
|
3800
|
+
updatedAt: new Date().toISOString()
|
|
3801
|
+
});
|
|
3802
|
+
emitBridgeEvent('RemoteAgentFailed', serializeRemoteAgentState());
|
|
3803
|
+
return { ok: false, error, agent: serializeRemoteAgentState() };
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
remoteAgentState = createRemoteAgentIdleState({
|
|
3807
|
+
status: 'starting',
|
|
3808
|
+
manager: managers[0] || '',
|
|
3809
|
+
managerCandidates: managers,
|
|
3810
|
+
leaseId,
|
|
3811
|
+
nodeId,
|
|
3812
|
+
engine,
|
|
3813
|
+
source,
|
|
3814
|
+
startedAt: new Date().toISOString(),
|
|
3815
|
+
launcher: 'race',
|
|
3816
|
+
usingNpx: false
|
|
3817
|
+
});
|
|
3818
|
+
|
|
3819
|
+
const attempts = [];
|
|
3389
3820
|
for (let index = 0; index < managers.length; index += 1) {
|
|
3390
3821
|
const manager = managers[index];
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
'remote');
|
|
3396
|
-
}
|
|
3822
|
+
logEvent(
|
|
3823
|
+
'remote',
|
|
3824
|
+
`managed RemoteAgent race ${index + 1}/${managers.length} ${formatKeyValue('manager', manager)}`,
|
|
3825
|
+
'remote');
|
|
3397
3826
|
|
|
3398
|
-
const
|
|
3827
|
+
const attempt = createRemoteAgentAttempt({
|
|
3399
3828
|
manager,
|
|
3400
3829
|
managerCandidates: managers,
|
|
3401
3830
|
pairToken,
|
|
3402
3831
|
leaseId,
|
|
3403
3832
|
nodeId,
|
|
3404
3833
|
source,
|
|
3405
|
-
engine
|
|
3834
|
+
engine,
|
|
3835
|
+
launcher
|
|
3406
3836
|
});
|
|
3407
|
-
|
|
3408
|
-
|
|
3837
|
+
attempts.push(attempt);
|
|
3838
|
+
|
|
3839
|
+
if (index + 1 < managers.length && REMOTE_AGENT_RACE_START_STAGGER_MS > 0) {
|
|
3840
|
+
await new Promise(resolve => setTimeout(resolve, REMOTE_AGENT_RACE_START_STAGGER_MS));
|
|
3409
3841
|
}
|
|
3842
|
+
}
|
|
3410
3843
|
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3844
|
+
const startedAt = Date.now();
|
|
3845
|
+
let winner = null;
|
|
3846
|
+
while (Date.now() - startedAt < REMOTE_AGENT_READY_TIMEOUT_MS) {
|
|
3847
|
+
winner = attempts.find(attempt =>
|
|
3848
|
+
attempt?.state?.ready === true
|
|
3849
|
+
&& attempt?.child
|
|
3850
|
+
&& attempt.child.exitCode === null
|
|
3851
|
+
&& !attempt.child.killed);
|
|
3852
|
+
if (winner) {
|
|
3853
|
+
break;
|
|
3414
3854
|
}
|
|
3415
3855
|
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3856
|
+
const allFinished = attempts.every(attempt =>
|
|
3857
|
+
attempt.failed === true
|
|
3858
|
+
|| !attempt.child
|
|
3859
|
+
|| attempt.child.exitCode !== null
|
|
3860
|
+
|| attempt.child.killed);
|
|
3861
|
+
if (allFinished) {
|
|
3862
|
+
break;
|
|
3420
3863
|
}
|
|
3864
|
+
|
|
3865
|
+
await new Promise(resolve => setTimeout(resolve, 60));
|
|
3421
3866
|
}
|
|
3422
3867
|
|
|
3868
|
+
if (winner) {
|
|
3869
|
+
for (const attempt of attempts) {
|
|
3870
|
+
if (attempt !== winner) {
|
|
3871
|
+
await stopRemoteAgentAttempt(attempt, 'race-lost');
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
remoteAgentState = winner.state;
|
|
3876
|
+
remoteAgentState.status = 'running';
|
|
3877
|
+
remoteAgentState.proc = winner.child;
|
|
3878
|
+
remoteAgentState.updatedAt = new Date().toISOString();
|
|
3879
|
+
rememberRemoteAgentSuccessfulManager(winner.manager, leaseId);
|
|
3880
|
+
emitBridgeEvent('RemoteAgentStarted', serializeRemoteAgentState());
|
|
3881
|
+
logEvent(
|
|
3882
|
+
'remote',
|
|
3883
|
+
`managed RemoteAgent connected ${formatKeyValue('manager', winner.manager)} ${formatKeyValue('launcher', winner.launcher?.launcher || '-')}`,
|
|
3884
|
+
'remote');
|
|
3885
|
+
return { ok: true, alreadyRunning: false, agent: serializeRemoteAgentState() };
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
const lastAttempt = attempts.findLast?.(attempt => attempt?.error || attempt?.state?.lastError)
|
|
3889
|
+
|| attempts[attempts.length - 1]
|
|
3890
|
+
|| null;
|
|
3891
|
+
for (const attempt of attempts) {
|
|
3892
|
+
await stopRemoteAgentAttempt(attempt, 'race-failed');
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
const error = lastAttempt
|
|
3896
|
+
? summarizeRemoteAgentAttempt(lastAttempt)
|
|
3897
|
+
: 'all-manager-candidates-failed';
|
|
3898
|
+
remoteAgentState = createRemoteAgentIdleState({
|
|
3899
|
+
status: 'failed',
|
|
3900
|
+
manager: managers[0] || '',
|
|
3901
|
+
managerCandidates: managers,
|
|
3902
|
+
leaseId,
|
|
3903
|
+
nodeId,
|
|
3904
|
+
engine,
|
|
3905
|
+
source,
|
|
3906
|
+
lastError: error,
|
|
3907
|
+
updatedAt: new Date().toISOString()
|
|
3908
|
+
});
|
|
3909
|
+
emitBridgeEvent('RemoteAgentFailed', serializeRemoteAgentState());
|
|
3423
3910
|
return {
|
|
3424
3911
|
ok: false,
|
|
3425
|
-
error
|
|
3426
|
-
agent:
|
|
3912
|
+
error,
|
|
3913
|
+
agent: serializeRemoteAgentState()
|
|
3427
3914
|
};
|
|
3428
3915
|
}
|
|
3429
3916
|
|
|
@@ -3445,17 +3932,11 @@ async function startRemoteAgentConnectionAttempt(options = {}) {
|
|
|
3445
3932
|
}
|
|
3446
3933
|
|
|
3447
3934
|
const connectionKey = createRemoteAgentConnectionKey(manager, leaseId);
|
|
3448
|
-
const launcher = resolveRemoteAgentLauncher();
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
manager,
|
|
3454
|
-
'--pair',
|
|
3455
|
-
pairToken,
|
|
3456
|
-
'--engine',
|
|
3457
|
-
engine
|
|
3458
|
-
];
|
|
3935
|
+
const launcher = resolveRemoteAgentLauncher(engine);
|
|
3936
|
+
if (launcher?.ok === false) {
|
|
3937
|
+
return { ok: false, error: launcher.error || 'remote-agent-launcher-unavailable', agent: serializeRemoteAgentState() };
|
|
3938
|
+
}
|
|
3939
|
+
const args = buildRemoteAgentLaunchArgs(launcher, manager, pairToken, engine);
|
|
3459
3940
|
|
|
3460
3941
|
remoteAgentState = createRemoteAgentIdleState({
|
|
3461
3942
|
status: 'starting',
|
|
@@ -8065,7 +8546,7 @@ app.get('/api/remote/status', (req, res) => {
|
|
|
8065
8546
|
|
|
8066
8547
|
app.get('/api/remote/devices', (req, res) => {
|
|
8067
8548
|
res.setHeader('Cache-Control', 'no-store');
|
|
8068
|
-
const includeDataUrl =
|
|
8549
|
+
const includeDataUrl = /^(1|true|yes|on|data|base64)$/i.test(String(req.query?.includeDataUrl ?? req.query?.frameData ?? ''));
|
|
8069
8550
|
const devices = remoteHub.listDevices({ includeDataUrl });
|
|
8070
8551
|
res.json({
|
|
8071
8552
|
total: devices.length,
|
|
@@ -8285,7 +8766,7 @@ function isRemoteCapabilityEnabled(device, key) {
|
|
|
8285
8766
|
}
|
|
8286
8767
|
|
|
8287
8768
|
function includeRemoteFrameDataUrl(req) {
|
|
8288
|
-
return
|
|
8769
|
+
return /^(1|true|yes|on|data|base64)$/i.test(String(req.query?.includeDataUrl ?? req.query?.frameData ?? ''));
|
|
8289
8770
|
}
|
|
8290
8771
|
|
|
8291
8772
|
function sendRemoteFrameBinary(req, res, frameKind) {
|
|
@@ -14103,6 +14103,7 @@
|
|
|
14103
14103
|
function isRemoteFleetFrameSource(value) {
|
|
14104
14104
|
const source = String(value || '').trim();
|
|
14105
14105
|
if (/^data:image\/(png|jpe?g|webp|svg\+xml);base64,/i.test(source)
|
|
14106
|
+
|| /^blob:/i.test(source)
|
|
14106
14107
|
|| /^\/api\/remote\//i.test(source)) {
|
|
14107
14108
|
return true;
|
|
14108
14109
|
}
|
|
@@ -14374,7 +14375,18 @@
|
|
|
14374
14375
|
return null;
|
|
14375
14376
|
}
|
|
14376
14377
|
|
|
14377
|
-
if (typeof
|
|
14378
|
+
if (typeof createImageBitmap !== 'function') {
|
|
14379
|
+
return null;
|
|
14380
|
+
}
|
|
14381
|
+
|
|
14382
|
+
const timeoutMs = getRemoteFleetFrameDecodeTimeoutMs(frame);
|
|
14383
|
+
if (frame._remoteFleetPayloadBlob) {
|
|
14384
|
+
return await withRemoteFleetFrameTimeout(
|
|
14385
|
+
createImageBitmap(frame._remoteFleetPayloadBlob),
|
|
14386
|
+
timeoutMs);
|
|
14387
|
+
}
|
|
14388
|
+
|
|
14389
|
+
if (typeof fetch !== 'function') {
|
|
14378
14390
|
return null;
|
|
14379
14391
|
}
|
|
14380
14392
|
|
|
@@ -14412,7 +14424,6 @@
|
|
|
14412
14424
|
entry.expiresAt = now + REMOTE_FLEET_FRAME_BLOB_CACHE_MS;
|
|
14413
14425
|
}
|
|
14414
14426
|
|
|
14415
|
-
const timeoutMs = getRemoteFleetFrameDecodeTimeoutMs(frame);
|
|
14416
14427
|
const blob = await withRemoteFleetFrameTimeout(
|
|
14417
14428
|
entry.promise,
|
|
14418
14429
|
timeoutMs,
|
|
@@ -14431,7 +14442,7 @@
|
|
|
14431
14442
|
}
|
|
14432
14443
|
|
|
14433
14444
|
function cloneRemoteFleetFramePatch(frame) {
|
|
14434
|
-
|
|
14445
|
+
const patch = {
|
|
14435
14446
|
deviceId: String(frame?.deviceId || '').trim(),
|
|
14436
14447
|
kind: String(frame?.kind || 'thumbnail').trim().toLowerCase(),
|
|
14437
14448
|
frameSeq: Number.isFinite(Number(frame?.frameSeq)) ? Math.floor(Number(frame.frameSeq)) : 0,
|
|
@@ -14443,6 +14454,16 @@
|
|
|
14443
14454
|
captureMs: Number(frame?.captureMs || 0) || 0,
|
|
14444
14455
|
sameContentStreak: Number(frame?.sameContentStreak || 0) || 0
|
|
14445
14456
|
};
|
|
14457
|
+
if (frame?._remoteFleetPayloadBlob) {
|
|
14458
|
+
patch._remoteFleetPayloadBlob = frame._remoteFleetPayloadBlob;
|
|
14459
|
+
}
|
|
14460
|
+
if (frame?._remoteFleetObjectUrl) {
|
|
14461
|
+
patch._remoteFleetObjectUrl = frame._remoteFleetObjectUrl;
|
|
14462
|
+
}
|
|
14463
|
+
if (frame?._remoteFleetBinaryFrame === true) {
|
|
14464
|
+
patch._remoteFleetBinaryFrame = true;
|
|
14465
|
+
}
|
|
14466
|
+
return patch;
|
|
14446
14467
|
}
|
|
14447
14468
|
|
|
14448
14469
|
function isRemoteFleetFrameNewerForPreview(preview, frame, comparePending = false) {
|
|
@@ -14543,23 +14564,7 @@
|
|
|
14543
14564
|
placeholder.remove();
|
|
14544
14565
|
}
|
|
14545
14566
|
|
|
14546
|
-
|
|
14547
|
-
if (previewMode === 'tile') {
|
|
14548
|
-
preview.querySelector?.('[data-remote-fleet-frame-badge="true"]')?.remove();
|
|
14549
|
-
return true;
|
|
14550
|
-
}
|
|
14551
|
-
|
|
14552
|
-
const badge = ensureRemoteFleetFrameBadge(preview, frame);
|
|
14553
|
-
if (badge) {
|
|
14554
|
-
const at = frame.receivedAt || frame.capturedAt || '';
|
|
14555
|
-
badge.textContent = frame.kind === 'live'
|
|
14556
|
-
? `LIVE ${at ? formatRemoteFleetAge(at) : ''}`.trim()
|
|
14557
|
-
: (at ? formatRemoteFleetAge(at) : 'Screen');
|
|
14558
|
-
badge.style.display = 'inline-flex';
|
|
14559
|
-
badge.style.background = frame.kind === 'live'
|
|
14560
|
-
? 'rgba(220, 38, 38, 0.84)'
|
|
14561
|
-
: 'rgba(15, 23, 42, 0.72)';
|
|
14562
|
-
}
|
|
14567
|
+
preview.querySelector?.('[data-remote-fleet-frame-badge="true"]')?.remove();
|
|
14563
14568
|
|
|
14564
14569
|
return true;
|
|
14565
14570
|
}
|
|
@@ -14880,11 +14885,10 @@
|
|
|
14880
14885
|
|
|
14881
14886
|
function applyRemoteFleetFramePatchToPreview(preview, frame) {
|
|
14882
14887
|
if (String(frame?.kind || '').toLowerCase() === 'live') {
|
|
14883
|
-
preview.
|
|
14884
|
-
delete preview.dataset.
|
|
14885
|
-
delete preview.dataset.
|
|
14886
|
-
delete preview.dataset.
|
|
14887
|
-
return queueRemoteFleetImageSwap(preview, frame);
|
|
14888
|
+
preview._remoteFleetPendingImageFrame = null;
|
|
14889
|
+
delete preview.dataset.remoteFleetPendingImageKind;
|
|
14890
|
+
delete preview.dataset.remoteFleetPendingImageSeq;
|
|
14891
|
+
delete preview.dataset.remoteFleetPendingImageUrl;
|
|
14888
14892
|
}
|
|
14889
14893
|
|
|
14890
14894
|
return queueRemoteFleetFramePaint(preview, frame);
|
|
@@ -14964,6 +14968,16 @@
|
|
|
14964
14968
|
return;
|
|
14965
14969
|
}
|
|
14966
14970
|
|
|
14971
|
+
const binarySession = bodyView._remoteFleetBinaryFrameSession;
|
|
14972
|
+
const binarySocket = binarySession?.ws || null;
|
|
14973
|
+
if (binarySocket
|
|
14974
|
+
&& typeof WebSocket !== 'undefined'
|
|
14975
|
+
&& (binarySocket.readyState === WebSocket.OPEN || binarySocket.readyState === WebSocket.CONNECTING)) {
|
|
14976
|
+
lastTick = timestamp;
|
|
14977
|
+
bodyView._remoteFleetFrameLoopRaf = requestRemoteFleetFrameLoopFrame(loop);
|
|
14978
|
+
return;
|
|
14979
|
+
}
|
|
14980
|
+
|
|
14967
14981
|
if (timestamp - lastTick >= intervalMs && bodyView._remoteFleetFrameRefreshInFlight !== true) {
|
|
14968
14982
|
lastTick = timestamp;
|
|
14969
14983
|
bodyView._remoteFleetFrameRefreshInFlight = true;
|
|
@@ -15993,29 +16007,6 @@
|
|
|
15993
16007
|
`;
|
|
15994
16008
|
preview.appendChild(placeholder);
|
|
15995
16009
|
}
|
|
15996
|
-
const previewBadge = document.createElement('span');
|
|
15997
|
-
previewBadge.dataset.remoteFleetFrameBadge = 'true';
|
|
15998
|
-
previewBadge.textContent = hasLiveFrame
|
|
15999
|
-
? `LIVE ${previewAt ? formatRemoteFleetAge(previewAt) : ''}`.trim()
|
|
16000
|
-
: (previewAt ? formatRemoteFleetAge(previewAt) : (hubStatus === 'online' ? 'Pinned' : 'Hub offline'));
|
|
16001
|
-
previewBadge.style.cssText = `
|
|
16002
|
-
position: absolute;
|
|
16003
|
-
left: 7px;
|
|
16004
|
-
bottom: 7px;
|
|
16005
|
-
max-width: calc(100% - 14px);
|
|
16006
|
-
padding: 4px 7px;
|
|
16007
|
-
border-radius: 999px;
|
|
16008
|
-
background: ${hasLiveFrame ? 'rgba(220, 38, 38, 0.84)' : 'rgba(15, 23, 42, 0.74)'};
|
|
16009
|
-
color: #e2e8f0;
|
|
16010
|
-
font-size: 9px;
|
|
16011
|
-
font-weight: 950;
|
|
16012
|
-
line-height: 1;
|
|
16013
|
-
overflow: hidden;
|
|
16014
|
-
text-overflow: ellipsis;
|
|
16015
|
-
white-space: nowrap;
|
|
16016
|
-
letter-spacing: 0;
|
|
16017
|
-
`;
|
|
16018
|
-
preview.appendChild(previewBadge);
|
|
16019
16010
|
bodyView.appendChild(preview);
|
|
16020
16011
|
|
|
16021
16012
|
const stats = document.createElement('div');
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"mainAssemblyName": "MindExecution.Web",
|
|
3
3
|
"resources": {
|
|
4
|
-
"hash": "sha256-
|
|
4
|
+
"hash": "sha256-o8W1U0lBmrbs5unXcwG+/IEh2KogSA7Rk3MqjQnNfGU=",
|
|
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.jc0g7fvyzv.dll": "MindExecution.Plugins.Directory.dll",
|
|
132
132
|
"MindExecution.Plugins.PlanMaster.2sugb66h95.dll": "MindExecution.Plugins.PlanMaster.dll",
|
|
133
133
|
"MindExecution.Plugins.YouTube.vy0nsbcoo4.dll": "MindExecution.Plugins.YouTube.dll",
|
|
134
|
-
"MindExecution.Shared.
|
|
134
|
+
"MindExecution.Shared.uwkrc41o75.dll": "MindExecution.Shared.dll",
|
|
135
135
|
"MindExecution.Web.994sb3nzuy.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.3zom09ir6j.dll": "sha256-78vR2SNkF9FwdQv0fEItSyz4HzgyzwGUROJAnpripPA=",
|
|
283
283
|
"MindExecution.Plugins.Concept.lxunwcdjgq.dll": "sha256-WVyvjaxW6UoGQvGW0icChLsXwYVCcdn0BLTftzlkCKc=",
|
|
284
284
|
"MindExecution.Plugins.PlanMaster.2sugb66h95.dll": "sha256-Lp0AieRHDo2H4Yl0Il1pPruy9nFUHJsX9t1AblXGD6Y=",
|
|
285
|
-
"MindExecution.Shared.
|
|
285
|
+
"MindExecution.Shared.uwkrc41o75.dll": "sha256-YjF+7VcGo3+nB0fsa2nh3AiaRyrHS1AL6d6Mq4RWKqo=",
|
|
286
286
|
"MindExecution.Web.994sb3nzuy.dll": "sha256-jQshtLmhEC4xenzdD9PqHjaCuKvJf/frzaKBi2weDAg="
|
|
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=20260616-
|
|
11
|
-
<link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-
|
|
10
|
+
<link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260616-mdm-ws-fast-v575" />
|
|
11
|
+
<link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-mdm-ws-fast-v575" />
|
|
12
12
|
<!-- ?쇄뼹??Font Awesome (local) ?쇄뼹??-->
|
|
13
13
|
<link rel="stylesheet" href="_content/MindExecution.Shared/lib/font-awesome/css/all.min.css" />
|
|
14
14
|
<!-- ?꿎뼯??-->
|
|
@@ -579,7 +579,7 @@
|
|
|
579
579
|
}
|
|
580
580
|
|
|
581
581
|
const base = '_content/MindExecution.Shared/js/';
|
|
582
|
-
const scriptVersion = '20260616-
|
|
582
|
+
const scriptVersion = '20260616-mdm-ws-fast-v575';
|
|
583
583
|
const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
|
|
584
584
|
console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
|
|
585
585
|
const criticalScripts = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
self.assetsManifest = {
|
|
2
|
-
"version": "
|
|
2
|
+
"version": "vnLn5hRl",
|
|
3
3
|
"assets": [
|
|
4
4
|
{
|
|
5
5
|
"hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
"hash": "sha256-
|
|
89
|
+
"hash": "sha256-eGHfAJwh8N3LmDZTr3/anW3CVrYCEYowGfK4N8KPzik=",
|
|
90
90
|
"url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
|
|
91
91
|
},
|
|
92
92
|
{
|
|
@@ -442,8 +442,8 @@
|
|
|
442
442
|
"url": "_framework/MindExecution.Plugins.YouTube.vy0nsbcoo4.dll"
|
|
443
443
|
},
|
|
444
444
|
{
|
|
445
|
-
"hash": "sha256-
|
|
446
|
-
"url": "_framework/MindExecution.Shared.
|
|
445
|
+
"hash": "sha256-YjF+7VcGo3+nB0fsa2nh3AiaRyrHS1AL6d6Mq4RWKqo=",
|
|
446
|
+
"url": "_framework/MindExecution.Shared.uwkrc41o75.dll"
|
|
447
447
|
},
|
|
448
448
|
{
|
|
449
449
|
"hash": "sha256-jQshtLmhEC4xenzdD9PqHjaCuKvJf/frzaKBi2weDAg=",
|
|
@@ -770,7 +770,7 @@
|
|
|
770
770
|
"url": "_framework/Websocket.Client.vapounvmnl.dll"
|
|
771
771
|
},
|
|
772
772
|
{
|
|
773
|
-
"hash": "sha256-
|
|
773
|
+
"hash": "sha256-lL/HCjpHZ3+EWJkTvNj72sH8NLl/eSU2UZDTr6bP99c=",
|
|
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-IYebw41Eytj+5xl0lRltZ9R7wOwuHg0mlcgar2MiudE=",
|
|
838
838
|
"url": "index.html"
|
|
839
839
|
},
|
|
840
840
|
{
|