@mindexec/cli 0.2.116 → 0.2.118
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/server.js +77 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +45 -0
- package/wwwroot/_framework/MindExecution.Core.asayb4hw1o.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.pbzp3jfync.dll → MindExecution.Kernel.3zom09ir6j.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.gxzwlji1cf.dll → MindExecution.Plugins.Admin.bk23zzy6wc.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.vtaey8c59y.dll → MindExecution.Plugins.Business.zwcvc8wufe.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.85y7un1ks6.dll → MindExecution.Plugins.Concept.lxunwcdjgq.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.zc8ffaoknd.dll → MindExecution.Plugins.Directory.jc0g7fvyzv.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.ylooagumgh.dll → MindExecution.Plugins.PlanMaster.2sugb66h95.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.azfozpumhv.dll → MindExecution.Plugins.YouTube.vy0nsbcoo4.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.mppf8quyau.dll → MindExecution.Shared.e8lipb57xv.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Web.994sb3nzuy.dll +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/service-worker-assets.js +23 -23
- package/wwwroot/service-worker.js +1 -1
- package/wwwroot/_framework/MindExecution.Core.fc9cjbjplq.dll +0 -0
- package/wwwroot/_framework/MindExecution.Web.tln762tijf.dll +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindexec/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.118",
|
|
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
|
+
}
|
package/server.js
CHANGED
|
@@ -2906,7 +2906,11 @@ const REMOTE_AGENT_STDIO_TAIL_CHARS = 12000;
|
|
|
2906
2906
|
const REMOTE_AGENT_EARLY_EXIT_MS = 1200;
|
|
2907
2907
|
const REMOTE_AGENT_READY_TIMEOUT_MS = 5000;
|
|
2908
2908
|
const REMOTE_AGENT_DEFAULT_ENGINE = 'auto';
|
|
2909
|
+
const REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS = 30000;
|
|
2909
2910
|
let remoteAgentState = createRemoteAgentIdleState();
|
|
2911
|
+
let remoteAgentSyncReportState = null;
|
|
2912
|
+
let remoteAgentSyncReportLogKey = '';
|
|
2913
|
+
let remoteAgentSyncReportLogAt = 0;
|
|
2910
2914
|
|
|
2911
2915
|
function createRemoteAgentIdleState(overrides = {}) {
|
|
2912
2916
|
return {
|
|
@@ -2940,6 +2944,7 @@ function serializeRemoteAgentState() {
|
|
|
2940
2944
|
const { proc, ...publicState } = remoteAgentState;
|
|
2941
2945
|
return {
|
|
2942
2946
|
...publicState,
|
|
2947
|
+
registrySync: remoteAgentSyncReportState,
|
|
2943
2948
|
running: remoteAgentState.status === 'running'
|
|
2944
2949
|
&& !!remoteAgentState.proc
|
|
2945
2950
|
&& remoteAgentState.proc.exitCode === null
|
|
@@ -2947,6 +2952,71 @@ function serializeRemoteAgentState() {
|
|
|
2947
2952
|
};
|
|
2948
2953
|
}
|
|
2949
2954
|
|
|
2955
|
+
function createRemoteAgentSyncReport(body = {}) {
|
|
2956
|
+
const normalizeList = value => Array.isArray(value)
|
|
2957
|
+
? value.map(item => safeRemoteAgentField(item, 160)).filter(Boolean).slice(0, 12)
|
|
2958
|
+
: [];
|
|
2959
|
+
return {
|
|
2960
|
+
ok: body.ok === true || body.Ok === true,
|
|
2961
|
+
attempted: body.attempted === true || body.Attempted === true,
|
|
2962
|
+
connected: body.connected === true || body.Connected === true,
|
|
2963
|
+
disconnected: body.disconnected === true || body.Disconnected === true,
|
|
2964
|
+
skipped: body.skipped === true || body.Skipped === true,
|
|
2965
|
+
reason: safeRemoteAgentField(body.reason || body.Reason, 160),
|
|
2966
|
+
error: safeRemoteAgentField(body.error || body.Error, 240),
|
|
2967
|
+
trigger: safeRemoteAgentField(body.trigger || body.Trigger, 80),
|
|
2968
|
+
authenticated: body.authenticated === true || body.Authenticated === true,
|
|
2969
|
+
localHostTargetActive: body.localHostTargetActive === true || body.LocalHostTargetActive === true,
|
|
2970
|
+
localHostTargetLeaseId: safeRemoteAgentField(body.localHostTargetLeaseId || body.LocalHostTargetLeaseId, 128),
|
|
2971
|
+
localHostTargetNodeId: safeRemoteAgentField(body.localHostTargetNodeId || body.LocalHostTargetNodeId, 128),
|
|
2972
|
+
targetActive: body.targetActive === true || body.TargetActive === true,
|
|
2973
|
+
targetEndpoint: normalizeRemoteManagerEndpoint(body.targetEndpoint || body.TargetEndpoint),
|
|
2974
|
+
targetEndpointCandidates: normalizeList(body.targetEndpointCandidates || body.TargetEndpointCandidates),
|
|
2975
|
+
targetLeaseId: safeRemoteAgentField(body.targetLeaseId || body.TargetLeaseId, 128),
|
|
2976
|
+
targetNodeId: safeRemoteAgentField(body.targetNodeId || body.TargetNodeId, 128),
|
|
2977
|
+
targetExpiresAt: safeRemoteAgentField(body.targetExpiresAt || body.TargetExpiresAt, 80),
|
|
2978
|
+
updatedAt: new Date().toISOString()
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
function rememberRemoteAgentSyncReport(report) {
|
|
2983
|
+
remoteAgentSyncReportState = report;
|
|
2984
|
+
const reason = report.reason || report.error || (report.ok ? 'ok' : 'failed');
|
|
2985
|
+
const status = report.connected
|
|
2986
|
+
? 'connected'
|
|
2987
|
+
: report.disconnected
|
|
2988
|
+
? 'disconnected'
|
|
2989
|
+
: report.attempted
|
|
2990
|
+
? 'attempted'
|
|
2991
|
+
: report.skipped
|
|
2992
|
+
? 'skipped'
|
|
2993
|
+
: report.ok
|
|
2994
|
+
? 'ok'
|
|
2995
|
+
: 'failed';
|
|
2996
|
+
const key = [
|
|
2997
|
+
status,
|
|
2998
|
+
reason,
|
|
2999
|
+
report.targetEndpoint || '',
|
|
3000
|
+
report.targetLeaseId || '',
|
|
3001
|
+
report.localHostTargetActive ? 'local-host' : ''
|
|
3002
|
+
].join('|');
|
|
3003
|
+
const now = Date.now();
|
|
3004
|
+
if (reason === 'throttled') {
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
if (key === remoteAgentSyncReportLogKey
|
|
3008
|
+
&& now - remoteAgentSyncReportLogAt < REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS) {
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
remoteAgentSyncReportLogKey = key;
|
|
3013
|
+
remoteAgentSyncReportLogAt = now;
|
|
3014
|
+
logEvent(
|
|
3015
|
+
'remote',
|
|
3016
|
+
`registry sync ${status} ${formatKeyValue('reason', reason)} ${formatKeyValue('target', report.targetEndpoint || '-')} ${formatKeyValue('auth', report.authenticated ? 'yes' : 'no')}`,
|
|
3017
|
+
'remote');
|
|
3018
|
+
}
|
|
3019
|
+
|
|
2950
3020
|
function appendRemoteAgentOutput(stream, chunk, stateConnectionKey = '') {
|
|
2951
3021
|
if (stateConnectionKey && remoteAgentState.connectionKey !== stateConnectionKey) {
|
|
2952
3022
|
return;
|
|
@@ -8066,6 +8136,13 @@ app.get('/api/remote/agent/status', (req, res) => {
|
|
|
8066
8136
|
res.json(serializeRemoteAgentState());
|
|
8067
8137
|
});
|
|
8068
8138
|
|
|
8139
|
+
app.post('/api/remote/agent/sync-report', (req, res) => {
|
|
8140
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
8141
|
+
const report = createRemoteAgentSyncReport(req.body || {});
|
|
8142
|
+
rememberRemoteAgentSyncReport(report);
|
|
8143
|
+
res.json({ ok: true, report });
|
|
8144
|
+
});
|
|
8145
|
+
|
|
8069
8146
|
app.post('/api/remote/agent/connect', async (req, res) => {
|
|
8070
8147
|
res.setHeader('Cache-Control', 'no-store');
|
|
8071
8148
|
try {
|
|
@@ -13359,6 +13359,8 @@
|
|
|
13359
13359
|
const REMOTE_FLEET_TASK_FOLLOW_REFRESH_MS = 2000;
|
|
13360
13360
|
const REMOTE_FLEET_TASK_FOLLOW_MAX_TICKS = 60;
|
|
13361
13361
|
const REMOTE_FLEET_HOST_LEASE_REFRESH_MS = 10000;
|
|
13362
|
+
const REMOTE_FLEET_AUTO_HOST_STORAGE_KEY = 'MindExec.RemoteFleet.AutoHost';
|
|
13363
|
+
const REMOTE_FLEET_AUTO_HOST_NODE_STORAGE_KEY = 'MindExec.RemoteFleet.AutoHostNodeId';
|
|
13362
13364
|
const remoteFleetHostLeaseTimers = new Map();
|
|
13363
13365
|
const remoteFleetLocalHostTargets = new Map();
|
|
13364
13366
|
const remoteFleetBinaryFrameSessions = new Map();
|
|
@@ -13445,6 +13447,31 @@
|
|
|
13445
13447
|
return result?.active === false || result?.Active === false;
|
|
13446
13448
|
}
|
|
13447
13449
|
|
|
13450
|
+
function isRemoteFleetAutoHostEnabled(nodeId) {
|
|
13451
|
+
try {
|
|
13452
|
+
const enabled = String(localStorage.getItem(REMOTE_FLEET_AUTO_HOST_STORAGE_KEY) || '').trim().toLowerCase();
|
|
13453
|
+
if (enabled !== 'true') return false;
|
|
13454
|
+
const storedNodeId = String(localStorage.getItem(REMOTE_FLEET_AUTO_HOST_NODE_STORAGE_KEY) || '').trim();
|
|
13455
|
+
return !storedNodeId || storedNodeId === String(nodeId || '').trim();
|
|
13456
|
+
} catch {
|
|
13457
|
+
return false;
|
|
13458
|
+
}
|
|
13459
|
+
}
|
|
13460
|
+
|
|
13461
|
+
function rememberRemoteFleetAutoHostPreference(nodeId, enabled) {
|
|
13462
|
+
try {
|
|
13463
|
+
if (enabled === true) {
|
|
13464
|
+
localStorage.setItem(REMOTE_FLEET_AUTO_HOST_STORAGE_KEY, 'true');
|
|
13465
|
+
localStorage.setItem(REMOTE_FLEET_AUTO_HOST_NODE_STORAGE_KEY, String(nodeId || '').trim());
|
|
13466
|
+
} else {
|
|
13467
|
+
localStorage.removeItem(REMOTE_FLEET_AUTO_HOST_STORAGE_KEY);
|
|
13468
|
+
localStorage.removeItem(REMOTE_FLEET_AUTO_HOST_NODE_STORAGE_KEY);
|
|
13469
|
+
}
|
|
13470
|
+
} catch {
|
|
13471
|
+
// localStorage may be unavailable in restricted browser modes.
|
|
13472
|
+
}
|
|
13473
|
+
}
|
|
13474
|
+
|
|
13448
13475
|
function rememberRemoteFleetLocalHostTarget(nodeId, enabled, result) {
|
|
13449
13476
|
const id = String(nodeId || '').trim();
|
|
13450
13477
|
if (!id) return;
|
|
@@ -17371,6 +17398,7 @@
|
|
|
17371
17398
|
const setRemoteFleetHostTarget = async (enabled, options = {}) => {
|
|
17372
17399
|
const quiet = options?.quiet === true;
|
|
17373
17400
|
const renew = options?.renew === true || quiet;
|
|
17401
|
+
const syncNodeState = quiet !== true || options?.sync === true;
|
|
17374
17402
|
const activeButton = enabled ? hostButton : null;
|
|
17375
17403
|
if (!quiet && activeButton) {
|
|
17376
17404
|
activeButton.disabled = true;
|
|
@@ -17389,10 +17417,16 @@
|
|
|
17389
17417
|
error: result?.error || result?.Error || ''
|
|
17390
17418
|
});
|
|
17391
17419
|
rememberRemoteFleetLocalHostTarget(nodeId, enabled === true, result);
|
|
17420
|
+
if (isRemoteFleetResultSuccess(result)) {
|
|
17421
|
+
rememberRemoteFleetAutoHostPreference(nodeId, enabled === true && isRemoteFleetResultActive(result));
|
|
17422
|
+
}
|
|
17392
17423
|
if (quiet) {
|
|
17393
17424
|
if (!isRemoteFleetResultSuccess(result) || !isRemoteFleetResultActive(result)) {
|
|
17394
17425
|
stopRemoteFleetHostLeaseTimer(nodeId);
|
|
17395
17426
|
}
|
|
17427
|
+
if (syncNodeState) {
|
|
17428
|
+
await syncRemoteFleetNodeStateFromResult(result);
|
|
17429
|
+
}
|
|
17396
17430
|
return result;
|
|
17397
17431
|
}
|
|
17398
17432
|
await syncRemoteFleetNodeStateFromResult(result);
|
|
@@ -17577,6 +17611,17 @@
|
|
|
17577
17611
|
|
|
17578
17612
|
if (isHostingTarget) {
|
|
17579
17613
|
startRemoteFleetHostLeaseTimer(nodeId, () => setRemoteFleetHostTarget(true, { quiet: true, renew: true }));
|
|
17614
|
+
} else if (isRemoteFleetAutoHostEnabled(nodeId) && bodyView._remoteFleetAutoHostRestoreStarted !== true) {
|
|
17615
|
+
bodyView._remoteFleetAutoHostRestoreStarted = true;
|
|
17616
|
+
setTimeout(() => {
|
|
17617
|
+
if (!document.body.contains(bodyView)) return;
|
|
17618
|
+
setRemoteFleetHostTarget(true, { quiet: true, renew: true, sync: true }).catch(error => {
|
|
17619
|
+
window.RuntimeTrace?.emit?.('remote.hostTarget.autoRestoreFailed', {
|
|
17620
|
+
nodeId,
|
|
17621
|
+
error: error?.message || String(error || '')
|
|
17622
|
+
});
|
|
17623
|
+
});
|
|
17624
|
+
}, 250);
|
|
17580
17625
|
}
|
|
17581
17626
|
|
|
17582
17627
|
const hasVisibleLiveTarget = getVisibleLiveFrameDeviceIds().length > 0
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"mainAssemblyName": "MindExecution.Web",
|
|
3
3
|
"resources": {
|
|
4
|
-
"hash": "sha256-
|
|
4
|
+
"hash": "sha256-Sm5LEcaIEbOQMLRdM7FmN29gN1AP1yTcQOVH+E/ZgGY=",
|
|
5
5
|
"fingerprinting": {
|
|
6
6
|
"Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
|
|
7
7
|
"Markdig.d1j7v41cl1.dll": "Markdig.dll",
|
|
@@ -123,16 +123,16 @@
|
|
|
123
123
|
"System.brmz7yk5qh.dll": "System.dll",
|
|
124
124
|
"netstandard.yvr3prsx0x.dll": "netstandard.dll",
|
|
125
125
|
"System.Private.CoreLib.c1dbswx1b2.dll": "System.Private.CoreLib.dll",
|
|
126
|
-
"MindExecution.Core.
|
|
127
|
-
"MindExecution.Kernel.
|
|
128
|
-
"MindExecution.Plugins.Admin.
|
|
129
|
-
"MindExecution.Plugins.Business.
|
|
130
|
-
"MindExecution.Plugins.Concept.
|
|
131
|
-
"MindExecution.Plugins.Directory.
|
|
132
|
-
"MindExecution.Plugins.PlanMaster.
|
|
133
|
-
"MindExecution.Plugins.YouTube.
|
|
134
|
-
"MindExecution.Shared.
|
|
135
|
-
"MindExecution.Web.
|
|
126
|
+
"MindExecution.Core.asayb4hw1o.dll": "MindExecution.Core.dll",
|
|
127
|
+
"MindExecution.Kernel.3zom09ir6j.dll": "MindExecution.Kernel.dll",
|
|
128
|
+
"MindExecution.Plugins.Admin.bk23zzy6wc.dll": "MindExecution.Plugins.Admin.dll",
|
|
129
|
+
"MindExecution.Plugins.Business.zwcvc8wufe.dll": "MindExecution.Plugins.Business.dll",
|
|
130
|
+
"MindExecution.Plugins.Concept.lxunwcdjgq.dll": "MindExecution.Plugins.Concept.dll",
|
|
131
|
+
"MindExecution.Plugins.Directory.jc0g7fvyzv.dll": "MindExecution.Plugins.Directory.dll",
|
|
132
|
+
"MindExecution.Plugins.PlanMaster.2sugb66h95.dll": "MindExecution.Plugins.PlanMaster.dll",
|
|
133
|
+
"MindExecution.Plugins.YouTube.vy0nsbcoo4.dll": "MindExecution.Plugins.YouTube.dll",
|
|
134
|
+
"MindExecution.Shared.e8lipb57xv.dll": "MindExecution.Shared.dll",
|
|
135
|
+
"MindExecution.Web.994sb3nzuy.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",
|
|
@@ -278,18 +278,18 @@
|
|
|
278
278
|
"System.Xml.XDocument.sn51jas17n.dll": "sha256-GNI2kFgFmPTwzuzwUn8gxK+AzGLUWRJFdg9JzIbrybQ=",
|
|
279
279
|
"System.brmz7yk5qh.dll": "sha256-CfM2miyj1KHApFmqMdLYWio3S/jrdON2pW9Xr2nTwlo=",
|
|
280
280
|
"netstandard.yvr3prsx0x.dll": "sha256-EksNn8Luo4bOWqJ6X7dIe9qG9oOqwOVzjH2xYyMNi+E=",
|
|
281
|
-
"MindExecution.Core.
|
|
282
|
-
"MindExecution.Kernel.
|
|
283
|
-
"MindExecution.Plugins.Concept.
|
|
284
|
-
"MindExecution.Plugins.PlanMaster.
|
|
285
|
-
"MindExecution.Shared.
|
|
286
|
-
"MindExecution.Web.
|
|
281
|
+
"MindExecution.Core.asayb4hw1o.dll": "sha256-5gla4JTR/zbvkGpneSt6S7ItE51+MjWgjW6L+B6Yevk=",
|
|
282
|
+
"MindExecution.Kernel.3zom09ir6j.dll": "sha256-78vR2SNkF9FwdQv0fEItSyz4HzgyzwGUROJAnpripPA=",
|
|
283
|
+
"MindExecution.Plugins.Concept.lxunwcdjgq.dll": "sha256-WVyvjaxW6UoGQvGW0icChLsXwYVCcdn0BLTftzlkCKc=",
|
|
284
|
+
"MindExecution.Plugins.PlanMaster.2sugb66h95.dll": "sha256-Lp0AieRHDo2H4Yl0Il1pPruy9nFUHJsX9t1AblXGD6Y=",
|
|
285
|
+
"MindExecution.Shared.e8lipb57xv.dll": "sha256-Au9QMwnm9MaP2+xzdldeQYtj4tRXzwOBWYOxmYR8spM=",
|
|
286
|
+
"MindExecution.Web.994sb3nzuy.dll": "sha256-jQshtLmhEC4xenzdD9PqHjaCuKvJf/frzaKBi2weDAg="
|
|
287
287
|
},
|
|
288
288
|
"lazyAssembly": {
|
|
289
|
-
"MindExecution.Plugins.Admin.
|
|
290
|
-
"MindExecution.Plugins.Business.
|
|
291
|
-
"MindExecution.Plugins.Directory.
|
|
292
|
-
"MindExecution.Plugins.YouTube.
|
|
289
|
+
"MindExecution.Plugins.Admin.bk23zzy6wc.dll": "sha256-uz7Nz0OZ8BBESp2Nr9YocyzV0dUrn7szPuD4/m/RWL8=",
|
|
290
|
+
"MindExecution.Plugins.Business.zwcvc8wufe.dll": "sha256-3YDsB2/uNI+feWqJk+cesLHgNt8s2/8hXxGPO6CRZZE=",
|
|
291
|
+
"MindExecution.Plugins.Directory.jc0g7fvyzv.dll": "sha256-PSe7135JPpTBmu4j+rGLF0Dc/6w/wDJNPi8zlfTmSMk=",
|
|
292
|
+
"MindExecution.Plugins.YouTube.vy0nsbcoo4.dll": "sha256-p8w1TI9Ri41EAG/j0MJCiuEcJdr4x/Hr5Spd2oluZjM="
|
|
293
293
|
}
|
|
294
294
|
},
|
|
295
295
|
"cacheBootResources": true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
self.assetsManifest = {
|
|
2
|
-
"version": "
|
|
2
|
+
"version": "31UVI5Fx",
|
|
3
3
|
"assets": [
|
|
4
4
|
{
|
|
5
5
|
"hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
"hash": "sha256-
|
|
89
|
+
"hash": "sha256-vpLVZnaAN2JQkbnKVrjBOCGkbHU0uveNB4vqR1sMyHg=",
|
|
90
90
|
"url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
|
|
91
91
|
},
|
|
92
92
|
{
|
|
@@ -410,44 +410,44 @@
|
|
|
410
410
|
"url": "_framework/MimeMapping.og9ys58ylm.dll"
|
|
411
411
|
},
|
|
412
412
|
{
|
|
413
|
-
"hash": "sha256-
|
|
414
|
-
"url": "_framework/MindExecution.Core.
|
|
413
|
+
"hash": "sha256-5gla4JTR/zbvkGpneSt6S7ItE51+MjWgjW6L+B6Yevk=",
|
|
414
|
+
"url": "_framework/MindExecution.Core.asayb4hw1o.dll"
|
|
415
415
|
},
|
|
416
416
|
{
|
|
417
|
-
"hash": "sha256-
|
|
418
|
-
"url": "_framework/MindExecution.Kernel.
|
|
417
|
+
"hash": "sha256-78vR2SNkF9FwdQv0fEItSyz4HzgyzwGUROJAnpripPA=",
|
|
418
|
+
"url": "_framework/MindExecution.Kernel.3zom09ir6j.dll"
|
|
419
419
|
},
|
|
420
420
|
{
|
|
421
|
-
"hash": "sha256-
|
|
422
|
-
"url": "_framework/MindExecution.Plugins.Admin.
|
|
421
|
+
"hash": "sha256-uz7Nz0OZ8BBESp2Nr9YocyzV0dUrn7szPuD4/m/RWL8=",
|
|
422
|
+
"url": "_framework/MindExecution.Plugins.Admin.bk23zzy6wc.dll"
|
|
423
423
|
},
|
|
424
424
|
{
|
|
425
|
-
"hash": "sha256-
|
|
426
|
-
"url": "_framework/MindExecution.Plugins.Business.
|
|
425
|
+
"hash": "sha256-3YDsB2/uNI+feWqJk+cesLHgNt8s2/8hXxGPO6CRZZE=",
|
|
426
|
+
"url": "_framework/MindExecution.Plugins.Business.zwcvc8wufe.dll"
|
|
427
427
|
},
|
|
428
428
|
{
|
|
429
|
-
"hash": "sha256-
|
|
430
|
-
"url": "_framework/MindExecution.Plugins.Concept.
|
|
429
|
+
"hash": "sha256-WVyvjaxW6UoGQvGW0icChLsXwYVCcdn0BLTftzlkCKc=",
|
|
430
|
+
"url": "_framework/MindExecution.Plugins.Concept.lxunwcdjgq.dll"
|
|
431
431
|
},
|
|
432
432
|
{
|
|
433
|
-
"hash": "sha256-
|
|
434
|
-
"url": "_framework/MindExecution.Plugins.Directory.
|
|
433
|
+
"hash": "sha256-PSe7135JPpTBmu4j+rGLF0Dc/6w/wDJNPi8zlfTmSMk=",
|
|
434
|
+
"url": "_framework/MindExecution.Plugins.Directory.jc0g7fvyzv.dll"
|
|
435
435
|
},
|
|
436
436
|
{
|
|
437
|
-
"hash": "sha256-
|
|
438
|
-
"url": "_framework/MindExecution.Plugins.PlanMaster.
|
|
437
|
+
"hash": "sha256-Lp0AieRHDo2H4Yl0Il1pPruy9nFUHJsX9t1AblXGD6Y=",
|
|
438
|
+
"url": "_framework/MindExecution.Plugins.PlanMaster.2sugb66h95.dll"
|
|
439
439
|
},
|
|
440
440
|
{
|
|
441
|
-
"hash": "sha256-
|
|
442
|
-
"url": "_framework/MindExecution.Plugins.YouTube.
|
|
441
|
+
"hash": "sha256-p8w1TI9Ri41EAG/j0MJCiuEcJdr4x/Hr5Spd2oluZjM=",
|
|
442
|
+
"url": "_framework/MindExecution.Plugins.YouTube.vy0nsbcoo4.dll"
|
|
443
443
|
},
|
|
444
444
|
{
|
|
445
|
-
"hash": "sha256-
|
|
446
|
-
"url": "_framework/MindExecution.Shared.
|
|
445
|
+
"hash": "sha256-Au9QMwnm9MaP2+xzdldeQYtj4tRXzwOBWYOxmYR8spM=",
|
|
446
|
+
"url": "_framework/MindExecution.Shared.e8lipb57xv.dll"
|
|
447
447
|
},
|
|
448
448
|
{
|
|
449
|
-
"hash": "sha256-
|
|
450
|
-
"url": "_framework/MindExecution.Web.
|
|
449
|
+
"hash": "sha256-jQshtLmhEC4xenzdD9PqHjaCuKvJf/frzaKBi2weDAg=",
|
|
450
|
+
"url": "_framework/MindExecution.Web.994sb3nzuy.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-XSS0rv+S2JAW90BVDbFUd23REh87EAC1P5aFjXSMAGo=",
|
|
774
774
|
"url": "_framework/blazor.boot.json"
|
|
775
775
|
},
|
|
776
776
|
{
|
|
Binary file
|
|
Binary file
|