@mindexec/cli 0.2.114 → 0.2.116
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 +2 -2
- package/remote-hub.js +17 -0
- package/scripts/remote-frame-ws-smoke.mjs +196 -0
- package/server.js +200 -28
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +1 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +514 -6
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.e1j4pkp0uf.dll → MindExecution.Plugins.Concept.85y7un1ks6.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.yfeqppy3kf.dll → MindExecution.Plugins.PlanMaster.ylooagumgh.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.vzjd4jwanl.dll → MindExecution.Plugins.YouTube.azfozpumhv.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.6xjhkrpfwd.dll → MindExecution.Shared.mppf8quyau.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Web.03g8r8265y.dll → MindExecution.Web.tln762tijf.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +11 -11
- package/wwwroot/index.html +3 -3
- package/wwwroot/service-worker-assets.js +15 -15
- package/wwwroot/service-worker.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindexec/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.116",
|
|
4
4
|
"description": "MindExec local runtime and bridge CLI",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"start": "node launch-bridge.cjs",
|
|
22
22
|
"dev": "node launch-bridge.cjs --watch",
|
|
23
|
-
"test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs",
|
|
23
|
+
"test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs",
|
|
24
24
|
"test:auth": "node scripts/auth-session-smoke.mjs",
|
|
25
25
|
"test:remote": "node scripts/remote-hub-smoke.mjs",
|
|
26
26
|
"test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
|
package/remote-hub.js
CHANGED
|
@@ -601,6 +601,7 @@ export function createRemoteHub(options = {}) {
|
|
|
601
601
|
const logEvent = options.logEvent || (() => {});
|
|
602
602
|
const logWarn = options.logWarn || (() => {});
|
|
603
603
|
const emitEvent = options.emitEvent || (() => {});
|
|
604
|
+
const emitFrame = typeof options.emitFrame === 'function' ? options.emitFrame : (() => {});
|
|
604
605
|
|
|
605
606
|
const enabled = isEnabledValue(env.MINDEXEC_REMOTE_HUB ?? env.REMOTE_HUB_ENABLED, true)
|
|
606
607
|
&& !isDisabledValue(env.REMOTE_HUB_DISABLED);
|
|
@@ -985,6 +986,14 @@ export function createRemoteHub(options = {}) {
|
|
|
985
986
|
});
|
|
986
987
|
device.latestLiveFrame = frame;
|
|
987
988
|
rememberRecentFramePayload(device, 'live', frame);
|
|
989
|
+
emitFrame({
|
|
990
|
+
kind: 'live',
|
|
991
|
+
deviceId: device.deviceId,
|
|
992
|
+
frame: serializeRemoteFrame(frame, device.deviceId, 'live', { includeDataUrl: false }),
|
|
993
|
+
payload: frame.payload,
|
|
994
|
+
mimeType: frame.mimeType,
|
|
995
|
+
byteLength: frame.byteLength
|
|
996
|
+
});
|
|
988
997
|
device.activeLiveStream.lastFrameAt = frame.receivedAt;
|
|
989
998
|
device.activeLiveStream.lastFrameSeq = frame.frameSeq;
|
|
990
999
|
device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
|
|
@@ -1780,6 +1789,14 @@ export function createRemoteHub(options = {}) {
|
|
|
1780
1789
|
accessToken: createFrameAccessToken()
|
|
1781
1790
|
};
|
|
1782
1791
|
rememberRecentFramePayload(device, 'live', device.latestLiveFrame);
|
|
1792
|
+
emitFrame({
|
|
1793
|
+
kind: 'live',
|
|
1794
|
+
deviceId: device.deviceId,
|
|
1795
|
+
frame: serializeRemoteFrame(device.latestLiveFrame, device.deviceId, 'live', { includeDataUrl: false }),
|
|
1796
|
+
payload: device.latestLiveFrame.payload,
|
|
1797
|
+
mimeType: device.latestLiveFrame.mimeType,
|
|
1798
|
+
byteLength: device.latestLiveFrame.byteLength
|
|
1799
|
+
});
|
|
1783
1800
|
device.activeLiveStream.lastFrameAt = device.lastSeenAt;
|
|
1784
1801
|
device.activeLiveStream.lastFrameSeq = frameSeq;
|
|
1785
1802
|
device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import net from 'node:net';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { WebSocket } from 'ws';
|
|
9
|
+
|
|
10
|
+
const BRIDGE_TOKEN = 'remote-frame-ws-smoke-token';
|
|
11
|
+
const PAIR_TOKEN = 'remote-frame-ws-pair-token';
|
|
12
|
+
|
|
13
|
+
function wait(ms) {
|
|
14
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function findFreePort() {
|
|
18
|
+
return await new Promise((resolve, reject) => {
|
|
19
|
+
const server = net.createServer();
|
|
20
|
+
server.unref();
|
|
21
|
+
server.once('error', reject);
|
|
22
|
+
server.listen(0, '127.0.0.1', () => {
|
|
23
|
+
const address = server.address();
|
|
24
|
+
const port = typeof address === 'object' && address ? address.port : 0;
|
|
25
|
+
server.close(() => resolve(port));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function fetchJson(url, options = {}) {
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
...options,
|
|
33
|
+
headers: {
|
|
34
|
+
...(options.body ? { 'Content-Type': 'application/json' } : {}),
|
|
35
|
+
'X-Bridge-Token': BRIDGE_TOKEN,
|
|
36
|
+
...(options.headers || {})
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const payload = await response.json().catch(() => null);
|
|
40
|
+
return { ok: response.ok, status: response.status, payload };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function waitForBridge(baseUrl, details) {
|
|
44
|
+
const startedAt = Date.now();
|
|
45
|
+
while (Date.now() - startedAt < 30000) {
|
|
46
|
+
try {
|
|
47
|
+
const result = await fetchJson(`${baseUrl}/api/remote/status`);
|
|
48
|
+
if (result.ok && result.payload?.started === true) {
|
|
49
|
+
return result.payload;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// Server is still starting.
|
|
53
|
+
}
|
|
54
|
+
await wait(100);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw new Error(`Timed out waiting for LocalBridge.\n${details()}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function spawnBridge({ bridgePort, remoteHubPort }) {
|
|
61
|
+
const bridgeRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
62
|
+
const env = {
|
|
63
|
+
...process.env,
|
|
64
|
+
BRIDGE_PORT: String(bridgePort),
|
|
65
|
+
BRIDGE_TOKEN,
|
|
66
|
+
BRIDGE_REQUIRE_TOKEN: '1',
|
|
67
|
+
MINDEXEC_REMOTE_HUB: '1',
|
|
68
|
+
MINDEXEC_REMOTE_SYNTHETIC_FLEET: '1',
|
|
69
|
+
REMOTE_HUB_HOST: '127.0.0.1',
|
|
70
|
+
REMOTE_HUB_PORT: String(remoteHubPort),
|
|
71
|
+
REMOTE_HUB_PAIR_TOKEN: PAIR_TOKEN,
|
|
72
|
+
NO_COLOR: '1'
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const child = spawn(process.execPath, ['server.js'], {
|
|
76
|
+
cwd: bridgeRoot,
|
|
77
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
78
|
+
windowsHide: true,
|
|
79
|
+
env
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let stdout = '';
|
|
83
|
+
let stderr = '';
|
|
84
|
+
child.stdout.on('data', chunk => {
|
|
85
|
+
stdout += chunk.toString();
|
|
86
|
+
});
|
|
87
|
+
child.stderr.on('data', chunk => {
|
|
88
|
+
stderr += chunk.toString();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const exitPromise = new Promise(resolve => child.once('exit', resolve));
|
|
92
|
+
const stop = async () => {
|
|
93
|
+
if (child.exitCode === null && !child.killed) {
|
|
94
|
+
child.kill('SIGTERM');
|
|
95
|
+
await Promise.race([exitPromise, wait(5000)]);
|
|
96
|
+
if (child.exitCode === null && !child.killed) {
|
|
97
|
+
child.kill('SIGKILL');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
baseUrl: `http://127.0.0.1:${bridgePort}`,
|
|
104
|
+
details: () => `stdout=${stdout}\nstderr=${stderr}`,
|
|
105
|
+
stop
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseFramePacket(data) {
|
|
110
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
111
|
+
assert.ok(buffer.length > 8, 'binary frame packet should include header, metadata, and payload');
|
|
112
|
+
const metaLength = buffer.readUInt32BE(0);
|
|
113
|
+
assert.ok(metaLength > 0 && metaLength < 64 * 1024, 'metadata length should be bounded');
|
|
114
|
+
assert.ok(4 + metaLength < buffer.length, 'packet should include binary payload after metadata');
|
|
115
|
+
const metadata = JSON.parse(buffer.subarray(4, 4 + metaLength).toString('utf8'));
|
|
116
|
+
const payload = buffer.subarray(4 + metaLength);
|
|
117
|
+
return { metadata, payload };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function waitForBinaryFrame(ws) {
|
|
121
|
+
return await new Promise((resolve, reject) => {
|
|
122
|
+
const timeout = setTimeout(() => reject(new Error('timed out waiting for binary frame')), 10000);
|
|
123
|
+
ws.on('message', data => {
|
|
124
|
+
if (typeof data === 'string') {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const raw = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
128
|
+
if (raw[0] === 0x7b || raw[0] === 0x5b) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
try {
|
|
133
|
+
resolve(parseFramePacket(raw));
|
|
134
|
+
} catch (error) {
|
|
135
|
+
reject(error);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
ws.on('error', reject);
|
|
139
|
+
ws.on('close', () => reject(new Error('websocket closed before binary frame')));
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function main() {
|
|
144
|
+
const bridgePort = await findFreePort();
|
|
145
|
+
const remoteHubPort = await findFreePort();
|
|
146
|
+
const bridge = spawnBridge({ bridgePort, remoteHubPort });
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const status = await waitForBridge(bridge.baseUrl, bridge.details);
|
|
150
|
+
assert.equal(status.started, true);
|
|
151
|
+
|
|
152
|
+
const seed = await fetchJson(`${bridge.baseUrl}/api/remote/synthetic/seed`, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
body: JSON.stringify({ count: 4, connectedRatio: 1, liveRatio: 1 })
|
|
155
|
+
});
|
|
156
|
+
assert.equal(seed.ok, true);
|
|
157
|
+
|
|
158
|
+
const devicesResult = await fetchJson(`${bridge.baseUrl}/api/remote/devices`);
|
|
159
|
+
assert.equal(devicesResult.ok, true);
|
|
160
|
+
const device = devicesResult.payload?.devices?.find(item => item.connected === true);
|
|
161
|
+
assert.ok(device?.deviceId, 'expected connected synthetic device');
|
|
162
|
+
|
|
163
|
+
const ws = new WebSocket(`${bridge.baseUrl.replace(/^http:/, 'ws:')}/api/remote/frames/ws?token=${encodeURIComponent(BRIDGE_TOKEN)}`);
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
ws.once('open', resolve);
|
|
166
|
+
ws.once('error', reject);
|
|
167
|
+
});
|
|
168
|
+
ws.send(JSON.stringify({ type: 'subscribe', deviceIds: [device.deviceId] }));
|
|
169
|
+
|
|
170
|
+
const framePromise = waitForBinaryFrame(ws);
|
|
171
|
+
const live = await fetchJson(`${bridge.baseUrl}/api/remote/devices/${encodeURIComponent(device.deviceId)}/live/start`, {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
body: JSON.stringify({ fps: 20 })
|
|
174
|
+
});
|
|
175
|
+
assert.equal(live.ok, true);
|
|
176
|
+
|
|
177
|
+
const frame = await framePromise;
|
|
178
|
+
assert.equal(frame.metadata.type, 'remote.frame.binary');
|
|
179
|
+
assert.equal(frame.metadata.kind, 'live');
|
|
180
|
+
assert.equal(frame.metadata.deviceId, device.deviceId);
|
|
181
|
+
assert.equal(frame.metadata.fps, 20);
|
|
182
|
+
assert.ok(frame.metadata.frameSeq > 0, 'frameSeq should be positive');
|
|
183
|
+
assert.equal(frame.metadata.mimeType, 'image/png');
|
|
184
|
+
assert.ok(frame.payload.length > 0, 'payload should be non-empty');
|
|
185
|
+
ws.close();
|
|
186
|
+
|
|
187
|
+
console.log('Remote frame WebSocket smoke OK');
|
|
188
|
+
} finally {
|
|
189
|
+
await bridge.stop();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main().catch(error => {
|
|
194
|
+
console.error(error);
|
|
195
|
+
process.exitCode = 1;
|
|
196
|
+
});
|
package/server.js
CHANGED
|
@@ -2120,28 +2120,41 @@ app.get('/api/local-files/:id', async (req, res) => {
|
|
|
2120
2120
|
|
|
2121
2121
|
const httpServer = createServer(app);
|
|
2122
2122
|
const wss = new WebSocketServer({ noServer: true });
|
|
2123
|
+
const remoteFrameWss = new WebSocketServer({ noServer: true });
|
|
2123
2124
|
const wsClients = new Set();
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
const
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2125
|
+
const remoteFrameClients = new Set();
|
|
2126
|
+
const shellJobs = new Map();
|
|
2127
|
+
|
|
2128
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
2129
|
+
try {
|
|
2130
|
+
const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
2131
|
+
const token = parsed.searchParams.get('token') || '';
|
|
2132
|
+
|
|
2133
|
+
if (parsed.pathname === '/events' && token === wsToken) {
|
|
2134
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
2135
|
+
wss.emit('connection', ws, req);
|
|
2136
|
+
});
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
if (parsed.pathname === '/api/remote/frames/ws'
|
|
2141
|
+
&& (!bridgeAuthRequired || token === bridgeToken)) {
|
|
2142
|
+
remoteFrameWss.handleUpgrade(req, socket, head, (ws) => {
|
|
2143
|
+
remoteFrameWss.emit('connection', ws, req);
|
|
2144
|
+
});
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
{
|
|
2149
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
2150
|
+
socket.destroy();
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
} catch {
|
|
2154
|
+
socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
|
|
2155
|
+
socket.destroy();
|
|
2156
|
+
}
|
|
2157
|
+
});
|
|
2145
2158
|
|
|
2146
2159
|
wss.on('connection', (ws) => {
|
|
2147
2160
|
wsClients.add(ws);
|
|
@@ -2154,11 +2167,168 @@ wss.on('connection', (ws) => {
|
|
|
2154
2167
|
projectRoot: projectSession.rootPath,
|
|
2155
2168
|
fileCount: projectSession.symbolsByFile.size
|
|
2156
2169
|
}
|
|
2157
|
-
}));
|
|
2158
|
-
});
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2170
|
+
}));
|
|
2171
|
+
});
|
|
2172
|
+
|
|
2173
|
+
function normalizeRemoteFrameWsDeviceIds(value) {
|
|
2174
|
+
const raw = [];
|
|
2175
|
+
if (Array.isArray(value)) {
|
|
2176
|
+
raw.push(...value);
|
|
2177
|
+
} else if (typeof value === 'string') {
|
|
2178
|
+
raw.push(...value.split(','));
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
const ids = [];
|
|
2182
|
+
const seen = new Set();
|
|
2183
|
+
for (const entry of raw) {
|
|
2184
|
+
const id = String(entry || '').trim();
|
|
2185
|
+
if (!id || seen.has(id)) {
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
seen.add(id);
|
|
2190
|
+
ids.push(id);
|
|
2191
|
+
if (ids.length >= 240) {
|
|
2192
|
+
break;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
return ids;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function sendRemoteFrameClientJson(ws, payload) {
|
|
2200
|
+
if (!ws || ws.readyState !== 1) {
|
|
2201
|
+
return false;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
try {
|
|
2205
|
+
ws.send(JSON.stringify(payload));
|
|
2206
|
+
return true;
|
|
2207
|
+
} catch {
|
|
2208
|
+
return false;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
function updateRemoteFrameClientSubscription(ws, payload = {}) {
|
|
2213
|
+
const deviceIds = normalizeRemoteFrameWsDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
|
|
2214
|
+
ws.remoteFrameDeviceIds = new Set(deviceIds);
|
|
2215
|
+
ws.remoteFrameSubscriptionUpdatedAt = new Date().toISOString();
|
|
2216
|
+
sendRemoteFrameClientJson(ws, {
|
|
2217
|
+
type: 'RemoteFrameSubscription',
|
|
2218
|
+
timestamp: ws.remoteFrameSubscriptionUpdatedAt,
|
|
2219
|
+
deviceIds,
|
|
2220
|
+
mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices'
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
2225
|
+
const payload = frameEvent?.payload;
|
|
2226
|
+
if (!Buffer.isBuffer(payload) || payload.length === 0) {
|
|
2227
|
+
return null;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
const frame = frameEvent.frame || {};
|
|
2231
|
+
const metadata = {
|
|
2232
|
+
type: 'remote.frame.binary',
|
|
2233
|
+
kind: frameEvent.kind || 'live',
|
|
2234
|
+
deviceId: frameEvent.deviceId || frame.deviceId || '',
|
|
2235
|
+
frameSeq: Number(frame.frameSeq || 0) || 0,
|
|
2236
|
+
streamId: frame.streamId || '',
|
|
2237
|
+
width: Number(frame.width || 0) || 0,
|
|
2238
|
+
height: Number(frame.height || 0) || 0,
|
|
2239
|
+
mimeType: frameEvent.mimeType || frame.mimeType || frame.format || 'image/jpeg',
|
|
2240
|
+
mode: frame.mode || '',
|
|
2241
|
+
fps: Number(frame.fps || 0) || 0,
|
|
2242
|
+
capturedAt: frame.capturedAt || '',
|
|
2243
|
+
receivedAt: frame.receivedAt || '',
|
|
2244
|
+
byteLength: Number(frameEvent.byteLength || payload.length) || payload.length,
|
|
2245
|
+
transport: frame.transport || '',
|
|
2246
|
+
contentHash: frame.contentHash || '',
|
|
2247
|
+
captureMs: Number(frame.captureMs || 0) || 0,
|
|
2248
|
+
sameContentStreak: Number(frame.sameContentStreak || 0) || 0
|
|
2249
|
+
};
|
|
2250
|
+
const metaBuffer = Buffer.from(JSON.stringify(metadata), 'utf8');
|
|
2251
|
+
const header = Buffer.allocUnsafe(4);
|
|
2252
|
+
header.writeUInt32BE(metaBuffer.length, 0);
|
|
2253
|
+
return Buffer.concat([header, metaBuffer, payload], header.length + metaBuffer.length + payload.length);
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
function broadcastRemoteBinaryFrame(frameEvent) {
|
|
2257
|
+
if (remoteFrameClients.size === 0) {
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
const deviceId = String(frameEvent?.deviceId || frameEvent?.frame?.deviceId || '').trim();
|
|
2262
|
+
if (!deviceId) {
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
const packet = buildRemoteFrameBinaryPacket(frameEvent);
|
|
2267
|
+
if (!packet) {
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
for (const client of remoteFrameClients) {
|
|
2272
|
+
if (client.readyState !== 1) {
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
const ids = client.remoteFrameDeviceIds;
|
|
2277
|
+
if (ids instanceof Set && ids.size > 0 && !ids.has(deviceId)) {
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
if (client.bufferedAmount > 8 * 1024 * 1024) {
|
|
2282
|
+
client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
try {
|
|
2287
|
+
client.send(packet, { binary: true });
|
|
2288
|
+
client.remoteFrameSentCount = (client.remoteFrameSentCount || 0) + 1;
|
|
2289
|
+
} catch {
|
|
2290
|
+
client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
remoteFrameWss.on('connection', (ws, req) => {
|
|
2296
|
+
remoteFrameClients.add(ws);
|
|
2297
|
+
ws.binaryType = 'arraybuffer';
|
|
2298
|
+
try {
|
|
2299
|
+
const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
2300
|
+
updateRemoteFrameClientSubscription(ws, {
|
|
2301
|
+
deviceIds: parsed.searchParams.get('devices') || ''
|
|
2302
|
+
});
|
|
2303
|
+
} catch {
|
|
2304
|
+
updateRemoteFrameClientSubscription(ws, {});
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
ws.on('message', data => {
|
|
2308
|
+
try {
|
|
2309
|
+
const text = Buffer.isBuffer(data) ? data.toString('utf8') : String(data || '');
|
|
2310
|
+
const payload = JSON.parse(text);
|
|
2311
|
+
if (payload?.type === 'subscribe') {
|
|
2312
|
+
updateRemoteFrameClientSubscription(ws, payload);
|
|
2313
|
+
}
|
|
2314
|
+
} catch {
|
|
2315
|
+
sendRemoteFrameClientJson(ws, {
|
|
2316
|
+
type: 'RemoteFrameSubscriptionError',
|
|
2317
|
+
error: 'invalid-message'
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
ws.on('close', () => remoteFrameClients.delete(ws));
|
|
2322
|
+
ws.on('error', () => remoteFrameClients.delete(ws));
|
|
2323
|
+
sendRemoteFrameClientJson(ws, {
|
|
2324
|
+
type: 'RemoteFrameSocketReady',
|
|
2325
|
+
timestamp: new Date().toISOString(),
|
|
2326
|
+
protocol: 'mindexec.remote.frames.binary.v1'
|
|
2327
|
+
});
|
|
2328
|
+
});
|
|
2329
|
+
|
|
2330
|
+
// Helper: Validate path is within workspace
|
|
2331
|
+
function validatePath(requestedPath) {
|
|
2162
2332
|
const fullPath = resolveWorkspaceBoundPath(requestedPath);
|
|
2163
2333
|
if (!isPathWithin(workspacePath, fullPath)) {
|
|
2164
2334
|
throw new Error('Path must be within workspace');
|
|
@@ -2726,6 +2896,7 @@ const remoteHub = createRemoteHub({
|
|
|
2726
2896
|
logWarn,
|
|
2727
2897
|
logError,
|
|
2728
2898
|
emitEvent: emitBridgeEvent,
|
|
2899
|
+
emitFrame: broadcastRemoteBinaryFrame,
|
|
2729
2900
|
managerPackage: BRIDGE_PACKAGE_NAME,
|
|
2730
2901
|
managerVersion: BRIDGE_VERSION,
|
|
2731
2902
|
hostInstanceId: BRIDGE_INSTANCE_ID
|
|
@@ -7772,8 +7943,9 @@ app.get('/api/status', async (req, res) => {
|
|
|
7772
7943
|
status: 'ok',
|
|
7773
7944
|
version: BRIDGE_VERSION,
|
|
7774
7945
|
workspace: workspacePath,
|
|
7775
|
-
wsToken,
|
|
7776
|
-
wsPath: '/events',
|
|
7946
|
+
wsToken,
|
|
7947
|
+
wsPath: '/events',
|
|
7948
|
+
remoteFrameWsPath: '/api/remote/frames/ws',
|
|
7777
7949
|
bridgeToken,
|
|
7778
7950
|
bridgeTokenHeader,
|
|
7779
7951
|
bridgeAuthRequired,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const DEBUG = false;
|
|
6
6
|
const FPS_DEBUG = false;
|
|
7
7
|
const FRAME_PERF_DEBUG = false;
|
|
8
|
-
const MINDMAP_CORE_BUILD_ID = '20260616-remote-
|
|
8
|
+
const MINDMAP_CORE_BUILD_ID = '20260616-remote-ws-control-frames-v573';
|
|
9
9
|
const CanvasPhase = Object.freeze({
|
|
10
10
|
Booting: 'booting',
|
|
11
11
|
BoardFileLoading: 'board-file-loading',
|
|
@@ -13345,19 +13345,25 @@
|
|
|
13345
13345
|
const REMOTE_FLEET_LIVE_REFRESH_MS = 30000;
|
|
13346
13346
|
const REMOTE_FLEET_MONITOR_LIVE_FPS = 10;
|
|
13347
13347
|
const REMOTE_FLEET_LIVE_FRAME_REFRESH_MS = Math.round(1000 / REMOTE_FLEET_MONITOR_LIVE_FPS);
|
|
13348
|
+
const REMOTE_FLEET_CONTROL_LIVE_FPS = 20;
|
|
13349
|
+
const REMOTE_FLEET_CONTROL_FRAME_REFRESH_MS = Math.round(1000 / REMOTE_FLEET_CONTROL_LIVE_FPS);
|
|
13348
13350
|
const REMOTE_FLEET_THUMBNAIL_FRAME_REFRESH_MS = 2000;
|
|
13349
13351
|
const REMOTE_FLEET_FRAME_CANVAS_MAX_DPR = 2;
|
|
13350
13352
|
const REMOTE_FLEET_FRAME_BLOB_CACHE_MS = 10000;
|
|
13351
13353
|
const REMOTE_FLEET_FRAME_BLOB_CACHE_LIMIT = 96;
|
|
13352
13354
|
const REMOTE_FLEET_LIVE_FRAME_DECODE_TIMEOUT_MS = 180;
|
|
13353
13355
|
const REMOTE_FLEET_THUMBNAIL_FRAME_DECODE_TIMEOUT_MS = 1200;
|
|
13356
|
+
const REMOTE_FLEET_BINARY_FRAME_WS_RECONNECT_MS = 1500;
|
|
13357
|
+
const REMOTE_FLEET_BINARY_FRAME_SUBSCRIBE_MS = 1000;
|
|
13354
13358
|
const REMOTE_FLEET_TASK_FOLLOW_INITIAL_MS = 250;
|
|
13355
13359
|
const REMOTE_FLEET_TASK_FOLLOW_REFRESH_MS = 2000;
|
|
13356
13360
|
const REMOTE_FLEET_TASK_FOLLOW_MAX_TICKS = 60;
|
|
13357
13361
|
const REMOTE_FLEET_HOST_LEASE_REFRESH_MS = 10000;
|
|
13358
13362
|
const remoteFleetHostLeaseTimers = new Map();
|
|
13359
13363
|
const remoteFleetLocalHostTargets = new Map();
|
|
13364
|
+
const remoteFleetBinaryFrameSessions = new Map();
|
|
13360
13365
|
const remoteFleetFrameBlobCache = new Map();
|
|
13366
|
+
let remoteFleetBridgeStatusPromise = null;
|
|
13361
13367
|
let activeRemoteFleetControlPopup = null;
|
|
13362
13368
|
|
|
13363
13369
|
function findRemoteFleetBodyByNodeId(nodeId) {
|
|
@@ -13495,6 +13501,7 @@
|
|
|
13495
13501
|
|
|
13496
13502
|
function clearRemoteFleetTimers(bodyView) {
|
|
13497
13503
|
if (!bodyView) return;
|
|
13504
|
+
releaseRemoteFleetBinaryFrameSocket(bodyView);
|
|
13498
13505
|
if (bodyView._remoteFleetLiveRefreshTimer) {
|
|
13499
13506
|
clearInterval(bodyView._remoteFleetLiveRefreshTimer);
|
|
13500
13507
|
bodyView._remoteFleetLiveRefreshTimer = null;
|
|
@@ -13519,6 +13526,372 @@
|
|
|
13519
13526
|
}
|
|
13520
13527
|
}
|
|
13521
13528
|
|
|
13529
|
+
function getRemoteFleetBridgeBaseUrlCandidates() {
|
|
13530
|
+
const candidates = [];
|
|
13531
|
+
const push = value => {
|
|
13532
|
+
const text = String(value || '').trim().replace(/\/+$/, '');
|
|
13533
|
+
if (text && !candidates.includes(text)) {
|
|
13534
|
+
candidates.push(text);
|
|
13535
|
+
}
|
|
13536
|
+
};
|
|
13537
|
+
|
|
13538
|
+
push(window.MindExecLocalBridge?.activeBridgeUrl);
|
|
13539
|
+
push(window.location?.origin);
|
|
13540
|
+
return candidates;
|
|
13541
|
+
}
|
|
13542
|
+
|
|
13543
|
+
async function getRemoteFleetBridgeStatusForFrames() {
|
|
13544
|
+
if (remoteFleetBridgeStatusPromise) {
|
|
13545
|
+
return remoteFleetBridgeStatusPromise;
|
|
13546
|
+
}
|
|
13547
|
+
|
|
13548
|
+
remoteFleetBridgeStatusPromise = (async () => {
|
|
13549
|
+
const nonce = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
13550
|
+
let lastError = null;
|
|
13551
|
+
for (const baseUrl of getRemoteFleetBridgeBaseUrlCandidates()) {
|
|
13552
|
+
try {
|
|
13553
|
+
const response = await fetch(`${baseUrl}/api/status?remoteFrames=ws&cb=${encodeURIComponent(nonce)}`, {
|
|
13554
|
+
cache: 'no-store',
|
|
13555
|
+
credentials: 'omit'
|
|
13556
|
+
});
|
|
13557
|
+
if (!response.ok) {
|
|
13558
|
+
lastError = new Error(`status:${response.status}`);
|
|
13559
|
+
continue;
|
|
13560
|
+
}
|
|
13561
|
+
|
|
13562
|
+
const status = await response.json();
|
|
13563
|
+
status.__remoteFleetBridgeBaseUrl = baseUrl;
|
|
13564
|
+
return status;
|
|
13565
|
+
} catch (error) {
|
|
13566
|
+
lastError = error;
|
|
13567
|
+
}
|
|
13568
|
+
}
|
|
13569
|
+
|
|
13570
|
+
throw lastError || new Error('bridge-status-unavailable');
|
|
13571
|
+
})();
|
|
13572
|
+
|
|
13573
|
+
try {
|
|
13574
|
+
return await remoteFleetBridgeStatusPromise;
|
|
13575
|
+
} catch (error) {
|
|
13576
|
+
remoteFleetBridgeStatusPromise = null;
|
|
13577
|
+
throw error;
|
|
13578
|
+
}
|
|
13579
|
+
}
|
|
13580
|
+
|
|
13581
|
+
function buildRemoteFleetBinaryFrameWsUrl(status) {
|
|
13582
|
+
const baseUrl = String(status?.__remoteFleetBridgeBaseUrl || window.location?.origin || '').trim();
|
|
13583
|
+
const path = String(status?.remoteFrameWsPath || '/api/remote/frames/ws').trim() || '/api/remote/frames/ws';
|
|
13584
|
+
if (!baseUrl) {
|
|
13585
|
+
return '';
|
|
13586
|
+
}
|
|
13587
|
+
|
|
13588
|
+
const url = new URL(path, baseUrl);
|
|
13589
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
13590
|
+
const bridgeToken = String(status?.bridgeToken || '').trim();
|
|
13591
|
+
if (bridgeToken) {
|
|
13592
|
+
url.searchParams.set('token', bridgeToken);
|
|
13593
|
+
}
|
|
13594
|
+
return url.toString();
|
|
13595
|
+
}
|
|
13596
|
+
|
|
13597
|
+
async function parseRemoteFleetBinaryFrameMessage(data) {
|
|
13598
|
+
let buffer = null;
|
|
13599
|
+
if (data instanceof ArrayBuffer) {
|
|
13600
|
+
buffer = data;
|
|
13601
|
+
} else if (data?.arrayBuffer && typeof data.arrayBuffer === 'function') {
|
|
13602
|
+
buffer = await data.arrayBuffer();
|
|
13603
|
+
}
|
|
13604
|
+
|
|
13605
|
+
if (!buffer || buffer.byteLength < 5) {
|
|
13606
|
+
return null;
|
|
13607
|
+
}
|
|
13608
|
+
|
|
13609
|
+
const view = new DataView(buffer);
|
|
13610
|
+
const metaLength = view.getUint32(0);
|
|
13611
|
+
if (!Number.isFinite(metaLength)
|
|
13612
|
+
|| metaLength <= 0
|
|
13613
|
+
|| metaLength > 64 * 1024
|
|
13614
|
+
|| 4 + metaLength >= buffer.byteLength) {
|
|
13615
|
+
return null;
|
|
13616
|
+
}
|
|
13617
|
+
|
|
13618
|
+
const metadataBytes = new Uint8Array(buffer, 4, metaLength);
|
|
13619
|
+
const metadataText = new TextDecoder().decode(metadataBytes);
|
|
13620
|
+
const metadata = JSON.parse(metadataText);
|
|
13621
|
+
if (String(metadata?.type || '').toLowerCase() !== 'remote.frame.binary') {
|
|
13622
|
+
return null;
|
|
13623
|
+
}
|
|
13624
|
+
|
|
13625
|
+
const payload = buffer.slice(4 + metaLength);
|
|
13626
|
+
const mimeType = String(metadata.mimeType || metadata.format || 'image/jpeg').trim() || 'image/jpeg';
|
|
13627
|
+
const payloadBlob = new Blob([payload], { type: mimeType });
|
|
13628
|
+
const objectUrl = URL.createObjectURL(payloadBlob);
|
|
13629
|
+
return {
|
|
13630
|
+
...metadata,
|
|
13631
|
+
kind: String(metadata.kind || 'live').toLowerCase() === 'thumbnail' ? 'thumbnail' : 'live',
|
|
13632
|
+
mimeType,
|
|
13633
|
+
frameUrl: objectUrl,
|
|
13634
|
+
framePath: objectUrl,
|
|
13635
|
+
dataUrl: '',
|
|
13636
|
+
_remoteFleetObjectUrl: objectUrl,
|
|
13637
|
+
_remoteFleetPayloadBlob: payloadBlob,
|
|
13638
|
+
_remoteFleetBinaryFrame: true
|
|
13639
|
+
};
|
|
13640
|
+
}
|
|
13641
|
+
|
|
13642
|
+
function releaseRemoteFleetFrameObjectUrl(preview, nextObjectUrl = '') {
|
|
13643
|
+
const previous = preview?._remoteFleetObjectUrl || '';
|
|
13644
|
+
if (previous && previous !== nextObjectUrl && typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {
|
|
13645
|
+
try {
|
|
13646
|
+
URL.revokeObjectURL(previous);
|
|
13647
|
+
} catch {
|
|
13648
|
+
// Best-effort browser memory cleanup.
|
|
13649
|
+
}
|
|
13650
|
+
}
|
|
13651
|
+
|
|
13652
|
+
if (preview) {
|
|
13653
|
+
preview._remoteFleetObjectUrl = nextObjectUrl || '';
|
|
13654
|
+
}
|
|
13655
|
+
}
|
|
13656
|
+
|
|
13657
|
+
function getRemoteFleetBinaryFrameDeviceIds(sessionOrGetter) {
|
|
13658
|
+
const getDeviceIds = typeof sessionOrGetter === 'function'
|
|
13659
|
+
? sessionOrGetter
|
|
13660
|
+
: sessionOrGetter?.getDeviceIds;
|
|
13661
|
+
const ids = typeof getDeviceIds === 'function'
|
|
13662
|
+
? getDeviceIds()
|
|
13663
|
+
: [];
|
|
13664
|
+
const normalized = (Array.isArray(ids) ? ids : [])
|
|
13665
|
+
.map(value => String(value || '').trim())
|
|
13666
|
+
.filter(Boolean);
|
|
13667
|
+
|
|
13668
|
+
if (sessionOrGetter?.controlSessions instanceof Set) {
|
|
13669
|
+
sessionOrGetter.controlSessions.forEach(controlSession => {
|
|
13670
|
+
const deviceId = String(controlSession?.deviceId || '').trim();
|
|
13671
|
+
if (deviceId) {
|
|
13672
|
+
normalized.push(deviceId);
|
|
13673
|
+
}
|
|
13674
|
+
});
|
|
13675
|
+
}
|
|
13676
|
+
|
|
13677
|
+
return Array.from(new Set(normalized))
|
|
13678
|
+
.slice(0, 240);
|
|
13679
|
+
}
|
|
13680
|
+
|
|
13681
|
+
function refreshRemoteFleetBinaryFrameSubscription(session, force = false) {
|
|
13682
|
+
if (!session?.ws || session.ws.readyState !== WebSocket.OPEN) {
|
|
13683
|
+
return false;
|
|
13684
|
+
}
|
|
13685
|
+
|
|
13686
|
+
const deviceIds = getRemoteFleetBinaryFrameDeviceIds(session);
|
|
13687
|
+
const key = deviceIds.join('\n');
|
|
13688
|
+
if (!force && session.subscriptionKey === key) {
|
|
13689
|
+
return true;
|
|
13690
|
+
}
|
|
13691
|
+
|
|
13692
|
+
session.subscriptionKey = key;
|
|
13693
|
+
session.ws.send(JSON.stringify({
|
|
13694
|
+
type: 'subscribe',
|
|
13695
|
+
nodeId: session.nodeId,
|
|
13696
|
+
deviceIds
|
|
13697
|
+
}));
|
|
13698
|
+
return true;
|
|
13699
|
+
}
|
|
13700
|
+
|
|
13701
|
+
function scheduleRemoteFleetBinaryFrameReconnect(session) {
|
|
13702
|
+
if (!session || session.reconnectTimer || (session.bodies.size === 0 && (!session.controlSessions || session.controlSessions.size === 0))) {
|
|
13703
|
+
return;
|
|
13704
|
+
}
|
|
13705
|
+
|
|
13706
|
+
session.reconnectTimer = setTimeout(() => {
|
|
13707
|
+
session.reconnectTimer = null;
|
|
13708
|
+
openRemoteFleetBinaryFrameSocket(session);
|
|
13709
|
+
}, REMOTE_FLEET_BINARY_FRAME_WS_RECONNECT_MS);
|
|
13710
|
+
}
|
|
13711
|
+
|
|
13712
|
+
async function openRemoteFleetBinaryFrameSocket(session) {
|
|
13713
|
+
if (!session || (session.bodies.size === 0 && (!session.controlSessions || session.controlSessions.size === 0)) || typeof WebSocket !== 'function') {
|
|
13714
|
+
return false;
|
|
13715
|
+
}
|
|
13716
|
+
|
|
13717
|
+
if (session.ws && (session.ws.readyState === WebSocket.OPEN || session.ws.readyState === WebSocket.CONNECTING)) {
|
|
13718
|
+
refreshRemoteFleetBinaryFrameSubscription(session);
|
|
13719
|
+
return true;
|
|
13720
|
+
}
|
|
13721
|
+
|
|
13722
|
+
try {
|
|
13723
|
+
const status = await getRemoteFleetBridgeStatusForFrames();
|
|
13724
|
+
const wsUrl = buildRemoteFleetBinaryFrameWsUrl(status);
|
|
13725
|
+
if (!wsUrl) {
|
|
13726
|
+
throw new Error('remote-frame-ws-url-unavailable');
|
|
13727
|
+
}
|
|
13728
|
+
|
|
13729
|
+
const ws = new WebSocket(wsUrl);
|
|
13730
|
+
session.ws = ws;
|
|
13731
|
+
session.subscriptionKey = '';
|
|
13732
|
+
ws.binaryType = 'arraybuffer';
|
|
13733
|
+
ws.onopen = () => {
|
|
13734
|
+
window.RuntimeTrace?.emit?.('remote.frame.wsOpen', { nodeId: session.nodeId });
|
|
13735
|
+
refreshRemoteFleetBinaryFrameSubscription(session, true);
|
|
13736
|
+
if (!session.subscriptionTimer) {
|
|
13737
|
+
session.subscriptionTimer = setInterval(
|
|
13738
|
+
() => refreshRemoteFleetBinaryFrameSubscription(session),
|
|
13739
|
+
REMOTE_FLEET_BINARY_FRAME_SUBSCRIBE_MS
|
|
13740
|
+
);
|
|
13741
|
+
}
|
|
13742
|
+
};
|
|
13743
|
+
ws.onmessage = event => {
|
|
13744
|
+
if (typeof event.data === 'string') {
|
|
13745
|
+
window.RuntimeTrace?.emit?.('remote.frame.wsMeta', {
|
|
13746
|
+
nodeId: session.nodeId,
|
|
13747
|
+
message: event.data.slice(0, 120)
|
|
13748
|
+
});
|
|
13749
|
+
return;
|
|
13750
|
+
}
|
|
13751
|
+
|
|
13752
|
+
parseRemoteFleetBinaryFrameMessage(event.data)
|
|
13753
|
+
.then(frame => {
|
|
13754
|
+
if (!frame || (session.bodies.size === 0 && (!session.controlSessions || session.controlSessions.size === 0))) {
|
|
13755
|
+
if (frame?._remoteFleetObjectUrl && typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {
|
|
13756
|
+
URL.revokeObjectURL(frame._remoteFleetObjectUrl);
|
|
13757
|
+
}
|
|
13758
|
+
return;
|
|
13759
|
+
}
|
|
13760
|
+
|
|
13761
|
+
let applied = 0;
|
|
13762
|
+
session.bodies.forEach(body => {
|
|
13763
|
+
if (document.body.contains(body)) {
|
|
13764
|
+
applied += applyRemoteFleetFramePatches(body, [frame]);
|
|
13765
|
+
}
|
|
13766
|
+
});
|
|
13767
|
+
|
|
13768
|
+
let controlPaintQueued = 0;
|
|
13769
|
+
if (session.controlSessions instanceof Set && session.controlSessions.size > 0) {
|
|
13770
|
+
session.controlSessions.forEach(controlSession => {
|
|
13771
|
+
if (!controlSession?.active) {
|
|
13772
|
+
session.controlSessions.delete(controlSession);
|
|
13773
|
+
return;
|
|
13774
|
+
}
|
|
13775
|
+
|
|
13776
|
+
if (String(controlSession.deviceId || '').trim() !== String(frame.deviceId || '').trim()) {
|
|
13777
|
+
return;
|
|
13778
|
+
}
|
|
13779
|
+
|
|
13780
|
+
controlPaintQueued += 1;
|
|
13781
|
+
paintRemoteFleetControlBinaryFrame(controlSession, frame)
|
|
13782
|
+
.catch(error => {
|
|
13783
|
+
window.RuntimeTrace?.emit?.('remote.control.binaryPaintFailed', {
|
|
13784
|
+
nodeId: session.nodeId,
|
|
13785
|
+
deviceId: controlSession.deviceId,
|
|
13786
|
+
error: error?.message || String(error || '')
|
|
13787
|
+
});
|
|
13788
|
+
});
|
|
13789
|
+
});
|
|
13790
|
+
}
|
|
13791
|
+
|
|
13792
|
+
const controlUsesPayloadBlob = controlPaintQueued > 0
|
|
13793
|
+
&& !!frame._remoteFleetPayloadBlob
|
|
13794
|
+
&& typeof createImageBitmap === 'function';
|
|
13795
|
+
if (applied <= 0
|
|
13796
|
+
&& (controlPaintQueued <= 0 || controlUsesPayloadBlob)
|
|
13797
|
+
&& frame._remoteFleetObjectUrl
|
|
13798
|
+
&& typeof URL !== 'undefined'
|
|
13799
|
+
&& typeof URL.revokeObjectURL === 'function') {
|
|
13800
|
+
URL.revokeObjectURL(frame._remoteFleetObjectUrl);
|
|
13801
|
+
}
|
|
13802
|
+
})
|
|
13803
|
+
.catch(error => {
|
|
13804
|
+
window.RuntimeTrace?.emit?.('remote.frame.wsParseFailed', {
|
|
13805
|
+
nodeId: session.nodeId,
|
|
13806
|
+
error: error?.message || String(error || '')
|
|
13807
|
+
});
|
|
13808
|
+
});
|
|
13809
|
+
};
|
|
13810
|
+
ws.onclose = () => {
|
|
13811
|
+
if (session.ws === ws) {
|
|
13812
|
+
session.ws = null;
|
|
13813
|
+
}
|
|
13814
|
+
window.RuntimeTrace?.emit?.('remote.frame.wsClosed', { nodeId: session.nodeId });
|
|
13815
|
+
scheduleRemoteFleetBinaryFrameReconnect(session);
|
|
13816
|
+
};
|
|
13817
|
+
ws.onerror = () => {
|
|
13818
|
+
try {
|
|
13819
|
+
ws.close();
|
|
13820
|
+
} catch {
|
|
13821
|
+
// Ignore WebSocket close failures.
|
|
13822
|
+
}
|
|
13823
|
+
};
|
|
13824
|
+
return true;
|
|
13825
|
+
} catch (error) {
|
|
13826
|
+
window.RuntimeTrace?.emit?.('remote.frame.wsOpenFailed', {
|
|
13827
|
+
nodeId: session.nodeId,
|
|
13828
|
+
error: error?.message || String(error || '')
|
|
13829
|
+
});
|
|
13830
|
+
scheduleRemoteFleetBinaryFrameReconnect(session);
|
|
13831
|
+
return false;
|
|
13832
|
+
}
|
|
13833
|
+
}
|
|
13834
|
+
|
|
13835
|
+
function startRemoteFleetBinaryFrameSocket(bodyView, getDeviceIds) {
|
|
13836
|
+
const nodeId = String(bodyView?.dataset?.nodeId || '').trim();
|
|
13837
|
+
if (!nodeId || typeof WebSocket !== 'function') {
|
|
13838
|
+
return false;
|
|
13839
|
+
}
|
|
13840
|
+
|
|
13841
|
+
let session = remoteFleetBinaryFrameSessions.get(nodeId);
|
|
13842
|
+
if (!session) {
|
|
13843
|
+
session = {
|
|
13844
|
+
nodeId,
|
|
13845
|
+
bodies: new Set(),
|
|
13846
|
+
ws: null,
|
|
13847
|
+
reconnectTimer: null,
|
|
13848
|
+
subscriptionTimer: null,
|
|
13849
|
+
subscriptionKey: '',
|
|
13850
|
+
controlSessions: new Set(),
|
|
13851
|
+
getDeviceIds: null
|
|
13852
|
+
};
|
|
13853
|
+
remoteFleetBinaryFrameSessions.set(nodeId, session);
|
|
13854
|
+
}
|
|
13855
|
+
|
|
13856
|
+
session.bodies.add(bodyView);
|
|
13857
|
+
session.getDeviceIds = getDeviceIds;
|
|
13858
|
+
bodyView._remoteFleetBinaryFrameSession = session;
|
|
13859
|
+
openRemoteFleetBinaryFrameSocket(session).catch(() => undefined);
|
|
13860
|
+
return true;
|
|
13861
|
+
}
|
|
13862
|
+
|
|
13863
|
+
function releaseRemoteFleetBinaryFrameSocket(bodyView) {
|
|
13864
|
+
const session = bodyView?._remoteFleetBinaryFrameSession;
|
|
13865
|
+
if (!session) {
|
|
13866
|
+
return;
|
|
13867
|
+
}
|
|
13868
|
+
|
|
13869
|
+
delete bodyView._remoteFleetBinaryFrameSession;
|
|
13870
|
+
session.bodies.delete(bodyView);
|
|
13871
|
+
if (session.bodies.size > 0 || (session.controlSessions instanceof Set && session.controlSessions.size > 0)) {
|
|
13872
|
+
refreshRemoteFleetBinaryFrameSubscription(session, true);
|
|
13873
|
+
return;
|
|
13874
|
+
}
|
|
13875
|
+
|
|
13876
|
+
if (session.reconnectTimer) {
|
|
13877
|
+
clearTimeout(session.reconnectTimer);
|
|
13878
|
+
session.reconnectTimer = null;
|
|
13879
|
+
}
|
|
13880
|
+
if (session.subscriptionTimer) {
|
|
13881
|
+
clearInterval(session.subscriptionTimer);
|
|
13882
|
+
session.subscriptionTimer = null;
|
|
13883
|
+
}
|
|
13884
|
+
if (session.ws) {
|
|
13885
|
+
try {
|
|
13886
|
+
session.ws.close();
|
|
13887
|
+
} catch {
|
|
13888
|
+
// Ignore WebSocket close failures.
|
|
13889
|
+
}
|
|
13890
|
+
session.ws = null;
|
|
13891
|
+
}
|
|
13892
|
+
remoteFleetBinaryFrameSessions.delete(session.nodeId);
|
|
13893
|
+
}
|
|
13894
|
+
|
|
13522
13895
|
function requestRemoteFleetFrameLoopFrame(callback) {
|
|
13523
13896
|
if (typeof requestAnimationFrame === 'function') {
|
|
13524
13897
|
return {
|
|
@@ -14021,6 +14394,7 @@
|
|
|
14021
14394
|
}
|
|
14022
14395
|
|
|
14023
14396
|
const nextSeq = Number(frame.frameSeq || 0);
|
|
14397
|
+
releaseRemoteFleetFrameObjectUrl(preview, frame._remoteFleetObjectUrl || '');
|
|
14024
14398
|
preview.dataset.remoteFleetFrameKind = frame.kind;
|
|
14025
14399
|
preview.dataset.remoteFleetFrameSeq = String(nextSeq || 0);
|
|
14026
14400
|
preview.dataset.remoteFleetFrameUrl = frame.frameUrl;
|
|
@@ -14529,6 +14903,7 @@
|
|
|
14529
14903
|
|
|
14530
14904
|
activeRemoteFleetControlPopup = null;
|
|
14531
14905
|
session.active = false;
|
|
14906
|
+
detachRemoteFleetControlBinaryFrameSession(session);
|
|
14532
14907
|
if (session.timer) {
|
|
14533
14908
|
clearTimeout(session.timer);
|
|
14534
14909
|
session.timer = null;
|
|
@@ -14550,6 +14925,66 @@
|
|
|
14550
14925
|
}
|
|
14551
14926
|
}
|
|
14552
14927
|
|
|
14928
|
+
function attachRemoteFleetControlBinaryFrameSession(controlSession, bodyView) {
|
|
14929
|
+
if (!controlSession?.active || !bodyView || typeof WebSocket !== 'function') {
|
|
14930
|
+
return false;
|
|
14931
|
+
}
|
|
14932
|
+
|
|
14933
|
+
let binarySession = bodyView._remoteFleetBinaryFrameSession || null;
|
|
14934
|
+
if (!binarySession) {
|
|
14935
|
+
if (!startRemoteFleetBinaryFrameSocket(bodyView, () => [])) {
|
|
14936
|
+
return false;
|
|
14937
|
+
}
|
|
14938
|
+
binarySession = bodyView._remoteFleetBinaryFrameSession || null;
|
|
14939
|
+
}
|
|
14940
|
+
|
|
14941
|
+
if (!binarySession) {
|
|
14942
|
+
return false;
|
|
14943
|
+
}
|
|
14944
|
+
|
|
14945
|
+
if (!(binarySession.controlSessions instanceof Set)) {
|
|
14946
|
+
binarySession.controlSessions = new Set();
|
|
14947
|
+
}
|
|
14948
|
+
|
|
14949
|
+
binarySession.controlSessions.add(controlSession);
|
|
14950
|
+
controlSession.binaryFrameSession = binarySession;
|
|
14951
|
+
controlSession.binaryFramePreferred = true;
|
|
14952
|
+
refreshRemoteFleetBinaryFrameSubscription(binarySession, true);
|
|
14953
|
+
openRemoteFleetBinaryFrameSocket(binarySession).catch(() => undefined);
|
|
14954
|
+
return true;
|
|
14955
|
+
}
|
|
14956
|
+
|
|
14957
|
+
function detachRemoteFleetControlBinaryFrameSession(controlSession) {
|
|
14958
|
+
const binarySession = controlSession?.binaryFrameSession;
|
|
14959
|
+
if (!binarySession?.controlSessions) {
|
|
14960
|
+
return;
|
|
14961
|
+
}
|
|
14962
|
+
|
|
14963
|
+
binarySession.controlSessions.delete(controlSession);
|
|
14964
|
+
delete controlSession.binaryFrameSession;
|
|
14965
|
+
refreshRemoteFleetBinaryFrameSubscription(binarySession, true);
|
|
14966
|
+
|
|
14967
|
+
if (binarySession.bodies.size === 0 && binarySession.controlSessions.size === 0) {
|
|
14968
|
+
if (binarySession.reconnectTimer) {
|
|
14969
|
+
clearTimeout(binarySession.reconnectTimer);
|
|
14970
|
+
binarySession.reconnectTimer = null;
|
|
14971
|
+
}
|
|
14972
|
+
if (binarySession.subscriptionTimer) {
|
|
14973
|
+
clearInterval(binarySession.subscriptionTimer);
|
|
14974
|
+
binarySession.subscriptionTimer = null;
|
|
14975
|
+
}
|
|
14976
|
+
if (binarySession.ws) {
|
|
14977
|
+
try {
|
|
14978
|
+
binarySession.ws.close();
|
|
14979
|
+
} catch {
|
|
14980
|
+
// Ignore WebSocket close failures.
|
|
14981
|
+
}
|
|
14982
|
+
binarySession.ws = null;
|
|
14983
|
+
}
|
|
14984
|
+
remoteFleetBinaryFrameSessions.delete(binarySession.nodeId);
|
|
14985
|
+
}
|
|
14986
|
+
}
|
|
14987
|
+
|
|
14553
14988
|
async function paintRemoteFleetControlFrame(session, frame) {
|
|
14554
14989
|
if (!session?.active || !frame || !isRemoteFleetFrameSource(frame.frameUrl)) {
|
|
14555
14990
|
return false;
|
|
@@ -14581,6 +15016,59 @@
|
|
|
14581
15016
|
}
|
|
14582
15017
|
}
|
|
14583
15018
|
|
|
15019
|
+
async function paintRemoteFleetControlBinaryFrame(session, frame) {
|
|
15020
|
+
if (!session?.active || !frame || String(frame.kind || '').toLowerCase() !== 'live') {
|
|
15021
|
+
return false;
|
|
15022
|
+
}
|
|
15023
|
+
|
|
15024
|
+
const frameSeq = Number(frame.frameSeq || frame.FrameSeq || 0) || 0;
|
|
15025
|
+
if (frameSeq > 0 && frameSeq < (session.lastControlFrameSeq || 0)) {
|
|
15026
|
+
return false;
|
|
15027
|
+
}
|
|
15028
|
+
|
|
15029
|
+
if (frameSeq > 0) {
|
|
15030
|
+
session.lastControlFrameSeq = frameSeq;
|
|
15031
|
+
}
|
|
15032
|
+
|
|
15033
|
+
let bitmap = null;
|
|
15034
|
+
try {
|
|
15035
|
+
if (frame._remoteFleetPayloadBlob && typeof createImageBitmap === 'function') {
|
|
15036
|
+
bitmap = await createImageBitmap(frame._remoteFleetPayloadBlob);
|
|
15037
|
+
} else if (isRemoteFleetFrameSource(frame.frameUrl)) {
|
|
15038
|
+
bitmap = await loadRemoteFleetFrameBitmap(frame);
|
|
15039
|
+
}
|
|
15040
|
+
|
|
15041
|
+
if (!bitmap || !session.active) {
|
|
15042
|
+
return false;
|
|
15043
|
+
}
|
|
15044
|
+
|
|
15045
|
+
requestRemoteFleetFrameLoopFrame(() => {
|
|
15046
|
+
if (!session.active
|
|
15047
|
+
|| !document.body.contains(session.overlay)
|
|
15048
|
+
|| (frameSeq > 0 && frameSeq < (session.lastControlFrameSeq || 0))) {
|
|
15049
|
+
if (typeof bitmap.close === 'function') {
|
|
15050
|
+
bitmap.close();
|
|
15051
|
+
}
|
|
15052
|
+
return;
|
|
15053
|
+
}
|
|
15054
|
+
|
|
15055
|
+
drawRemoteFleetFrameToCanvas(session.canvas, bitmap, 'contain');
|
|
15056
|
+
session.canvas.style.display = 'block';
|
|
15057
|
+
session.lastControlFrameAt = Date.now();
|
|
15058
|
+
session.status.textContent = `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
|
|
15059
|
+
if (typeof bitmap.close === 'function') {
|
|
15060
|
+
bitmap.close();
|
|
15061
|
+
}
|
|
15062
|
+
});
|
|
15063
|
+
return true;
|
|
15064
|
+
} catch {
|
|
15065
|
+
if (bitmap && typeof bitmap.close === 'function') {
|
|
15066
|
+
bitmap.close();
|
|
15067
|
+
}
|
|
15068
|
+
return false;
|
|
15069
|
+
}
|
|
15070
|
+
}
|
|
15071
|
+
|
|
14584
15072
|
function sendRemoteFleetControlInput(session, payload, throttleMove = false) {
|
|
14585
15073
|
if (!session?.active || !payload?.type) {
|
|
14586
15074
|
return;
|
|
@@ -14901,16 +15389,29 @@
|
|
|
14901
15389
|
timer: null,
|
|
14902
15390
|
moveTimer: null,
|
|
14903
15391
|
pendingMove: null,
|
|
14904
|
-
lastMoveSentAt: 0
|
|
15392
|
+
lastMoveSentAt: 0,
|
|
15393
|
+
lastControlFrameAt: 0,
|
|
15394
|
+
lastControlFrameSeq: 0,
|
|
15395
|
+
binaryFramePreferred: false
|
|
14905
15396
|
};
|
|
14906
15397
|
activeRemoteFleetControlPopup = session;
|
|
14907
15398
|
bindRemoteFleetControlInput(session);
|
|
15399
|
+
attachRemoteFleetControlBinaryFrameSession(session, bodyView);
|
|
14908
15400
|
|
|
14909
15401
|
const refresh = async () => {
|
|
14910
15402
|
if (!session.active) {
|
|
14911
15403
|
return;
|
|
14912
15404
|
}
|
|
14913
15405
|
|
|
15406
|
+
const now = Date.now();
|
|
15407
|
+
const hasFreshBinaryFrame = session.binaryFramePreferred === true
|
|
15408
|
+
&& session.lastControlFrameAt > 0
|
|
15409
|
+
&& now - session.lastControlFrameAt < 1200;
|
|
15410
|
+
if (hasFreshBinaryFrame) {
|
|
15411
|
+
session.timer = setTimeout(refresh, 500);
|
|
15412
|
+
return;
|
|
15413
|
+
}
|
|
15414
|
+
|
|
14914
15415
|
try {
|
|
14915
15416
|
const result = await invokeDotNetAsync('GetRemoteFleetFrameFromJs', nodeId, targetId, 'live');
|
|
14916
15417
|
const frame = normalizeRemoteFleetFramePayload(result?.frame || result?.Frame, 'live');
|
|
@@ -14918,7 +15419,8 @@
|
|
|
14918
15419
|
frame.deviceId = frame.deviceId || targetId;
|
|
14919
15420
|
await paintRemoteFleetControlFrame(session, frame);
|
|
14920
15421
|
applyRemoteFleetFramePatches(bodyView, [frame]);
|
|
14921
|
-
|
|
15422
|
+
session.lastControlFrameAt = Date.now();
|
|
15423
|
+
status.textContent = session.binaryFramePreferred ? 'Control fallback' : `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
|
|
14922
15424
|
} else if (result?.error || result?.Error) {
|
|
14923
15425
|
status.textContent = result.error || result.Error;
|
|
14924
15426
|
}
|
|
@@ -14926,7 +15428,7 @@
|
|
|
14926
15428
|
status.textContent = error?.message || 'Frame failed';
|
|
14927
15429
|
} finally {
|
|
14928
15430
|
if (session.active) {
|
|
14929
|
-
session.timer = setTimeout(refresh,
|
|
15431
|
+
session.timer = setTimeout(refresh, REMOTE_FLEET_CONTROL_FRAME_REFRESH_MS);
|
|
14930
15432
|
}
|
|
14931
15433
|
}
|
|
14932
15434
|
};
|
|
@@ -14950,7 +15452,7 @@
|
|
|
14950
15452
|
deviceId: targetId
|
|
14951
15453
|
});
|
|
14952
15454
|
|
|
14953
|
-
invokeDotNetAsync('StartRemoteFleetLiveStreamFromJs', nodeId, targetId)
|
|
15455
|
+
invokeDotNetAsync('StartRemoteFleetLiveStreamFromJs', nodeId, targetId, REMOTE_FLEET_CONTROL_LIVE_FPS)
|
|
14954
15456
|
.then(async result => {
|
|
14955
15457
|
if (!session.active) {
|
|
14956
15458
|
return;
|
|
@@ -14959,7 +15461,7 @@
|
|
|
14959
15461
|
await syncRemoteFleetNodeStateFromResult(result);
|
|
14960
15462
|
session.startedStream = result?.success === true || result?.Success === true;
|
|
14961
15463
|
session.streamId = String(result?.streamId || result?.StreamId || '');
|
|
14962
|
-
status.textContent = session.startedStream ?
|
|
15464
|
+
status.textContent = session.startedStream ? `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps` : (result?.error || result?.Error || 'View');
|
|
14963
15465
|
refresh();
|
|
14964
15466
|
})
|
|
14965
15467
|
.catch(error => {
|
|
@@ -17086,7 +17588,13 @@
|
|
|
17086
17588
|
error: error?.message || String(error || '')
|
|
17087
17589
|
});
|
|
17088
17590
|
});
|
|
17089
|
-
|
|
17591
|
+
const binaryFrameSocketStarted = startRemoteFleetBinaryFrameSocket(bodyView, () => {
|
|
17592
|
+
const liveIds = getVisibleLiveFrameDeviceIds();
|
|
17593
|
+
return liveIds.length > 0 ? liveIds : getVisibleFrameDeviceIds();
|
|
17594
|
+
});
|
|
17595
|
+
if (!binaryFrameSocketStarted) {
|
|
17596
|
+
startRemoteFleetFrameLoop(bodyView, REMOTE_FLEET_LIVE_FRAME_REFRESH_MS, () => refreshRemoteFleetVisibleLiveFrames());
|
|
17597
|
+
}
|
|
17090
17598
|
bodyView._remoteFleetLiveRefreshTimer = setInterval(async () => {
|
|
17091
17599
|
if (!document.body.contains(bodyView)) {
|
|
17092
17600
|
clearRemoteFleetTimers(bodyView);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/wwwroot/_framework/{MindExecution.Web.03g8r8265y.dll → MindExecution.Web.tln762tijf.dll}
RENAMED
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"mainAssemblyName": "MindExecution.Web",
|
|
3
3
|
"resources": {
|
|
4
|
-
"hash": "sha256-
|
|
4
|
+
"hash": "sha256-JxTp+hXYDhd47gICFMzpCDWm8b4z1JGet67SEEI1F3c=",
|
|
5
5
|
"fingerprinting": {
|
|
6
6
|
"Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
|
|
7
7
|
"Markdig.d1j7v41cl1.dll": "Markdig.dll",
|
|
@@ -127,12 +127,12 @@
|
|
|
127
127
|
"MindExecution.Kernel.pbzp3jfync.dll": "MindExecution.Kernel.dll",
|
|
128
128
|
"MindExecution.Plugins.Admin.gxzwlji1cf.dll": "MindExecution.Plugins.Admin.dll",
|
|
129
129
|
"MindExecution.Plugins.Business.vtaey8c59y.dll": "MindExecution.Plugins.Business.dll",
|
|
130
|
-
"MindExecution.Plugins.Concept.
|
|
130
|
+
"MindExecution.Plugins.Concept.85y7un1ks6.dll": "MindExecution.Plugins.Concept.dll",
|
|
131
131
|
"MindExecution.Plugins.Directory.zc8ffaoknd.dll": "MindExecution.Plugins.Directory.dll",
|
|
132
|
-
"MindExecution.Plugins.PlanMaster.
|
|
133
|
-
"MindExecution.Plugins.YouTube.
|
|
134
|
-
"MindExecution.Shared.
|
|
135
|
-
"MindExecution.Web.
|
|
132
|
+
"MindExecution.Plugins.PlanMaster.ylooagumgh.dll": "MindExecution.Plugins.PlanMaster.dll",
|
|
133
|
+
"MindExecution.Plugins.YouTube.azfozpumhv.dll": "MindExecution.Plugins.YouTube.dll",
|
|
134
|
+
"MindExecution.Shared.mppf8quyau.dll": "MindExecution.Shared.dll",
|
|
135
|
+
"MindExecution.Web.tln762tijf.dll": "MindExecution.Web.dll",
|
|
136
136
|
"dotnet.js": "dotnet.js",
|
|
137
137
|
"dotnet.native.qc8g39g30v.js": "dotnet.native.js",
|
|
138
138
|
"dotnet.native.boem75ye5i.wasm": "dotnet.native.wasm",
|
|
@@ -280,16 +280,16 @@
|
|
|
280
280
|
"netstandard.yvr3prsx0x.dll": "sha256-EksNn8Luo4bOWqJ6X7dIe9qG9oOqwOVzjH2xYyMNi+E=",
|
|
281
281
|
"MindExecution.Core.fc9cjbjplq.dll": "sha256-TymUFwryFLdbCYy2U5qdgE2ZV1yHaLR9bgyrmx6Tcp0=",
|
|
282
282
|
"MindExecution.Kernel.pbzp3jfync.dll": "sha256-Ff6WHyLD39V0fEjUiQlxX9Y+edssyTH+7T4zfBdj3s0=",
|
|
283
|
-
"MindExecution.Plugins.Concept.
|
|
284
|
-
"MindExecution.Plugins.PlanMaster.
|
|
285
|
-
"MindExecution.Shared.
|
|
286
|
-
"MindExecution.Web.
|
|
283
|
+
"MindExecution.Plugins.Concept.85y7un1ks6.dll": "sha256-ZK10a6NfAZTCKX0BpiH6e+pBatMwTeXMCtA79A+zGQg=",
|
|
284
|
+
"MindExecution.Plugins.PlanMaster.ylooagumgh.dll": "sha256-aoNSkErjCAqRgET/An00bm2GP3Jsnnu4hz8e/jpaLcI=",
|
|
285
|
+
"MindExecution.Shared.mppf8quyau.dll": "sha256-FsB415v0PlO8/788VKC99rE/BhViR61KcM9+Crxo9Ok=",
|
|
286
|
+
"MindExecution.Web.tln762tijf.dll": "sha256-vy73kyPmRU7hqhUFOFs9fdsn5AWmO7cNTRDoWigkc2M="
|
|
287
287
|
},
|
|
288
288
|
"lazyAssembly": {
|
|
289
289
|
"MindExecution.Plugins.Admin.gxzwlji1cf.dll": "sha256-5D5B2ZuUMj46zBMgH2dRA7CbQf++uukMPrEliFLuZWs=",
|
|
290
290
|
"MindExecution.Plugins.Business.vtaey8c59y.dll": "sha256-rU9MzRRmHuj0/IluV1dvE6x3aRCK/DA4DWKy1DO4VVg=",
|
|
291
291
|
"MindExecution.Plugins.Directory.zc8ffaoknd.dll": "sha256-Ey/HajaVuBxB/Ou4qsM70nhAZBoODeCENzG1J/uEsxs=",
|
|
292
|
-
"MindExecution.Plugins.YouTube.
|
|
292
|
+
"MindExecution.Plugins.YouTube.azfozpumhv.dll": "sha256-GjL5SM7+D8SKJBE528Y5Py29kQrTXPmG5OFr6+E0L4s="
|
|
293
293
|
}
|
|
294
294
|
},
|
|
295
295
|
"cacheBootResources": true,
|
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-remote-
|
|
11
|
-
<link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-remote-
|
|
10
|
+
<link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260616-remote-ws-control-frames-v573" />
|
|
11
|
+
<link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-remote-ws-control-frames-v573" />
|
|
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-remote-
|
|
582
|
+
const scriptVersion = '20260616-remote-ws-control-frames-v573';
|
|
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": "pV7f33KI",
|
|
3
3
|
"assets": [
|
|
4
4
|
{
|
|
5
5
|
"hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"url": "_content/MindExecution.Shared/js/marked.min.js"
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
|
-
"hash": "sha256-
|
|
81
|
+
"hash": "sha256-oDfBxq3aqmSU1z5MGGpE9ZeTdsBtrykHEBnygOVFJtE=",
|
|
82
82
|
"url": "_content/MindExecution.Shared/js/mind-map-core.js"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
"hash": "sha256-
|
|
89
|
+
"hash": "sha256-8uQGgnoT3SXKM89hTz49ThSpGgT88GRP7ipe8xlR3z0=",
|
|
90
90
|
"url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
|
|
91
91
|
},
|
|
92
92
|
{
|
|
@@ -426,28 +426,28 @@
|
|
|
426
426
|
"url": "_framework/MindExecution.Plugins.Business.vtaey8c59y.dll"
|
|
427
427
|
},
|
|
428
428
|
{
|
|
429
|
-
"hash": "sha256-
|
|
430
|
-
"url": "_framework/MindExecution.Plugins.Concept.
|
|
429
|
+
"hash": "sha256-ZK10a6NfAZTCKX0BpiH6e+pBatMwTeXMCtA79A+zGQg=",
|
|
430
|
+
"url": "_framework/MindExecution.Plugins.Concept.85y7un1ks6.dll"
|
|
431
431
|
},
|
|
432
432
|
{
|
|
433
433
|
"hash": "sha256-Ey/HajaVuBxB/Ou4qsM70nhAZBoODeCENzG1J/uEsxs=",
|
|
434
434
|
"url": "_framework/MindExecution.Plugins.Directory.zc8ffaoknd.dll"
|
|
435
435
|
},
|
|
436
436
|
{
|
|
437
|
-
"hash": "sha256-
|
|
438
|
-
"url": "_framework/MindExecution.Plugins.PlanMaster.
|
|
437
|
+
"hash": "sha256-aoNSkErjCAqRgET/An00bm2GP3Jsnnu4hz8e/jpaLcI=",
|
|
438
|
+
"url": "_framework/MindExecution.Plugins.PlanMaster.ylooagumgh.dll"
|
|
439
439
|
},
|
|
440
440
|
{
|
|
441
|
-
"hash": "sha256-
|
|
442
|
-
"url": "_framework/MindExecution.Plugins.YouTube.
|
|
441
|
+
"hash": "sha256-GjL5SM7+D8SKJBE528Y5Py29kQrTXPmG5OFr6+E0L4s=",
|
|
442
|
+
"url": "_framework/MindExecution.Plugins.YouTube.azfozpumhv.dll"
|
|
443
443
|
},
|
|
444
444
|
{
|
|
445
|
-
"hash": "sha256-
|
|
446
|
-
"url": "_framework/MindExecution.Shared.
|
|
445
|
+
"hash": "sha256-FsB415v0PlO8/788VKC99rE/BhViR61KcM9+Crxo9Ok=",
|
|
446
|
+
"url": "_framework/MindExecution.Shared.mppf8quyau.dll"
|
|
447
447
|
},
|
|
448
448
|
{
|
|
449
|
-
"hash": "sha256-
|
|
450
|
-
"url": "_framework/MindExecution.Web.
|
|
449
|
+
"hash": "sha256-vy73kyPmRU7hqhUFOFs9fdsn5AWmO7cNTRDoWigkc2M=",
|
|
450
|
+
"url": "_framework/MindExecution.Web.tln762tijf.dll"
|
|
451
451
|
},
|
|
452
452
|
{
|
|
453
453
|
"hash": "sha256-IsZJ91/OW+fHzNqIgEc7Y072ns8z9dGritiSyvR9Wgc=",
|
|
@@ -770,7 +770,7 @@
|
|
|
770
770
|
"url": "_framework/Websocket.Client.vapounvmnl.dll"
|
|
771
771
|
},
|
|
772
772
|
{
|
|
773
|
-
"hash": "sha256-
|
|
773
|
+
"hash": "sha256-HDeRZ1juous1GiTsZw+QLukbDxOOYjh72G4Dta2w5Fc=",
|
|
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-VJOyHChPIe+gqQk3r5g4qTjsKYTrLXoP6I5L+o4ZYzE=",
|
|
838
838
|
"url": "index.html"
|
|
839
839
|
},
|
|
840
840
|
{
|