@mindexec/cli 0.2.115 → 0.2.117
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 +3 -3
- package/remote-hub.js +419 -3
- package/scripts/remote-agent-ws-smoke.mjs +202 -0
- package/scripts/remote-frame-ws-smoke.mjs +2 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +1 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +194 -15
- 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.117",
|
|
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 && node --check scripts/remote-frame-ws-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 && node --check scripts/remote-agent-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",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"node": ">=20"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@mindexec/remote": "^0.1.
|
|
50
|
+
"@mindexec/remote": "^0.1.17",
|
|
51
51
|
"@openai/codex-sdk": "^0.137.0",
|
|
52
52
|
"chokidar": "^3.6.0",
|
|
53
53
|
"cors": "^2.8.5",
|
package/remote-hub.js
CHANGED
|
@@ -592,10 +592,266 @@ function writeJsonLine(socket, payload) {
|
|
|
592
592
|
return false;
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
if (socket.__remoteHubWebSocket === true && typeof socket.sendJson === 'function') {
|
|
596
|
+
return socket.sendJson(payload);
|
|
597
|
+
}
|
|
598
|
+
|
|
595
599
|
socket.write(`${JSON.stringify(payload)}\n`);
|
|
596
600
|
return true;
|
|
597
601
|
}
|
|
598
602
|
|
|
603
|
+
function isRemoteHubWebSocketUpgradeStart(buffer) {
|
|
604
|
+
if (!Buffer.isBuffer(buffer) || buffer.length < 4) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return buffer.subarray(0, Math.min(buffer.length, 16)).toString('latin1').startsWith('GET ');
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function parseRemoteHubWebSocketUpgrade(buffer) {
|
|
612
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
613
|
+
if (headerEnd < 0) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const headerText = buffer.subarray(0, headerEnd).toString('latin1');
|
|
618
|
+
const lines = headerText.split('\r\n');
|
|
619
|
+
const requestLine = lines.shift() || '';
|
|
620
|
+
const [method, path] = requestLine.split(/\s+/);
|
|
621
|
+
const headers = {};
|
|
622
|
+
for (const line of lines) {
|
|
623
|
+
const separator = line.indexOf(':');
|
|
624
|
+
if (separator <= 0) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
headers[line.slice(0, separator).trim().toLowerCase()] = line.slice(separator + 1).trim();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
method,
|
|
633
|
+
path,
|
|
634
|
+
headers,
|
|
635
|
+
head: buffer.subarray(headerEnd + 4)
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function buildRemoteHubWebSocketAcceptKey(key) {
|
|
640
|
+
return crypto
|
|
641
|
+
.createHash('sha1')
|
|
642
|
+
.update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
|
|
643
|
+
.digest('base64');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function buildRemoteHubWebSocketFrame(opcode, payload) {
|
|
647
|
+
const body = Buffer.isBuffer(payload) ? payload : Buffer.from(String(payload || ''), 'utf8');
|
|
648
|
+
let header = null;
|
|
649
|
+
if (body.length < 126) {
|
|
650
|
+
header = Buffer.allocUnsafe(2);
|
|
651
|
+
header[0] = 0x80 | opcode;
|
|
652
|
+
header[1] = body.length;
|
|
653
|
+
} else if (body.length <= 0xffff) {
|
|
654
|
+
header = Buffer.allocUnsafe(4);
|
|
655
|
+
header[0] = 0x80 | opcode;
|
|
656
|
+
header[1] = 126;
|
|
657
|
+
header.writeUInt16BE(body.length, 2);
|
|
658
|
+
} else {
|
|
659
|
+
header = Buffer.allocUnsafe(10);
|
|
660
|
+
header[0] = 0x80 | opcode;
|
|
661
|
+
header[1] = 127;
|
|
662
|
+
header.writeBigUInt64BE(BigInt(body.length), 2);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return Buffer.concat([header, body], header.length + body.length);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function parseRemoteHubWebSocketBinaryFrame(buffer) {
|
|
669
|
+
if (!Buffer.isBuffer(buffer) || buffer.length < 5) {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const metaLength = buffer.readUInt32BE(0);
|
|
674
|
+
if (!Number.isFinite(metaLength)
|
|
675
|
+
|| metaLength <= 0
|
|
676
|
+
|| metaLength > 64 * 1024
|
|
677
|
+
|| 4 + metaLength >= buffer.length) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const header = JSON.parse(buffer.subarray(4, 4 + metaLength).toString('utf8'));
|
|
682
|
+
const payload = buffer.subarray(4 + metaLength);
|
|
683
|
+
return { header, payload };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
class RemoteHubWebSocketAgentSocket {
|
|
687
|
+
constructor(socket) {
|
|
688
|
+
this.__remoteHubWebSocket = true;
|
|
689
|
+
this.socket = socket;
|
|
690
|
+
this.remoteAddress = socket.remoteAddress;
|
|
691
|
+
this.remotePort = socket.remotePort;
|
|
692
|
+
this.destroyed = false;
|
|
693
|
+
this.buffer = Buffer.alloc(0);
|
|
694
|
+
this.fragmentOpcode = 0;
|
|
695
|
+
this.fragments = [];
|
|
696
|
+
this.onTextMessage = () => {};
|
|
697
|
+
this.onBinaryMessage = () => {};
|
|
698
|
+
this.onCloseMessage = () => {};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
setNoDelay() {}
|
|
702
|
+
|
|
703
|
+
setKeepAlive() {}
|
|
704
|
+
|
|
705
|
+
sendJson(payload) {
|
|
706
|
+
return this.sendText(JSON.stringify(payload));
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
write(payload) {
|
|
710
|
+
if (this.destroyed) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const text = (Buffer.isBuffer(payload) ? payload.toString('utf8') : String(payload || '')).replace(/\n$/, '');
|
|
715
|
+
return this.sendText(text);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
sendText(text) {
|
|
719
|
+
return this.sendFrame(0x1, Buffer.from(String(text || ''), 'utf8'));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
sendFrame(opcode, payload) {
|
|
723
|
+
if (this.destroyed || this.socket.destroyed) {
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
try {
|
|
728
|
+
this.socket.write(buildRemoteHubWebSocketFrame(opcode, payload));
|
|
729
|
+
return true;
|
|
730
|
+
} catch {
|
|
731
|
+
this.destroy();
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
destroy() {
|
|
737
|
+
if (this.destroyed) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
this.destroyed = true;
|
|
742
|
+
try {
|
|
743
|
+
if (!this.socket.destroyed) {
|
|
744
|
+
this.socket.write(buildRemoteHubWebSocketFrame(0x8, Buffer.alloc(0)));
|
|
745
|
+
}
|
|
746
|
+
} catch {
|
|
747
|
+
// Ignore close-frame failures.
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
this.socket.destroy();
|
|
751
|
+
} catch {
|
|
752
|
+
// Ignore socket destroy failures.
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
handleData(chunk) {
|
|
757
|
+
if (this.destroyed) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
this.buffer = this.buffer.length > 0
|
|
762
|
+
? Buffer.concat([this.buffer, chunk])
|
|
763
|
+
: chunk;
|
|
764
|
+
|
|
765
|
+
while (!this.destroyed) {
|
|
766
|
+
if (this.buffer.length < 2) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const first = this.buffer[0];
|
|
771
|
+
const second = this.buffer[1];
|
|
772
|
+
const fin = (first & 0x80) !== 0;
|
|
773
|
+
const opcode = first & 0x0f;
|
|
774
|
+
const masked = (second & 0x80) !== 0;
|
|
775
|
+
let payloadLength = second & 0x7f;
|
|
776
|
+
let offset = 2;
|
|
777
|
+
|
|
778
|
+
if (payloadLength === 126) {
|
|
779
|
+
if (this.buffer.length < offset + 2) return;
|
|
780
|
+
payloadLength = this.buffer.readUInt16BE(offset);
|
|
781
|
+
offset += 2;
|
|
782
|
+
} else if (payloadLength === 127) {
|
|
783
|
+
if (this.buffer.length < offset + 8) return;
|
|
784
|
+
const bigLength = this.buffer.readBigUInt64BE(offset);
|
|
785
|
+
if (bigLength > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
786
|
+
this.destroy();
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
payloadLength = Number(bigLength);
|
|
790
|
+
offset += 8;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (!masked) {
|
|
794
|
+
this.destroy();
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (this.buffer.length < offset + 4 + payloadLength) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const mask = this.buffer.subarray(offset, offset + 4);
|
|
803
|
+
offset += 4;
|
|
804
|
+
const payload = Buffer.from(this.buffer.subarray(offset, offset + payloadLength));
|
|
805
|
+
this.buffer = this.buffer.subarray(offset + payloadLength);
|
|
806
|
+
for (let index = 0; index < payload.length; index += 1) {
|
|
807
|
+
payload[index] ^= mask[index & 3];
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (opcode === 0x8) {
|
|
811
|
+
this.destroyed = true;
|
|
812
|
+
this.onCloseMessage('websocket-close');
|
|
813
|
+
try {
|
|
814
|
+
this.socket.destroy();
|
|
815
|
+
} catch {
|
|
816
|
+
// Ignore socket destroy failures.
|
|
817
|
+
}
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (opcode === 0x9) {
|
|
822
|
+
this.sendFrame(0xA, payload);
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (opcode === 0xA) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
let messageOpcode = opcode;
|
|
831
|
+
let messagePayload = payload;
|
|
832
|
+
if (!fin) {
|
|
833
|
+
this.fragmentOpcode = opcode;
|
|
834
|
+
this.fragments = [payload];
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (opcode === 0x0) {
|
|
839
|
+
this.fragments.push(payload);
|
|
840
|
+
messageOpcode = this.fragmentOpcode;
|
|
841
|
+
messagePayload = Buffer.concat(this.fragments);
|
|
842
|
+
this.fragmentOpcode = 0;
|
|
843
|
+
this.fragments = [];
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (messageOpcode === 0x1) {
|
|
847
|
+
this.onTextMessage(messagePayload.toString('utf8'));
|
|
848
|
+
} else if (messageOpcode === 0x2) {
|
|
849
|
+
this.onBinaryMessage(messagePayload);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
599
855
|
export function createRemoteHub(options = {}) {
|
|
600
856
|
const env = options.env || process.env;
|
|
601
857
|
const logEvent = options.logEvent || (() => {});
|
|
@@ -1921,7 +2177,146 @@ export function createRemoteHub(options = {}) {
|
|
|
1921
2177
|
}
|
|
1922
2178
|
}
|
|
1923
2179
|
|
|
1924
|
-
function
|
|
2180
|
+
function handleWebSocketAgentSocket(socket) {
|
|
2181
|
+
allSockets.add(socket);
|
|
2182
|
+
socket.setNoDelay(true);
|
|
2183
|
+
socket.setKeepAlive(true, heartbeatMs);
|
|
2184
|
+
|
|
2185
|
+
const state = {
|
|
2186
|
+
authenticated: false,
|
|
2187
|
+
device: null
|
|
2188
|
+
};
|
|
2189
|
+
|
|
2190
|
+
const helloTimer = setTimeout(() => {
|
|
2191
|
+
if (!state.authenticated) {
|
|
2192
|
+
writeJsonLine(socket, { type: 'error', error: 'hello-timeout' });
|
|
2193
|
+
socket.destroy();
|
|
2194
|
+
}
|
|
2195
|
+
}, 10000);
|
|
2196
|
+
|
|
2197
|
+
socket.onTextMessage = text => {
|
|
2198
|
+
try {
|
|
2199
|
+
handleAgentMessage(socket, state, parseJsonLine(String(text || '')));
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
writeJsonLine(socket, { type: 'error', error: 'invalid-json' });
|
|
2202
|
+
logWarn('remote', `invalid websocket agent message: ${err?.message || err}`);
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2205
|
+
|
|
2206
|
+
socket.onBinaryMessage = payload => {
|
|
2207
|
+
try {
|
|
2208
|
+
const packet = parseRemoteHubWebSocketBinaryFrame(payload);
|
|
2209
|
+
if (!packet?.header || !Buffer.isBuffer(packet.payload)) {
|
|
2210
|
+
writeJsonLine(socket, { type: 'error', error: 'invalid-websocket-binary-frame' });
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
const frameKind = safeString(packet.header.frameKind || packet.header.kind || packet.header.frameType, 40).toLowerCase();
|
|
2215
|
+
const maxBytes = frameKind === 'thumbnail'
|
|
2216
|
+
? MAX_THUMBNAIL_BINARY_BYTES
|
|
2217
|
+
: MAX_STREAM_BINARY_BYTES;
|
|
2218
|
+
const byteLength = Number(packet.header.byteLength ?? packet.header.payloadBytes ?? packet.payload.length);
|
|
2219
|
+
if (!Number.isFinite(byteLength)
|
|
2220
|
+
|| byteLength < 1
|
|
2221
|
+
|| byteLength > maxBytes
|
|
2222
|
+
|| byteLength !== packet.payload.length) {
|
|
2223
|
+
writeJsonLine(socket, { type: 'error', error: 'invalid-binary-frame' });
|
|
2224
|
+
logWarn('remote', 'invalid websocket binary frame from agent.');
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
handleAgentBinaryFrame(socket, state, packet.header, packet.payload);
|
|
2229
|
+
} catch (err) {
|
|
2230
|
+
writeJsonLine(socket, { type: 'error', error: 'invalid-websocket-binary-frame' });
|
|
2231
|
+
logWarn('remote', `invalid websocket binary frame: ${err?.message || err}`);
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
|
|
2235
|
+
const detach = reason => {
|
|
2236
|
+
allSockets.delete(socket);
|
|
2237
|
+
clearTimeout(helloTimer);
|
|
2238
|
+
detachSocket(socket, reason);
|
|
2239
|
+
};
|
|
2240
|
+
|
|
2241
|
+
socket.onCloseMessage = reason => detach(reason || 'websocket-closed');
|
|
2242
|
+
socket.socket.on('close', () => detach('websocket-closed'));
|
|
2243
|
+
socket.socket.on('error', err => detach(err?.message || 'websocket-error'));
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
function handleWebSocketUpgradeSocket(socket, firstChunk) {
|
|
2247
|
+
let upgradeBuffer = Buffer.isBuffer(firstChunk) ? firstChunk : Buffer.from(firstChunk);
|
|
2248
|
+
let onUpgradeData = null;
|
|
2249
|
+
const fail = (status, message) => {
|
|
2250
|
+
try {
|
|
2251
|
+
socket.write(`HTTP/1.1 ${status} ${message}\r\nConnection: close\r\n\r\n`);
|
|
2252
|
+
} catch {
|
|
2253
|
+
// Ignore write failures while rejecting the upgrade.
|
|
2254
|
+
}
|
|
2255
|
+
socket.destroy();
|
|
2256
|
+
};
|
|
2257
|
+
|
|
2258
|
+
const completeUpgrade = () => {
|
|
2259
|
+
const request = parseRemoteHubWebSocketUpgrade(upgradeBuffer);
|
|
2260
|
+
if (!request) {
|
|
2261
|
+
return false;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
const upgrade = String(request.headers.upgrade || '').toLowerCase();
|
|
2265
|
+
const connection = String(request.headers.connection || '').toLowerCase();
|
|
2266
|
+
const key = safeString(request.headers['sec-websocket-key'], 256);
|
|
2267
|
+
const pathName = safeString(String(request.path || '').split('?')[0], 128) || '/';
|
|
2268
|
+
const allowedPath = pathName === '/'
|
|
2269
|
+
|| pathName === '/remote-agent'
|
|
2270
|
+
|| pathName === '/api/remote/agent/ws';
|
|
2271
|
+
if (request.method !== 'GET'
|
|
2272
|
+
|| upgrade !== 'websocket'
|
|
2273
|
+
|| !connection.includes('upgrade')
|
|
2274
|
+
|| !key
|
|
2275
|
+
|| !allowedPath) {
|
|
2276
|
+
fail(400, 'Bad Request');
|
|
2277
|
+
return true;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
const acceptKey = buildRemoteHubWebSocketAcceptKey(key);
|
|
2281
|
+
socket.write([
|
|
2282
|
+
'HTTP/1.1 101 Switching Protocols',
|
|
2283
|
+
'Upgrade: websocket',
|
|
2284
|
+
'Connection: Upgrade',
|
|
2285
|
+
`Sec-WebSocket-Accept: ${acceptKey}`,
|
|
2286
|
+
'\r\n'
|
|
2287
|
+
].join('\r\n'));
|
|
2288
|
+
|
|
2289
|
+
if (onUpgradeData) {
|
|
2290
|
+
socket.removeListener('data', onUpgradeData);
|
|
2291
|
+
}
|
|
2292
|
+
const wsSocket = new RemoteHubWebSocketAgentSocket(socket);
|
|
2293
|
+
const onFrameData = chunk => wsSocket.handleData(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2294
|
+
socket.on('data', onFrameData);
|
|
2295
|
+
socket.once('close', () => {
|
|
2296
|
+
socket.removeListener('data', onFrameData);
|
|
2297
|
+
});
|
|
2298
|
+
handleWebSocketAgentSocket(wsSocket);
|
|
2299
|
+
if (request.head.length > 0) {
|
|
2300
|
+
wsSocket.handleData(request.head);
|
|
2301
|
+
}
|
|
2302
|
+
return true;
|
|
2303
|
+
};
|
|
2304
|
+
|
|
2305
|
+
onUpgradeData = chunk => {
|
|
2306
|
+
upgradeBuffer = Buffer.concat([upgradeBuffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
2307
|
+
if (upgradeBuffer.length > 16 * 1024) {
|
|
2308
|
+
fail(431, 'Request Header Fields Too Large');
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
completeUpgrade();
|
|
2312
|
+
};
|
|
2313
|
+
|
|
2314
|
+
if (!completeUpgrade()) {
|
|
2315
|
+
socket.on('data', onUpgradeData);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
function handleTcpAgentSocket(socket, firstChunk = null) {
|
|
1925
2320
|
allSockets.add(socket);
|
|
1926
2321
|
socket.setNoDelay(true);
|
|
1927
2322
|
socket.setKeepAlive(true, heartbeatMs);
|
|
@@ -1940,7 +2335,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1940
2335
|
}
|
|
1941
2336
|
}, 10000);
|
|
1942
2337
|
|
|
1943
|
-
|
|
2338
|
+
const processData = chunk => {
|
|
1944
2339
|
const incoming = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1945
2340
|
state.buffer = state.buffer.length > 0
|
|
1946
2341
|
? Buffer.concat([state.buffer, incoming])
|
|
@@ -1999,7 +2394,12 @@ export function createRemoteHub(options = {}) {
|
|
|
1999
2394
|
logWarn('remote', `invalid agent message: ${err?.message || err}`);
|
|
2000
2395
|
}
|
|
2001
2396
|
}
|
|
2002
|
-
}
|
|
2397
|
+
};
|
|
2398
|
+
|
|
2399
|
+
socket.on('data', processData);
|
|
2400
|
+
if (firstChunk) {
|
|
2401
|
+
processData(firstChunk);
|
|
2402
|
+
}
|
|
2003
2403
|
|
|
2004
2404
|
socket.on('close', () => {
|
|
2005
2405
|
allSockets.delete(socket);
|
|
@@ -2013,6 +2413,22 @@ export function createRemoteHub(options = {}) {
|
|
|
2013
2413
|
});
|
|
2014
2414
|
}
|
|
2015
2415
|
|
|
2416
|
+
function handleSocket(socket) {
|
|
2417
|
+
socket.once('data', chunk => {
|
|
2418
|
+
const firstChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2419
|
+
if (isRemoteHubWebSocketUpgradeStart(firstChunk)) {
|
|
2420
|
+
handleWebSocketUpgradeSocket(socket, firstChunk);
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
handleTcpAgentSocket(socket, firstChunk);
|
|
2425
|
+
});
|
|
2426
|
+
|
|
2427
|
+
socket.once('error', () => {
|
|
2428
|
+
// The transport-specific handler owns logging after the first byte.
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2016
2432
|
async function start() {
|
|
2017
2433
|
if (!enabled || started) {
|
|
2018
2434
|
return getStatus({ includeSecrets: false });
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { WebSocket } from 'ws';
|
|
5
|
+
import { createRemoteHub } from '../remote-hub.js';
|
|
6
|
+
|
|
7
|
+
const PAIR_TOKEN = 'remote-agent-ws-smoke-token';
|
|
8
|
+
const DEVICE_ID = 'remote-agent-ws-smoke-device';
|
|
9
|
+
const SMOKE_PNG = Buffer.from(
|
|
10
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC',
|
|
11
|
+
'base64');
|
|
12
|
+
|
|
13
|
+
function wait(ms) {
|
|
14
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function waitFor(predicate, timeoutMs = 5000, label = 'condition') {
|
|
18
|
+
const startedAt = Date.now();
|
|
19
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
20
|
+
const value = predicate();
|
|
21
|
+
if (value) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
await wait(25);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(`Timed out waiting for ${label}.`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeAgentBinaryFrame(ws, header, payload) {
|
|
31
|
+
const framePayload = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
32
|
+
const metadata = Buffer.from(JSON.stringify({
|
|
33
|
+
...header,
|
|
34
|
+
type: 'frame.binary',
|
|
35
|
+
byteLength: framePayload.length
|
|
36
|
+
}), 'utf8');
|
|
37
|
+
const packet = Buffer.allocUnsafe(4 + metadata.length + framePayload.length);
|
|
38
|
+
packet.writeUInt32BE(metadata.length, 0);
|
|
39
|
+
metadata.copy(packet, 4);
|
|
40
|
+
framePayload.copy(packet, 4 + metadata.length);
|
|
41
|
+
ws.send(packet);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hub = createRemoteHub({
|
|
45
|
+
env: {
|
|
46
|
+
MINDEXEC_REMOTE_HUB: '1',
|
|
47
|
+
REMOTE_HUB_HOST: '127.0.0.1',
|
|
48
|
+
REMOTE_HUB_PORT: '0',
|
|
49
|
+
REMOTE_HUB_PAIR_TOKEN: PAIR_TOKEN
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let ws = null;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await hub.start();
|
|
57
|
+
const status = hub.getStatus({ includeSecrets: true });
|
|
58
|
+
assert.equal(status.started, true);
|
|
59
|
+
|
|
60
|
+
ws = new WebSocket(`ws://127.0.0.1:${status.port}/remote-agent`);
|
|
61
|
+
const received = [];
|
|
62
|
+
ws.on('message', data => {
|
|
63
|
+
if (typeof data !== 'string' && !Buffer.isBuffer(data)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const text = Buffer.isBuffer(data) ? data.toString('utf8') : data;
|
|
68
|
+
if (!text.trim().startsWith('{')) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
received.push(JSON.parse(text));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
ws.once('open', resolve);
|
|
77
|
+
ws.once('error', reject);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
ws.send(JSON.stringify({
|
|
81
|
+
type: 'hello',
|
|
82
|
+
pairToken: PAIR_TOKEN,
|
|
83
|
+
deviceId: DEVICE_ID,
|
|
84
|
+
deviceName: 'Remote Agent WS Smoke',
|
|
85
|
+
hostname: 'remote-agent-ws-smoke',
|
|
86
|
+
platform: process.platform,
|
|
87
|
+
arch: process.arch,
|
|
88
|
+
pid: process.pid,
|
|
89
|
+
agentVersion: '0.0.0-ws-smoke',
|
|
90
|
+
runtime: 'node-smoke',
|
|
91
|
+
capabilities: {
|
|
92
|
+
status: true,
|
|
93
|
+
thumbnail: true,
|
|
94
|
+
liveStream: true,
|
|
95
|
+
binaryFrames: true,
|
|
96
|
+
control: true,
|
|
97
|
+
computerAgent: true,
|
|
98
|
+
taskDispatch: true,
|
|
99
|
+
aiAssist: false
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
ws.send(JSON.stringify({
|
|
103
|
+
type: 'status',
|
|
104
|
+
status: {
|
|
105
|
+
uptimeSec: 1,
|
|
106
|
+
totalMem: 2,
|
|
107
|
+
freeMem: 1,
|
|
108
|
+
timestamp: new Date().toISOString()
|
|
109
|
+
}
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
await waitFor(() => received.find(item => item.type === 'welcome'), 5000, 'welcome');
|
|
113
|
+
const device = await waitFor(() => {
|
|
114
|
+
const current = hub.listDevices();
|
|
115
|
+
return current.length === 1 && current[0].deviceId === DEVICE_ID ? current[0] : null;
|
|
116
|
+
}, 5000, 'device registration');
|
|
117
|
+
assert.equal(device.connected, true);
|
|
118
|
+
assert.equal(device.capabilities.binaryFrames, true);
|
|
119
|
+
|
|
120
|
+
const inputCommand = hub.sendInputControl(DEVICE_ID, { type: 'noop' });
|
|
121
|
+
assert.equal(inputCommand.ok, true);
|
|
122
|
+
const inputMessage = await waitFor(
|
|
123
|
+
() => received.find(item => item.commandId === inputCommand.commandId),
|
|
124
|
+
5000,
|
|
125
|
+
'input command');
|
|
126
|
+
assert.equal(inputMessage.command, 'input.control');
|
|
127
|
+
ws.send(JSON.stringify({
|
|
128
|
+
type: 'command.result',
|
|
129
|
+
commandId: inputCommand.commandId,
|
|
130
|
+
result: {
|
|
131
|
+
input: true,
|
|
132
|
+
handled: true
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
135
|
+
await waitFor(() => hub.listDevices()[0]?.counters?.commandResultsReceived === 1, 5000, 'input result');
|
|
136
|
+
|
|
137
|
+
const thumbnailCommand = hub.requestThumbnail(DEVICE_ID, {
|
|
138
|
+
streamId: 'remote-agent-ws-thumb',
|
|
139
|
+
maxWidth: 320,
|
|
140
|
+
maxHeight: 180,
|
|
141
|
+
quality: 50
|
|
142
|
+
});
|
|
143
|
+
assert.equal(thumbnailCommand.ok, true);
|
|
144
|
+
await waitFor(
|
|
145
|
+
() => received.find(item => item.commandId === thumbnailCommand.commandId),
|
|
146
|
+
5000,
|
|
147
|
+
'thumbnail command');
|
|
148
|
+
writeAgentBinaryFrame(ws, {
|
|
149
|
+
frameKind: 'thumbnail',
|
|
150
|
+
commandId: thumbnailCommand.commandId,
|
|
151
|
+
streamId: 'remote-agent-ws-thumb',
|
|
152
|
+
frameSeq: 11,
|
|
153
|
+
width: 2,
|
|
154
|
+
height: 1,
|
|
155
|
+
mimeType: 'image/png',
|
|
156
|
+
capturedAt: new Date().toISOString()
|
|
157
|
+
}, SMOKE_PNG);
|
|
158
|
+
const thumbnailDevice = await waitFor(() => {
|
|
159
|
+
const current = hub.listDevices()[0];
|
|
160
|
+
return current?.latestThumbnail?.frameSeq === 11 ? current : null;
|
|
161
|
+
}, 5000, 'websocket binary thumbnail');
|
|
162
|
+
assert.equal(thumbnailDevice.latestThumbnail.transport, 'binary');
|
|
163
|
+
assert.equal(thumbnailDevice.latestThumbnail.byteLength, SMOKE_PNG.length);
|
|
164
|
+
|
|
165
|
+
const liveCommand = hub.startLiveStream(DEVICE_ID, {
|
|
166
|
+
streamId: 'remote-agent-ws-live',
|
|
167
|
+
fps: 10,
|
|
168
|
+
maxWidth: 320,
|
|
169
|
+
maxHeight: 180,
|
|
170
|
+
quality: 50
|
|
171
|
+
});
|
|
172
|
+
assert.equal(liveCommand.ok, true);
|
|
173
|
+
await waitFor(
|
|
174
|
+
() => received.find(item => item.commandId === liveCommand.commandId),
|
|
175
|
+
5000,
|
|
176
|
+
'live command');
|
|
177
|
+
writeAgentBinaryFrame(ws, {
|
|
178
|
+
frameKind: 'stream',
|
|
179
|
+
commandId: liveCommand.commandId,
|
|
180
|
+
streamId: 'remote-agent-ws-live',
|
|
181
|
+
frameSeq: 12,
|
|
182
|
+
width: 2,
|
|
183
|
+
height: 1,
|
|
184
|
+
mimeType: 'image/png',
|
|
185
|
+
fps: 10,
|
|
186
|
+
capturedAt: new Date().toISOString()
|
|
187
|
+
}, SMOKE_PNG);
|
|
188
|
+
const liveDevice = await waitFor(() => {
|
|
189
|
+
const current = hub.listDevices()[0];
|
|
190
|
+
return current?.latestLiveFrame?.frameSeq === 12 ? current : null;
|
|
191
|
+
}, 5000, 'websocket binary live frame');
|
|
192
|
+
assert.equal(liveDevice.latestLiveFrame.transport, 'binary');
|
|
193
|
+
assert.equal(liveDevice.latestLiveFrame.streamId, 'remote-agent-ws-live');
|
|
194
|
+
|
|
195
|
+
ws.close();
|
|
196
|
+
console.log('RemoteAgent WebSocket smoke OK');
|
|
197
|
+
} finally {
|
|
198
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
199
|
+
ws.close();
|
|
200
|
+
}
|
|
201
|
+
await hub.close();
|
|
202
|
+
}
|
|
@@ -170,7 +170,7 @@ async function main() {
|
|
|
170
170
|
const framePromise = waitForBinaryFrame(ws);
|
|
171
171
|
const live = await fetchJson(`${bridge.baseUrl}/api/remote/devices/${encodeURIComponent(device.deviceId)}/live/start`, {
|
|
172
172
|
method: 'POST',
|
|
173
|
-
body: JSON.stringify({ fps:
|
|
173
|
+
body: JSON.stringify({ fps: 20 })
|
|
174
174
|
});
|
|
175
175
|
assert.equal(live.ok, true);
|
|
176
176
|
|
|
@@ -178,6 +178,7 @@ async function main() {
|
|
|
178
178
|
assert.equal(frame.metadata.type, 'remote.frame.binary');
|
|
179
179
|
assert.equal(frame.metadata.kind, 'live');
|
|
180
180
|
assert.equal(frame.metadata.deviceId, device.deviceId);
|
|
181
|
+
assert.equal(frame.metadata.fps, 20);
|
|
181
182
|
assert.ok(frame.metadata.frameSeq > 0, 'frameSeq should be positive');
|
|
182
183
|
assert.equal(frame.metadata.mimeType, 'image/png');
|
|
183
184
|
assert.ok(frame.payload.length > 0, 'payload should be non-empty');
|
|
@@ -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-ws-
|
|
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,6 +13345,8 @@
|
|
|
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;
|
|
@@ -13622,7 +13624,8 @@
|
|
|
13622
13624
|
|
|
13623
13625
|
const payload = buffer.slice(4 + metaLength);
|
|
13624
13626
|
const mimeType = String(metadata.mimeType || metadata.format || 'image/jpeg').trim() || 'image/jpeg';
|
|
13625
|
-
const
|
|
13627
|
+
const payloadBlob = new Blob([payload], { type: mimeType });
|
|
13628
|
+
const objectUrl = URL.createObjectURL(payloadBlob);
|
|
13626
13629
|
return {
|
|
13627
13630
|
...metadata,
|
|
13628
13631
|
kind: String(metadata.kind || 'live').toLowerCase() === 'thumbnail' ? 'thumbnail' : 'live',
|
|
@@ -13631,6 +13634,7 @@
|
|
|
13631
13634
|
framePath: objectUrl,
|
|
13632
13635
|
dataUrl: '',
|
|
13633
13636
|
_remoteFleetObjectUrl: objectUrl,
|
|
13637
|
+
_remoteFleetPayloadBlob: payloadBlob,
|
|
13634
13638
|
_remoteFleetBinaryFrame: true
|
|
13635
13639
|
};
|
|
13636
13640
|
}
|
|
@@ -13650,13 +13654,27 @@
|
|
|
13650
13654
|
}
|
|
13651
13655
|
}
|
|
13652
13656
|
|
|
13653
|
-
function getRemoteFleetBinaryFrameDeviceIds(
|
|
13657
|
+
function getRemoteFleetBinaryFrameDeviceIds(sessionOrGetter) {
|
|
13658
|
+
const getDeviceIds = typeof sessionOrGetter === 'function'
|
|
13659
|
+
? sessionOrGetter
|
|
13660
|
+
: sessionOrGetter?.getDeviceIds;
|
|
13654
13661
|
const ids = typeof getDeviceIds === 'function'
|
|
13655
13662
|
? getDeviceIds()
|
|
13656
13663
|
: [];
|
|
13657
|
-
|
|
13664
|
+
const normalized = (Array.isArray(ids) ? ids : [])
|
|
13658
13665
|
.map(value => String(value || '').trim())
|
|
13659
|
-
.filter(Boolean)
|
|
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))
|
|
13660
13678
|
.slice(0, 240);
|
|
13661
13679
|
}
|
|
13662
13680
|
|
|
@@ -13665,7 +13683,7 @@
|
|
|
13665
13683
|
return false;
|
|
13666
13684
|
}
|
|
13667
13685
|
|
|
13668
|
-
const deviceIds = getRemoteFleetBinaryFrameDeviceIds(session
|
|
13686
|
+
const deviceIds = getRemoteFleetBinaryFrameDeviceIds(session);
|
|
13669
13687
|
const key = deviceIds.join('\n');
|
|
13670
13688
|
if (!force && session.subscriptionKey === key) {
|
|
13671
13689
|
return true;
|
|
@@ -13681,7 +13699,7 @@
|
|
|
13681
13699
|
}
|
|
13682
13700
|
|
|
13683
13701
|
function scheduleRemoteFleetBinaryFrameReconnect(session) {
|
|
13684
|
-
if (!session || session.reconnectTimer || session.bodies.size === 0) {
|
|
13702
|
+
if (!session || session.reconnectTimer || (session.bodies.size === 0 && (!session.controlSessions || session.controlSessions.size === 0))) {
|
|
13685
13703
|
return;
|
|
13686
13704
|
}
|
|
13687
13705
|
|
|
@@ -13692,7 +13710,7 @@
|
|
|
13692
13710
|
}
|
|
13693
13711
|
|
|
13694
13712
|
async function openRemoteFleetBinaryFrameSocket(session) {
|
|
13695
|
-
if (!session || session.bodies.size === 0 || typeof WebSocket !== 'function') {
|
|
13713
|
+
if (!session || (session.bodies.size === 0 && (!session.controlSessions || session.controlSessions.size === 0)) || typeof WebSocket !== 'function') {
|
|
13696
13714
|
return false;
|
|
13697
13715
|
}
|
|
13698
13716
|
|
|
@@ -13733,7 +13751,7 @@
|
|
|
13733
13751
|
|
|
13734
13752
|
parseRemoteFleetBinaryFrameMessage(event.data)
|
|
13735
13753
|
.then(frame => {
|
|
13736
|
-
if (!frame || session.bodies.size === 0) {
|
|
13754
|
+
if (!frame || (session.bodies.size === 0 && (!session.controlSessions || session.controlSessions.size === 0))) {
|
|
13737
13755
|
if (frame?._remoteFleetObjectUrl && typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {
|
|
13738
13756
|
URL.revokeObjectURL(frame._remoteFleetObjectUrl);
|
|
13739
13757
|
}
|
|
@@ -13747,7 +13765,38 @@
|
|
|
13747
13765
|
}
|
|
13748
13766
|
});
|
|
13749
13767
|
|
|
13750
|
-
|
|
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') {
|
|
13751
13800
|
URL.revokeObjectURL(frame._remoteFleetObjectUrl);
|
|
13752
13801
|
}
|
|
13753
13802
|
})
|
|
@@ -13798,6 +13847,7 @@
|
|
|
13798
13847
|
reconnectTimer: null,
|
|
13799
13848
|
subscriptionTimer: null,
|
|
13800
13849
|
subscriptionKey: '',
|
|
13850
|
+
controlSessions: new Set(),
|
|
13801
13851
|
getDeviceIds: null
|
|
13802
13852
|
};
|
|
13803
13853
|
remoteFleetBinaryFrameSessions.set(nodeId, session);
|
|
@@ -13818,7 +13868,8 @@
|
|
|
13818
13868
|
|
|
13819
13869
|
delete bodyView._remoteFleetBinaryFrameSession;
|
|
13820
13870
|
session.bodies.delete(bodyView);
|
|
13821
|
-
if (session.bodies.size > 0) {
|
|
13871
|
+
if (session.bodies.size > 0 || (session.controlSessions instanceof Set && session.controlSessions.size > 0)) {
|
|
13872
|
+
refreshRemoteFleetBinaryFrameSubscription(session, true);
|
|
13822
13873
|
return;
|
|
13823
13874
|
}
|
|
13824
13875
|
|
|
@@ -14852,6 +14903,7 @@
|
|
|
14852
14903
|
|
|
14853
14904
|
activeRemoteFleetControlPopup = null;
|
|
14854
14905
|
session.active = false;
|
|
14906
|
+
detachRemoteFleetControlBinaryFrameSession(session);
|
|
14855
14907
|
if (session.timer) {
|
|
14856
14908
|
clearTimeout(session.timer);
|
|
14857
14909
|
session.timer = null;
|
|
@@ -14873,6 +14925,66 @@
|
|
|
14873
14925
|
}
|
|
14874
14926
|
}
|
|
14875
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
|
+
|
|
14876
14988
|
async function paintRemoteFleetControlFrame(session, frame) {
|
|
14877
14989
|
if (!session?.active || !frame || !isRemoteFleetFrameSource(frame.frameUrl)) {
|
|
14878
14990
|
return false;
|
|
@@ -14904,6 +15016,59 @@
|
|
|
14904
15016
|
}
|
|
14905
15017
|
}
|
|
14906
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
|
+
|
|
14907
15072
|
function sendRemoteFleetControlInput(session, payload, throttleMove = false) {
|
|
14908
15073
|
if (!session?.active || !payload?.type) {
|
|
14909
15074
|
return;
|
|
@@ -15224,16 +15389,29 @@
|
|
|
15224
15389
|
timer: null,
|
|
15225
15390
|
moveTimer: null,
|
|
15226
15391
|
pendingMove: null,
|
|
15227
|
-
lastMoveSentAt: 0
|
|
15392
|
+
lastMoveSentAt: 0,
|
|
15393
|
+
lastControlFrameAt: 0,
|
|
15394
|
+
lastControlFrameSeq: 0,
|
|
15395
|
+
binaryFramePreferred: false
|
|
15228
15396
|
};
|
|
15229
15397
|
activeRemoteFleetControlPopup = session;
|
|
15230
15398
|
bindRemoteFleetControlInput(session);
|
|
15399
|
+
attachRemoteFleetControlBinaryFrameSession(session, bodyView);
|
|
15231
15400
|
|
|
15232
15401
|
const refresh = async () => {
|
|
15233
15402
|
if (!session.active) {
|
|
15234
15403
|
return;
|
|
15235
15404
|
}
|
|
15236
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
|
+
|
|
15237
15415
|
try {
|
|
15238
15416
|
const result = await invokeDotNetAsync('GetRemoteFleetFrameFromJs', nodeId, targetId, 'live');
|
|
15239
15417
|
const frame = normalizeRemoteFleetFramePayload(result?.frame || result?.Frame, 'live');
|
|
@@ -15241,7 +15419,8 @@
|
|
|
15241
15419
|
frame.deviceId = frame.deviceId || targetId;
|
|
15242
15420
|
await paintRemoteFleetControlFrame(session, frame);
|
|
15243
15421
|
applyRemoteFleetFramePatches(bodyView, [frame]);
|
|
15244
|
-
|
|
15422
|
+
session.lastControlFrameAt = Date.now();
|
|
15423
|
+
status.textContent = session.binaryFramePreferred ? 'Control fallback' : `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
|
|
15245
15424
|
} else if (result?.error || result?.Error) {
|
|
15246
15425
|
status.textContent = result.error || result.Error;
|
|
15247
15426
|
}
|
|
@@ -15249,7 +15428,7 @@
|
|
|
15249
15428
|
status.textContent = error?.message || 'Frame failed';
|
|
15250
15429
|
} finally {
|
|
15251
15430
|
if (session.active) {
|
|
15252
|
-
session.timer = setTimeout(refresh,
|
|
15431
|
+
session.timer = setTimeout(refresh, REMOTE_FLEET_CONTROL_FRAME_REFRESH_MS);
|
|
15253
15432
|
}
|
|
15254
15433
|
}
|
|
15255
15434
|
};
|
|
@@ -15273,7 +15452,7 @@
|
|
|
15273
15452
|
deviceId: targetId
|
|
15274
15453
|
});
|
|
15275
15454
|
|
|
15276
|
-
invokeDotNetAsync('StartRemoteFleetLiveStreamFromJs', nodeId, targetId)
|
|
15455
|
+
invokeDotNetAsync('StartRemoteFleetLiveStreamFromJs', nodeId, targetId, REMOTE_FLEET_CONTROL_LIVE_FPS)
|
|
15277
15456
|
.then(async result => {
|
|
15278
15457
|
if (!session.active) {
|
|
15279
15458
|
return;
|
|
@@ -15282,7 +15461,7 @@
|
|
|
15282
15461
|
await syncRemoteFleetNodeStateFromResult(result);
|
|
15283
15462
|
session.startedStream = result?.success === true || result?.Success === true;
|
|
15284
15463
|
session.streamId = String(result?.streamId || result?.StreamId || '');
|
|
15285
|
-
status.textContent = session.startedStream ?
|
|
15464
|
+
status.textContent = session.startedStream ? `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps` : (result?.error || result?.Error || 'View');
|
|
15286
15465
|
refresh();
|
|
15287
15466
|
})
|
|
15288
15467
|
.catch(error => {
|
|
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-ws-
|
|
11
|
-
<link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-remote-ws-
|
|
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-ws-
|
|
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
|
{
|