@mindexec/cli 0.2.445 → 0.2.447
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/README.md +2 -2
- package/package.json +6 -3
- package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
- package/remote-hub.js +171 -6
- package/scripts/remote-fast-live-rate-smoke.mjs +1 -0
- package/scripts/remote-fleet-render-smoke.mjs +74 -36
- package/scripts/remote-hub-smoke.mjs +7 -0
- package/server.js +2 -2
- package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1 -3
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +199 -100
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-hotpath.js +902 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +186 -360
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +124 -179
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +167 -24
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +96 -74
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +66 -30
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-remote-codecs.js +6 -3
- package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +35 -2
- package/wwwroot/_framework/{MindExecution.Core.4moacskf64.dll → MindExecution.Core.mswl4wlkm9.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.pa251cfsz4.dll → MindExecution.Kernel.zupeldfptg.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.dxyuwmxv1s.dll → MindExecution.Plugins.Admin.hq0vyqnqtn.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.7uj5erqhhn.dll → MindExecution.Plugins.Business.pucw3o7rno.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.108rmsxc96.dll → MindExecution.Plugins.Concept.557tzzkrrx.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.ccsy3g0fe5.dll → MindExecution.Plugins.Directory.zh1ddg1fn6.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.1vjpuoe9ff.dll → MindExecution.Plugins.PlanMaster.0z9pzynpu9.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.q8ml98phj9.dll → MindExecution.Plugins.YouTube.y8g77r54fa.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.2pywq19zjy.dll → MindExecution.Shared.1ymsdbwid2.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Web.qucbls1wfr.dll → MindExecution.Web.bb1txupuv0.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/index.html +2 -1
- package/wwwroot/service-worker-assets.js +36 -32
- package/wwwroot/service-worker.js +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ experiments. By default it binds to loopback only:
|
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
npx @mindexec/cli
|
|
55
|
-
npx @mindexec/remote connect --manager 127.0.0.1:
|
|
55
|
+
npx @mindexec/remote connect --manager 127.0.0.1:5198 --pair <pair-token>
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
Read the pair token from the protected local endpoint:
|
|
@@ -65,7 +65,7 @@ LAN/direct mode must be enabled explicitly by changing the RemoteHub bind host:
|
|
|
65
65
|
|
|
66
66
|
```powershell
|
|
67
67
|
$env:REMOTE_HUB_HOST="0.0.0.0"
|
|
68
|
-
$env:REMOTE_HUB_PORT="
|
|
68
|
+
$env:REMOTE_HUB_PORT="5198"
|
|
69
69
|
$env:REMOTE_HUB_PAIR_TOKEN="<strong-token>"
|
|
70
70
|
npx @mindexec/cli
|
|
71
71
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindexec/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.447",
|
|
4
4
|
"description": "MindExec local runtime and bridge CLI",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
66
|
-
"@mindexec/remote": "^0.1.
|
|
66
|
+
"@mindexec/remote": "^0.1.18",
|
|
67
67
|
"@openai/codex-sdk": "^0.144.1",
|
|
68
68
|
"chokidar": "^3.6.0",
|
|
69
69
|
"cors": "^2.8.5",
|
|
@@ -71,10 +71,13 @@
|
|
|
71
71
|
"ffmpeg-static": "^5.3.0",
|
|
72
72
|
"multer": "^2.2.0",
|
|
73
73
|
"playwright-core": "^1.53.0",
|
|
74
|
-
"sharp": "^0.
|
|
74
|
+
"sharp": "^0.35.3",
|
|
75
75
|
"web-tree-sitter": "^0.22.6",
|
|
76
76
|
"ws": "^8.16.0"
|
|
77
77
|
},
|
|
78
|
+
"overrides": {
|
|
79
|
+
"body-parser": "1.20.6"
|
|
80
|
+
},
|
|
78
81
|
"bin": {
|
|
79
82
|
"mindexec": "launch-bridge.cjs",
|
|
80
83
|
"mind-bridge": "launch-bridge.cjs"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/remote-hub.js
CHANGED
|
@@ -2,7 +2,7 @@ import net from 'net';
|
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
|
|
5
|
-
const DEFAULT_REMOTE_HUB_PORT =
|
|
5
|
+
const DEFAULT_REMOTE_HUB_PORT = 5198;
|
|
6
6
|
const DEFAULT_REMOTE_HUB_HOST = '0.0.0.0';
|
|
7
7
|
const DEFAULT_HEARTBEAT_MS = 5000;
|
|
8
8
|
const DEFAULT_AGENT_TASK_TIMEOUT_MS = 120000;
|
|
@@ -212,6 +212,30 @@ function getSupportedRemoteFrameModeProfiles() {
|
|
|
212
212
|
.map(serializeRemoteFrameModeProfile);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
function resolveDeviceRemoteFrameMode(device, requestedMode, fallbackMode = DEFAULT_REMOTE_FRAME_MODE) {
|
|
216
|
+
const requested = normalizeRemoteFrameMode(requestedMode, fallbackMode);
|
|
217
|
+
if (device?.frameModesAdvertised === false) {
|
|
218
|
+
return normalizeRemoteFrameMode(device?.frameProtocol?.defaultMode, DEFAULT_REMOTE_FRAME_MODE);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const supportedModes = new Set(
|
|
222
|
+
(Array.isArray(device?.frameModes) ? device.frameModes : [])
|
|
223
|
+
.filter(profile => profile?.implemented !== false)
|
|
224
|
+
.map(profile => normalizeRemoteFrameMode(profile?.mode || profile?.frameMode || profile?.profile, ''))
|
|
225
|
+
.filter(Boolean));
|
|
226
|
+
|
|
227
|
+
if (supportedModes.size === 0 || supportedModes.has(requested)) {
|
|
228
|
+
return requested;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const deviceDefault = normalizeRemoteFrameMode(device?.frameProtocol?.defaultMode, fallbackMode);
|
|
232
|
+
if (supportedModes.has(deviceDefault)) {
|
|
233
|
+
return deviceDefault;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return supportedModes.values().next().value || normalizeRemoteFrameMode(fallbackMode);
|
|
237
|
+
}
|
|
238
|
+
|
|
215
239
|
function buildRemoteFrameProtocolDescriptor() {
|
|
216
240
|
return {
|
|
217
241
|
protocol: REMOTE_FRAME_PROTOCOL,
|
|
@@ -1125,6 +1149,12 @@ export function createRemoteHub(options = {}) {
|
|
|
1125
1149
|
const taskBatches = new Map();
|
|
1126
1150
|
const sockets = new Map();
|
|
1127
1151
|
const allSockets = new Set();
|
|
1152
|
+
const sideChannelSockets = {
|
|
1153
|
+
input: new Map(),
|
|
1154
|
+
frame: new Map(),
|
|
1155
|
+
audio: new Map(),
|
|
1156
|
+
file: new Map()
|
|
1157
|
+
};
|
|
1128
1158
|
const duplicateDeviceLogAt = new Map();
|
|
1129
1159
|
let server = null;
|
|
1130
1160
|
let started = false;
|
|
@@ -1523,6 +1553,9 @@ export function createRemoteHub(options = {}) {
|
|
|
1523
1553
|
|
|
1524
1554
|
function closeExistingDeviceSocket(deviceId, nextSessionId) {
|
|
1525
1555
|
const existing = devices.get(deviceId);
|
|
1556
|
+
for (const channel of Object.keys(sideChannelSockets)) {
|
|
1557
|
+
closeSideChannel(existing, channel, 'replaced-by-new-session');
|
|
1558
|
+
}
|
|
1526
1559
|
if (!existing?.socket || existing.socket.destroyed) {
|
|
1527
1560
|
return;
|
|
1528
1561
|
}
|
|
@@ -1537,6 +1570,37 @@ export function createRemoteHub(options = {}) {
|
|
|
1537
1570
|
existing.socket.destroy();
|
|
1538
1571
|
}
|
|
1539
1572
|
|
|
1573
|
+
function closeSideChannel(device, channel, reason = `${channel}-socket-closed`) {
|
|
1574
|
+
const socketKey = `${channel}Socket`;
|
|
1575
|
+
const socket = device?.[socketKey];
|
|
1576
|
+
if (!socket) return;
|
|
1577
|
+
device[socketKey] = null;
|
|
1578
|
+
sideChannelSockets[channel]?.delete(socket);
|
|
1579
|
+
try {
|
|
1580
|
+
writeJsonLine(socket, { type: 'disconnect', channel, reason });
|
|
1581
|
+
} catch {
|
|
1582
|
+
// Best-effort notice before closing the side channel.
|
|
1583
|
+
}
|
|
1584
|
+
try {
|
|
1585
|
+
socket.destroy?.();
|
|
1586
|
+
} catch {
|
|
1587
|
+
// Ignore close failures.
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function detachSideChannel(socket, channel, reason = `${channel}-socket-closed`) {
|
|
1592
|
+
const channelMap = sideChannelSockets[channel];
|
|
1593
|
+
const deviceId = channelMap?.get(socket);
|
|
1594
|
+
channelMap?.delete(socket);
|
|
1595
|
+
if (!deviceId) return;
|
|
1596
|
+
const device = devices.get(deviceId);
|
|
1597
|
+
const socketKey = `${channel}Socket`;
|
|
1598
|
+
if (!device || device[socketKey] !== socket) return;
|
|
1599
|
+
device[socketKey] = null;
|
|
1600
|
+
device[`${channel}LastSeenAt`] = new Date().toISOString();
|
|
1601
|
+
emitRemoteEvent(`Remote${channel[0].toUpperCase()}${channel.slice(1)}SocketDisconnected`, device, { reason });
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1540
1604
|
function getDeviceActivityMs(device) {
|
|
1541
1605
|
return Math.max(
|
|
1542
1606
|
Date.parse(device?.lastSeenAt || '') || 0,
|
|
@@ -1841,6 +1905,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1841
1905
|
protocolVersion: Number.isFinite(Number(hello.protocolVersion)) ? Math.max(1, Math.floor(Number(hello.protocolVersion))) : 1,
|
|
1842
1906
|
frameProtocol: frameProtocol || buildRemoteFrameProtocolDescriptor(),
|
|
1843
1907
|
frameModes: frameModes.length > 0 ? frameModes : getSupportedRemoteFrameModeProfiles(),
|
|
1908
|
+
frameModesAdvertised: frameModes.length > 0,
|
|
1844
1909
|
capabilities,
|
|
1845
1910
|
connected: true,
|
|
1846
1911
|
connectedAt: now,
|
|
@@ -1853,6 +1918,10 @@ export function createRemoteHub(options = {}) {
|
|
|
1853
1918
|
status: {},
|
|
1854
1919
|
latestThumbnail: null,
|
|
1855
1920
|
latestLiveFrame: null,
|
|
1921
|
+
inputSocket: null,
|
|
1922
|
+
frameSocket: null,
|
|
1923
|
+
audioSocket: null,
|
|
1924
|
+
fileSocket: null,
|
|
1856
1925
|
recentFramePayloads: {
|
|
1857
1926
|
thumbnail: [],
|
|
1858
1927
|
live: []
|
|
@@ -1885,6 +1954,40 @@ export function createRemoteHub(options = {}) {
|
|
|
1885
1954
|
return device;
|
|
1886
1955
|
}
|
|
1887
1956
|
|
|
1957
|
+
function attachSideChannel(socket, hello, channel) {
|
|
1958
|
+
const deviceId = normalizeDeviceId(hello.deviceId);
|
|
1959
|
+
const device = devices.get(deviceId);
|
|
1960
|
+
if (!device || !device.connected || !device.socket || device.socket.destroyed) {
|
|
1961
|
+
writeJsonLine(socket, { type: 'error', error: 'device-not-connected' });
|
|
1962
|
+
socket.destroy();
|
|
1963
|
+
return null;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
closeSideChannel(device, channel, `replaced-by-new-${channel}-socket`);
|
|
1967
|
+
const now = new Date().toISOString();
|
|
1968
|
+
device[`${channel}Socket`] = socket;
|
|
1969
|
+
device[`${channel}ConnectedAt`] = now;
|
|
1970
|
+
device[`${channel}LastSeenAt`] = now;
|
|
1971
|
+
sideChannelSockets[channel].set(socket, deviceId);
|
|
1972
|
+
writeJsonLine(socket, {
|
|
1973
|
+
type: 'welcome',
|
|
1974
|
+
channel,
|
|
1975
|
+
protocol: REMOTE_AGENT_PROTOCOL,
|
|
1976
|
+
protocolVersion: REMOTE_PROTOCOL_VERSION,
|
|
1977
|
+
sessionId: device.sessionId,
|
|
1978
|
+
deviceId,
|
|
1979
|
+
...(channel === 'frame'
|
|
1980
|
+
? {
|
|
1981
|
+
frameProtocol: buildRemoteFrameProtocolDescriptor(),
|
|
1982
|
+
frameModes: getSupportedRemoteFrameModeProfiles()
|
|
1983
|
+
}
|
|
1984
|
+
: {}),
|
|
1985
|
+
serverTime: now
|
|
1986
|
+
});
|
|
1987
|
+
emitRemoteEvent(`Remote${channel[0].toUpperCase()}${channel.slice(1)}SocketConnected`, device);
|
|
1988
|
+
return device;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1888
1991
|
function detachSocket(socket, reason = 'socket-closed') {
|
|
1889
1992
|
const deviceId = sockets.get(socket);
|
|
1890
1993
|
sockets.delete(socket);
|
|
@@ -1906,6 +2009,9 @@ export function createRemoteHub(options = {}) {
|
|
|
1906
2009
|
device.activeLiveStream.stoppedAt = device.disconnectedAt;
|
|
1907
2010
|
device.activeLiveStream.stopReason = reason;
|
|
1908
2011
|
}
|
|
2012
|
+
for (const channel of Object.keys(sideChannelSockets)) {
|
|
2013
|
+
closeSideChannel(device, channel, reason);
|
|
2014
|
+
}
|
|
1909
2015
|
failAllPendingTasks(device, 'device-disconnected');
|
|
1910
2016
|
logWarn('remote', `device disconnected ${device.deviceName} (${deviceId}): ${reason}`);
|
|
1911
2017
|
emitRemoteEvent('RemoteDeviceDisconnected', device, { reason });
|
|
@@ -2570,6 +2676,16 @@ export function createRemoteHub(options = {}) {
|
|
|
2570
2676
|
return;
|
|
2571
2677
|
}
|
|
2572
2678
|
|
|
2679
|
+
const channel = safeString(message.channel || message.Channel, 40).toLowerCase();
|
|
2680
|
+
if (Object.hasOwn(sideChannelSockets, channel)) {
|
|
2681
|
+
const device = attachSideChannel(socket, message, channel);
|
|
2682
|
+
if (!device) return;
|
|
2683
|
+
state.authenticated = true;
|
|
2684
|
+
state.device = device;
|
|
2685
|
+
state.sideChannel = channel;
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2573
2689
|
const device = attachDevice(socket, message);
|
|
2574
2690
|
if (!device) {
|
|
2575
2691
|
return;
|
|
@@ -2586,6 +2702,17 @@ export function createRemoteHub(options = {}) {
|
|
|
2586
2702
|
return;
|
|
2587
2703
|
}
|
|
2588
2704
|
|
|
2705
|
+
if (state.sideChannel) {
|
|
2706
|
+
device[`${state.sideChannel}LastSeenAt`] = new Date().toISOString();
|
|
2707
|
+
if (state.sideChannel === 'input' && message.type === 'input.error') {
|
|
2708
|
+
emitRemoteEvent('RemoteInputError', device, {
|
|
2709
|
+
inputSeq: Number(message.inputSeq || 0) || 0,
|
|
2710
|
+
error: safeString(message.error, 600) || 'remote-input-failed'
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2589
2716
|
device.counters.messagesReceived += 1;
|
|
2590
2717
|
device.lastSeenAt = new Date().toISOString();
|
|
2591
2718
|
|
|
@@ -2698,7 +2825,11 @@ export function createRemoteHub(options = {}) {
|
|
|
2698
2825
|
const detach = reason => {
|
|
2699
2826
|
allSockets.delete(socket);
|
|
2700
2827
|
clearTimeout(helloTimer);
|
|
2701
|
-
|
|
2828
|
+
if (state.sideChannel) {
|
|
2829
|
+
detachSideChannel(socket, state.sideChannel, reason);
|
|
2830
|
+
} else {
|
|
2831
|
+
detachSocket(socket, reason);
|
|
2832
|
+
}
|
|
2702
2833
|
};
|
|
2703
2834
|
|
|
2704
2835
|
socket.onCloseMessage = reason => detach(reason || 'websocket-closed');
|
|
@@ -2867,12 +2998,20 @@ export function createRemoteHub(options = {}) {
|
|
|
2867
2998
|
socket.on('close', () => {
|
|
2868
2999
|
allSockets.delete(socket);
|
|
2869
3000
|
clearTimeout(helloTimer);
|
|
2870
|
-
|
|
3001
|
+
if (state.sideChannel) {
|
|
3002
|
+
detachSideChannel(socket, state.sideChannel);
|
|
3003
|
+
} else {
|
|
3004
|
+
detachSocket(socket);
|
|
3005
|
+
}
|
|
2871
3006
|
});
|
|
2872
3007
|
socket.on('error', err => {
|
|
2873
3008
|
allSockets.delete(socket);
|
|
2874
3009
|
clearTimeout(helloTimer);
|
|
2875
|
-
|
|
3010
|
+
if (state.sideChannel) {
|
|
3011
|
+
detachSideChannel(socket, state.sideChannel, err?.message || 'socket-error');
|
|
3012
|
+
} else {
|
|
3013
|
+
detachSocket(socket, err?.message || 'socket-error');
|
|
3014
|
+
}
|
|
2876
3015
|
});
|
|
2877
3016
|
}
|
|
2878
3017
|
|
|
@@ -2936,6 +3075,7 @@ export function createRemoteHub(options = {}) {
|
|
|
2936
3075
|
}
|
|
2937
3076
|
|
|
2938
3077
|
sockets.clear();
|
|
3078
|
+
Object.values(sideChannelSockets).forEach(channelMap => channelMap.clear());
|
|
2939
3079
|
if (!server) {
|
|
2940
3080
|
started = false;
|
|
2941
3081
|
return;
|
|
@@ -3058,10 +3198,29 @@ export function createRemoteHub(options = {}) {
|
|
|
3058
3198
|
return { ok: false, error: 'missing-input-type' };
|
|
3059
3199
|
}
|
|
3060
3200
|
|
|
3201
|
+
const inputSocket = device.inputSocket;
|
|
3202
|
+
if (inputSocket && !inputSocket.destroyed) {
|
|
3203
|
+
const sent = writeJsonLine(inputSocket, {
|
|
3204
|
+
type: 'input.control',
|
|
3205
|
+
payload: {
|
|
3206
|
+
...normalized,
|
|
3207
|
+
hubForwardedAtEpochMs: Date.now(),
|
|
3208
|
+
issuedAt: normalized.issuedAt || new Date().toISOString()
|
|
3209
|
+
}
|
|
3210
|
+
});
|
|
3211
|
+
if (sent) {
|
|
3212
|
+
device.counters.commandsSent += 1;
|
|
3213
|
+
device.inputLastSeenAt = new Date().toISOString();
|
|
3214
|
+
return { ok: true, inputSocket: true };
|
|
3215
|
+
}
|
|
3216
|
+
detachSideChannel(inputSocket, 'input', 'input-socket-write-failed');
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3061
3219
|
return sendCommand(deviceId, {
|
|
3062
3220
|
command: 'input.control',
|
|
3063
3221
|
payload: {
|
|
3064
3222
|
...normalized,
|
|
3223
|
+
hubForwardedAtEpochMs: Date.now(),
|
|
3065
3224
|
issuedAt: normalized.issuedAt || new Date().toISOString()
|
|
3066
3225
|
}
|
|
3067
3226
|
});
|
|
@@ -3285,7 +3444,10 @@ export function createRemoteHub(options = {}) {
|
|
|
3285
3444
|
const now = new Date().toISOString();
|
|
3286
3445
|
const streamId = safeString(options.streamId, 128) || `live-${Date.now()}`;
|
|
3287
3446
|
const streamPurpose = safeString(options.streamPurpose, 32).toLowerCase();
|
|
3288
|
-
const requestedMode =
|
|
3447
|
+
const requestedMode = resolveDeviceRemoteFrameMode(
|
|
3448
|
+
device,
|
|
3449
|
+
options.mode || (streamPurpose === 'control' ? 'mode3-h264-hw' : 'mode2-lzo'),
|
|
3450
|
+
streamPurpose === 'control' ? 'mode3-h264-hw' : 'mode2-lzo');
|
|
3289
3451
|
const fps = clampNumber(options.fps, 1, 30, streamPurpose === 'control' ? 30 : 20);
|
|
3290
3452
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
3291
3453
|
const transfer = buildRemoteFrameTransferDescriptor(requestedMode, streamPurpose === 'control' ? 'mode3-h264-hw' : 'mode2-lzo');
|
|
@@ -3342,7 +3504,10 @@ export function createRemoteHub(options = {}) {
|
|
|
3342
3504
|
const now = new Date().toISOString();
|
|
3343
3505
|
const streamId = safeString(options.streamId, 128) || `live-${Date.now()}`;
|
|
3344
3506
|
const streamPurpose = safeString(options.streamPurpose, 32).toLowerCase();
|
|
3345
|
-
const requestedMode =
|
|
3507
|
+
const requestedMode = resolveDeviceRemoteFrameMode(
|
|
3508
|
+
device,
|
|
3509
|
+
options.mode || (streamPurpose === 'control' ? 'mode3-h264-hw' : 'mode2-lzo'),
|
|
3510
|
+
streamPurpose === 'control' ? 'mode3-h264-hw' : 'mode2-lzo');
|
|
3346
3511
|
const fps = clampNumber(options.fps, 1, 30, streamPurpose === 'control' ? 30 : 20);
|
|
3347
3512
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
3348
3513
|
const transfer = buildRemoteFrameTransferDescriptor(requestedMode, streamPurpose === 'control' ? 'mode3-h264-hw' : 'mode2-lzo');
|
|
@@ -293,6 +293,7 @@ async function main() {
|
|
|
293
293
|
const live = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices/${encodeURIComponent(device.deviceId)}/live/start`, {
|
|
294
294
|
method: 'POST',
|
|
295
295
|
body: JSON.stringify({
|
|
296
|
+
mode: 'remote-fast',
|
|
296
297
|
fps: REQUESTED_FPS,
|
|
297
298
|
maxWidth: 960,
|
|
298
299
|
maxHeight: 540,
|
|
@@ -435,7 +435,9 @@ function projectDevice(device) {
|
|
|
435
435
|
|
|
436
436
|
async function loadCss3DManager() {
|
|
437
437
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
438
|
+
const hotPathModulePath = path.resolve(__dirname, '../../MindExecution.Shared/wwwroot/js/mind-map-css3d-hotpath.js');
|
|
438
439
|
const managerPath = path.resolve(__dirname, '../../MindExecution.Shared/wwwroot/js/mind-map-css3d-manager.js');
|
|
440
|
+
const hotPathSource = await fs.readFile(hotPathModulePath, 'utf8');
|
|
439
441
|
const source = await fs.readFile(managerPath, 'utf8');
|
|
440
442
|
const document = new MiniDocument();
|
|
441
443
|
const objectUrlCalls = [];
|
|
@@ -558,11 +560,18 @@ async function loadCss3DManager() {
|
|
|
558
560
|
textOverlayInvalidations.push({ module, nodeId, reason, fallback: true });
|
|
559
561
|
}
|
|
560
562
|
},
|
|
563
|
+
MindExecRemoteCodecs: {
|
|
564
|
+
async decodeH264Frame(canvas, frame, onFrame) {
|
|
565
|
+
onFrame?.(canvas, frame);
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
},
|
|
561
569
|
Promise
|
|
562
570
|
};
|
|
563
571
|
context.window = context;
|
|
564
572
|
context.self = context;
|
|
565
573
|
|
|
574
|
+
vm.runInNewContext(hotPathSource, context, { filename: hotPathModulePath });
|
|
566
575
|
vm.runInNewContext(source, context, { filename: managerPath });
|
|
567
576
|
return {
|
|
568
577
|
manager: context.window.MindMapCss3DManager,
|
|
@@ -584,6 +593,15 @@ function wait(ms = 0) {
|
|
|
584
593
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
585
594
|
}
|
|
586
595
|
|
|
596
|
+
async function waitFor(predicate, timeoutMs = 500, intervalMs = 10) {
|
|
597
|
+
const deadline = Date.now() + Math.max(1, Number(timeoutMs || 500));
|
|
598
|
+
while (Date.now() < deadline) {
|
|
599
|
+
if (predicate()) return true;
|
|
600
|
+
await wait(intervalMs);
|
|
601
|
+
}
|
|
602
|
+
return predicate();
|
|
603
|
+
}
|
|
604
|
+
|
|
587
605
|
function encodeRemoteBinaryFrame(metadata, payload = new Uint8Array([0xff, 0xd8, 0xff, 0xd9])) {
|
|
588
606
|
const metaBytes = new TextEncoder().encode(JSON.stringify(metadata));
|
|
589
607
|
const payloadBytes = payload instanceof Uint8Array ? payload : new Uint8Array(payload);
|
|
@@ -598,7 +616,7 @@ function encodeRemoteBinaryFrame(metadata, payload = new Uint8Array([0xff, 0xd8,
|
|
|
598
616
|
|
|
599
617
|
function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTaskBatches = [], lastError = '', nodeId = 'remote-fleet-render-smoke') {
|
|
600
618
|
const connected = devices.filter(device => device.Connected).length;
|
|
601
|
-
const endpoint = hubStatus.agentEndpoint || '127.0.0.1:
|
|
619
|
+
const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5198';
|
|
602
620
|
const pairToken = hubStatus.pairToken || 'render-smoke-token';
|
|
603
621
|
const hostTargetState = hubStatus.hostTargetActive
|
|
604
622
|
? (hubStatus.hostTargetNodeId === nodeId ? 'hosting' : 'other-monitor')
|
|
@@ -665,7 +683,7 @@ function createRemoteFleetTemplateShell(document, focusDeviceId = '', nodeId = '
|
|
|
665
683
|
}
|
|
666
684
|
|
|
667
685
|
function buildDeviceNode(device, hubStatus) {
|
|
668
|
-
const endpoint = hubStatus.agentEndpoint || '127.0.0.1:
|
|
686
|
+
const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5198';
|
|
669
687
|
return {
|
|
670
688
|
id: 'remote-device-render-smoke',
|
|
671
689
|
contentType: 'memo',
|
|
@@ -1060,6 +1078,13 @@ try {
|
|
|
1060
1078
|
binaryDrawCalls.some(call => call.op === 'clearRect' || call.op === 'fillRect'),
|
|
1061
1079
|
false,
|
|
1062
1080
|
'binary Blob frame paint must not clear/fill the canvas before drawing');
|
|
1081
|
+
assert.ok(
|
|
1082
|
+
Number(moduleRef._forceUpdateFrames || 0) >= 2,
|
|
1083
|
+
'binary live frame commits must wake the passive MDM CSS3D frame loop');
|
|
1084
|
+
assert.equal(moduleRef._lastAnimationWakeReason, 'remote-live-css3d-frame');
|
|
1085
|
+
assert.ok(
|
|
1086
|
+
diagnostics.runtimeTrace.some(event => event.type === 'remote.frame.css3dWake'),
|
|
1087
|
+
'binary live frame commits must trace the MDM CSS3D wake path');
|
|
1063
1088
|
const binaryCanvasBeforeRerender = binaryCanvas;
|
|
1064
1089
|
|
|
1065
1090
|
const liveOverlayCard = document.createElement('div');
|
|
@@ -1073,12 +1098,28 @@ try {
|
|
|
1073
1098
|
moduleRef._lastAnimationWakeReason = '';
|
|
1074
1099
|
|
|
1075
1100
|
await wait(20);
|
|
1076
|
-
const liveWs = diagnostics.webSocketConnections
|
|
1077
|
-
|
|
1078
|
-
assert.
|
|
1101
|
+
const liveWs = diagnostics.webSocketConnections.find(connection =>
|
|
1102
|
+
String(connection?.url || '').includes('/api/remote/atlas/ws'));
|
|
1103
|
+
assert.ok(
|
|
1104
|
+
liveWs,
|
|
1105
|
+
`expected MDM render to open the Mode 4 Atlas WebSocket: ${diagnostics.webSocketConnections.map(connection => connection.url).join(', ')}`
|
|
1106
|
+
);
|
|
1107
|
+
assert.equal(liveWs.readyState, 1, 'Mode 4 Atlas WebSocket should be open before rerender');
|
|
1079
1108
|
const wsCountBeforeRerender = diagnostics.webSocketConnections.length;
|
|
1080
1109
|
const wsCloseCountBeforeRerender = liveWs.closeCount;
|
|
1081
1110
|
const websocketFrameSeq = 10001;
|
|
1111
|
+
liveWs.onmessage?.({
|
|
1112
|
+
data: JSON.stringify({
|
|
1113
|
+
type: 'Mode4AtlasLayout',
|
|
1114
|
+
tiles: [{
|
|
1115
|
+
deviceId: patchTarget.DeviceId,
|
|
1116
|
+
x: 0,
|
|
1117
|
+
y: 0,
|
|
1118
|
+
width: 64,
|
|
1119
|
+
height: 36
|
|
1120
|
+
}]
|
|
1121
|
+
})
|
|
1122
|
+
});
|
|
1082
1123
|
liveWs.onmessage?.({
|
|
1083
1124
|
data: new Blob([encodeRemoteBinaryFrame({
|
|
1084
1125
|
type: 'remote.frame.binary',
|
|
@@ -1088,36 +1129,36 @@ try {
|
|
|
1088
1129
|
streamId: 'render-smoke-ws-live',
|
|
1089
1130
|
contentHash: `render-smoke-ws-live-${websocketFrameSeq}`,
|
|
1090
1131
|
mimeType: 'image/jpeg',
|
|
1132
|
+
frameMode: 'mode4-h264-atlas',
|
|
1091
1133
|
width: 64,
|
|
1092
1134
|
height: 36,
|
|
1093
1135
|
capturedAt: new Date().toISOString(),
|
|
1094
1136
|
receivedAt: new Date().toISOString()
|
|
1095
1137
|
})])
|
|
1096
1138
|
});
|
|
1097
|
-
await
|
|
1139
|
+
await waitFor(
|
|
1140
|
+
() => patchPreview.dataset.remoteFleetFrameSeq === String(websocketFrameSeq)
|
|
1141
|
+
);
|
|
1098
1142
|
assert.equal(
|
|
1099
1143
|
patchPreview.dataset.remoteFleetFrameSeq,
|
|
1100
1144
|
String(websocketFrameSeq),
|
|
1101
1145
|
JSON.stringify(diagnostics.runtimeTrace.slice(-12)));
|
|
1102
|
-
await wait(10);
|
|
1103
|
-
assert.ok(
|
|
1104
|
-
Number(moduleRef._forceUpdateFrames || 0) >= 2,
|
|
1105
|
-
'binary live frame commits must wake the passive MDM CSS3D frame loop');
|
|
1106
|
-
assert.equal(moduleRef._lastAnimationWakeReason, 'remote-live-css3d-frame');
|
|
1107
1146
|
assert.ok(
|
|
1108
|
-
diagnostics.runtimeTrace.some(event =>
|
|
1109
|
-
|
|
1147
|
+
diagnostics.runtimeTrace.some(event =>
|
|
1148
|
+
event.type === 'remote.atlas.painted' &&
|
|
1149
|
+
Number(event.frameSeq || 0) === websocketFrameSeq),
|
|
1150
|
+
'Mode 4 Atlas frame must paint its configured device tile');
|
|
1110
1151
|
|
|
1111
1152
|
manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
|
|
1112
1153
|
await wait(30);
|
|
1113
1154
|
assert.equal(
|
|
1114
1155
|
liveWs.closeCount,
|
|
1115
1156
|
wsCloseCountBeforeRerender,
|
|
1116
|
-
'MDM rerender must not close the active
|
|
1157
|
+
'MDM rerender must not close the active Mode 4 Atlas WebSocket');
|
|
1117
1158
|
assert.equal(
|
|
1118
1159
|
diagnostics.webSocketConnections.length,
|
|
1119
1160
|
wsCountBeforeRerender,
|
|
1120
|
-
'MDM rerender must reuse the active
|
|
1161
|
+
'MDM rerender must reuse the active Mode 4 Atlas WebSocket instead of opening a replacement');
|
|
1121
1162
|
const rerenderedPatchPreview = bodyView
|
|
1122
1163
|
.querySelector(`article[data-device-id="${patchTarget.DeviceId}"]`)
|
|
1123
1164
|
?.querySelector('[data-remote-fleet-device-preview="tile"]');
|
|
@@ -1148,15 +1189,11 @@ try {
|
|
|
1148
1189
|
.querySelector(`article[data-device-id="${patchTarget.DeviceId}"]`)
|
|
1149
1190
|
?.querySelector('[data-remote-fleet-device-preview="tile"]');
|
|
1150
1191
|
assert.ok(otherBoardPatchPreview);
|
|
1151
|
-
assert.
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
'a different-board MDM should prime from the device-global latest binary frame cache');
|
|
1155
|
-
assert.equal(
|
|
1156
|
-
otherBoardPatchPreview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]')?.style.display,
|
|
1157
|
-
'block');
|
|
1192
|
+
assert.ok(
|
|
1193
|
+
diagnostics.webSocketConnections.length > wsCountBeforeRerender,
|
|
1194
|
+
'a different-board MDM must own a separate Mode 4 Atlas session');
|
|
1158
1195
|
assert.ok(diagnostics.runtimeTrace.some(event =>
|
|
1159
|
-
|
|
1196
|
+
event.type === 'remote.atlas.wsOpen'
|
|
1160
1197
|
&& event.nodeId === otherBoardNodeId));
|
|
1161
1198
|
otherBoardShell.remove();
|
|
1162
1199
|
|
|
@@ -1260,18 +1297,17 @@ try {
|
|
|
1260
1297
|
}
|
|
1261
1298
|
})
|
|
1262
1299
|
.filter(Boolean);
|
|
1263
|
-
const
|
|
1264
|
-
message.type === '
|
|
1265
|
-
&& message.nodeId === 'remote-fleet-render-smoke'
|
|
1266
|
-
&& message.autoStartLive === true);
|
|
1300
|
+
const atlasConfigure = subscribeMessages.find(message =>
|
|
1301
|
+
message.type === 'configure');
|
|
1267
1302
|
assert.ok(diagnostics.fetchCalls.some(call => call.url.includes('/api/status?remoteFrames=ws')));
|
|
1268
|
-
assert.ok(diagnostics.webSocketConnections.length >= 1, 'expected MDM render to open
|
|
1269
|
-
assert.ok(
|
|
1270
|
-
assert.equal(
|
|
1271
|
-
assert.equal(
|
|
1272
|
-
assert.equal(
|
|
1273
|
-
assert.equal(
|
|
1274
|
-
assert.
|
|
1303
|
+
assert.ok(diagnostics.webSocketConnections.length >= 1, 'expected MDM render to open Mode 4 Atlas WebSocket');
|
|
1304
|
+
assert.ok(atlasConfigure, 'expected MDM render to configure the Mode 4 Atlas WebSocket');
|
|
1305
|
+
assert.equal(atlasConfigure.fps, 20);
|
|
1306
|
+
assert.equal(atlasConfigure.width, 1920);
|
|
1307
|
+
assert.equal(atlasConfigure.height, 1080);
|
|
1308
|
+
assert.equal(atlasConfigure.tileWidth, 320);
|
|
1309
|
+
assert.equal(atlasConfigure.tileHeight, 180);
|
|
1310
|
+
assert.ok(atlasConfigure.deviceIds.length > 1, 'expected visible connected devices in Atlas configuration');
|
|
1275
1311
|
assert.ok(diagnostics.runtimeTrace.some(event => event.type === 'remote.live.wsAutoStartRequested'));
|
|
1276
1312
|
assert.equal(liveStartCalls.length, 0, 'WebSocket live path must not call DotNet live-start fallback');
|
|
1277
1313
|
await wait(320);
|
|
@@ -1373,10 +1409,12 @@ try {
|
|
|
1373
1409
|
&& message.type === 'subscribe'
|
|
1374
1410
|
&& message.nodeId === 'remote-device-render-smoke'
|
|
1375
1411
|
&& message.autoStartLive === true
|
|
1376
|
-
&& message.fps ===
|
|
1412
|
+
&& message.fps === 30
|
|
1413
|
+
&& message.mode === 'mode3-h264-hw'
|
|
1414
|
+
&& message.streamPurpose === 'control'
|
|
1377
1415
|
&& Array.isArray(message.deviceIds)
|
|
1378
1416
|
&& message.deviceIds.includes(focusedDevice.DeviceId));
|
|
1379
|
-
assert.ok(deviceControlSubscribe, 'RemoteFleetDevice node should subscribe to
|
|
1417
|
+
assert.ok(deviceControlSubscribe, 'RemoteFleetDevice node should subscribe to 30fps Mode 3 control frames');
|
|
1380
1418
|
const deviceControlWs = diagnostics.webSocketConnections
|
|
1381
1419
|
.filter(ws => ws.url.includes('/api/remote/frames/ws'))
|
|
1382
1420
|
.at(-1);
|
|
@@ -22,6 +22,13 @@ function wait(ms) {
|
|
|
22
22
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const defaultHub = createRemoteHub({
|
|
26
|
+
env: {
|
|
27
|
+
MINDEXEC_REMOTE_HUB: '0'
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
assert.equal(defaultHub.getStatus().port, 5198, 'MindExecution RemoteHub must not reuse the LiveDesk 5197 port');
|
|
31
|
+
|
|
25
32
|
async function waitFor(predicate, timeoutMs = 3000) {
|
|
26
33
|
const startedAt = Date.now();
|
|
27
34
|
while (Date.now() - startedAt < timeoutMs) {
|
package/server.js
CHANGED
|
@@ -4354,7 +4354,7 @@ function appendRemoteAgentOutput(stream, chunk, stateConnectionKey = '') {
|
|
|
4354
4354
|
const text = chunk.toString();
|
|
4355
4355
|
const key = stream === 'stderr' ? 'stderrTail' : 'stdoutTail';
|
|
4356
4356
|
remoteAgentState[key] = String((remoteAgentState[key] || '') + text).slice(-REMOTE_AGENT_STDIO_TAIL_CHARS);
|
|
4357
|
-
if (/Connected to RemoteHub/i.test(text)) {
|
|
4357
|
+
if (/Connected to (?:RemoteHub|LiveDesk Hub)/i.test(text)) {
|
|
4358
4358
|
remoteAgentState.ready = true;
|
|
4359
4359
|
remoteAgentState.connectedAt = new Date().toISOString();
|
|
4360
4360
|
remoteAgentState.needsReconnect = false;
|
|
@@ -5222,7 +5222,7 @@ function appendRemoteAgentAttemptOutput(attempt, stream, chunk) {
|
|
|
5222
5222
|
const text = chunk.toString();
|
|
5223
5223
|
const key = stream === 'stderr' ? 'stderrTail' : 'stdoutTail';
|
|
5224
5224
|
attempt.state[key] = String((attempt.state[key] || '') + text).slice(-REMOTE_AGENT_STDIO_TAIL_CHARS);
|
|
5225
|
-
if (/Connected to RemoteHub/i.test(text)) {
|
|
5225
|
+
if (/Connected to (?:RemoteHub|LiveDesk Hub)/i.test(text)) {
|
|
5226
5226
|
attempt.state.ready = true;
|
|
5227
5227
|
attempt.state.connectedAt = new Date().toISOString();
|
|
5228
5228
|
attempt.state.needsReconnect = false;
|
|
@@ -302,10 +302,8 @@
|
|
|
302
302
|
themeGroup.userData.localSnapGridStyle = options.localSnapGridStyle || 'line';
|
|
303
303
|
themeGroup.userData.localSnapGridRadiusPx = 500;
|
|
304
304
|
themeGroup.userData.themeAlias = options.themeAlias || 'LocalSnapGrid';
|
|
305
|
+
themeGroup.userData.localSnapGridBackgroundColor = options.backgroundColor;
|
|
305
306
|
themeGroup.animate = () => { };
|
|
306
|
-
if (scene && options.backgroundColor !== undefined) {
|
|
307
|
-
scene.background = new THREE.Color(options.backgroundColor);
|
|
308
|
-
}
|
|
309
307
|
return setThemeAnimationMode(themeGroup, THEME_ANIMATION_MODE.None);
|
|
310
308
|
}
|
|
311
309
|
|