@mindexec/cli 0.2.395 → 0.2.397
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/remote-hub.js +17 -5
- package/scripts/remote-agent-managed-smoke.mjs +28 -4
- package/scripts/remote-hub-smoke.mjs +1 -1
- package/scripts/remote-registry-follower-smoke.mjs +33 -1
- package/server.js +331 -17
- package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +15 -3
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +24 -21
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +107 -12
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +32 -5
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +11 -5
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-overlay-v2.js +61 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +6 -1
- package/wwwroot/service-worker-assets.js +8 -8
- package/wwwroot/service-worker.js +1 -1
package/package.json
CHANGED
package/remote-hub.js
CHANGED
|
@@ -90,7 +90,8 @@ const REMOTE_FRAME_MODE_ALIASES = new Map([
|
|
|
90
90
|
const MAX_SYNTHETIC_DEVICES = 1000;
|
|
91
91
|
const DEFAULT_HOST_TARGET_LEASE_MS = 30000;
|
|
92
92
|
const DUPLICATE_DEVICE_ACTIVE_REJECT_MS = 15000;
|
|
93
|
-
const DUPLICATE_DEVICE_LOG_THROTTLE_MS =
|
|
93
|
+
const DUPLICATE_DEVICE_LOG_THROTTLE_MS = 60000;
|
|
94
|
+
const DUPLICATE_DEVICE_RETRY_AFTER_MS = 30000;
|
|
94
95
|
const SYNTHETIC_FRAME_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC';
|
|
95
96
|
const SYNTHETIC_FRAME_PAYLOAD = Buffer.from(SYNTHETIC_FRAME_DATA_URL.split(',')[1], 'base64');
|
|
96
97
|
const SYNTHETIC_FRAME_HASH = crypto.createHash('sha256').update(SYNTHETIC_FRAME_PAYLOAD).digest('hex').slice(0, 16);
|
|
@@ -1060,6 +1061,16 @@ export function createRemoteHub(options = {}) {
|
|
|
1060
1061
|
const pairToken = safeString(
|
|
1061
1062
|
options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(6).toString('hex'),
|
|
1062
1063
|
256);
|
|
1064
|
+
const duplicateDeviceLogThrottleMs = clampNumber(
|
|
1065
|
+
env.MINDEXEC_REMOTE_DUPLICATE_DEVICE_LOG_MS || env.REMOTE_HUB_DUPLICATE_DEVICE_LOG_MS,
|
|
1066
|
+
5000,
|
|
1067
|
+
10 * 60 * 1000,
|
|
1068
|
+
DUPLICATE_DEVICE_LOG_THROTTLE_MS);
|
|
1069
|
+
const duplicateDeviceRetryAfterMs = clampNumber(
|
|
1070
|
+
env.MINDEXEC_REMOTE_DUPLICATE_DEVICE_RETRY_MS || env.REMOTE_HUB_DUPLICATE_DEVICE_RETRY_MS,
|
|
1071
|
+
5000,
|
|
1072
|
+
10 * 60 * 1000,
|
|
1073
|
+
DUPLICATE_DEVICE_RETRY_AFTER_MS);
|
|
1063
1074
|
|
|
1064
1075
|
const devices = new Map();
|
|
1065
1076
|
const taskBatches = new Map();
|
|
@@ -1500,7 +1511,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1500
1511
|
}
|
|
1501
1512
|
|
|
1502
1513
|
function rejectDuplicateActiveDevice(socket, existing, attemptedSessionId) {
|
|
1503
|
-
const retryAfterMs = Math.max(
|
|
1514
|
+
const retryAfterMs = Math.max(heartbeatMs, duplicateDeviceRetryAfterMs);
|
|
1504
1515
|
writeJsonLine(socket, {
|
|
1505
1516
|
type: 'disconnect',
|
|
1506
1517
|
reason: 'duplicate-device-active',
|
|
@@ -1512,11 +1523,12 @@ export function createRemoteHub(options = {}) {
|
|
|
1512
1523
|
existing.counters.duplicateConnectionsRejected = (existing.counters.duplicateConnectionsRejected || 0) + 1;
|
|
1513
1524
|
const nowMs = Date.now();
|
|
1514
1525
|
const lastLoggedAt = duplicateDeviceLogAt.get(existing.deviceId) || 0;
|
|
1515
|
-
if (nowMs - lastLoggedAt >=
|
|
1526
|
+
if (nowMs - lastLoggedAt >= duplicateDeviceLogThrottleMs) {
|
|
1516
1527
|
duplicateDeviceLogAt.set(existing.deviceId, nowMs);
|
|
1517
|
-
|
|
1528
|
+
logEvent(
|
|
1518
1529
|
'remote',
|
|
1519
|
-
`duplicate device connection suppressed ${existing.deviceName} (${existing.deviceId}); keeping active session ${existing.sessionId}
|
|
1530
|
+
`duplicate device connection suppressed ${existing.deviceName} (${existing.deviceId}); keeping active session ${existing.sessionId}`,
|
|
1531
|
+
'remote');
|
|
1520
1532
|
}
|
|
1521
1533
|
|
|
1522
1534
|
try {
|
|
@@ -162,6 +162,7 @@ async function connectManagedAgent(clientBridge, managerEndpoint, staleEndpoint)
|
|
|
162
162
|
const result = await fetchJson(`${clientBridge.baseUrl}/api/remote/agent/connect`, {
|
|
163
163
|
method: 'POST',
|
|
164
164
|
body: JSON.stringify({
|
|
165
|
+
manager: managerEndpoint,
|
|
165
166
|
managerCandidates: [staleEndpoint, managerEndpoint],
|
|
166
167
|
pairToken: PAIR_TOKEN,
|
|
167
168
|
leaseId: LEASE_ID,
|
|
@@ -216,9 +217,9 @@ async function main() {
|
|
|
216
217
|
assert.equal(firstAgent.ready, true, JSON.stringify(firstAgent));
|
|
217
218
|
assert.equal(firstAgent.usingNpx, false, JSON.stringify(firstAgent));
|
|
218
219
|
assert.match(String(firstAgent.launcher || ''), /mindexec-remote-fast/i);
|
|
219
|
-
assert.
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
assert.equal(firstAgent.manager, managerEndpoint, JSON.stringify(firstAgent));
|
|
221
|
+
assert.equal(firstAgent.managerCandidates?.[0], managerEndpoint, JSON.stringify(firstAgent.managerCandidates));
|
|
222
|
+
assert.ok(firstConnectMs < 4500, `managed RemoteAgent selected manager should connect promptly, got ${firstConnectMs}ms`);
|
|
222
223
|
|
|
223
224
|
const connectedDevice = await waitFor(async () => {
|
|
224
225
|
const result = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices`);
|
|
@@ -248,7 +249,30 @@ async function main() {
|
|
|
248
249
|
assert.equal(secondAgent.usingNpx, false, JSON.stringify(secondAgent));
|
|
249
250
|
assert.match(String(secondAgent.launcher || ''), /mindexec-remote-fast/i);
|
|
250
251
|
assert.equal(secondAgent.managerCandidates?.[0], managerEndpoint, JSON.stringify(secondAgent.managerCandidates));
|
|
251
|
-
assert.equal(secondAgent.managerCandidates?.
|
|
252
|
+
assert.equal(secondAgent.managerCandidates?.length, 1, JSON.stringify(secondAgent.managerCandidates));
|
|
253
|
+
|
|
254
|
+
const duplicateReport = await fetchJson(`${clientBridge.baseUrl}/api/remote/agent/sync-report`, {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
ok: false,
|
|
258
|
+
attempted: true,
|
|
259
|
+
disconnected: true,
|
|
260
|
+
authenticated: true,
|
|
261
|
+
reason: 'connect-failed',
|
|
262
|
+
error: 'duplicate-device-active',
|
|
263
|
+
targetActive: true,
|
|
264
|
+
targetEndpoint: managerEndpoint,
|
|
265
|
+
targetEndpointCandidates: [managerEndpoint, staleEndpoint],
|
|
266
|
+
targetLeaseId: LEASE_ID,
|
|
267
|
+
targetNodeId: 'remote-agent-managed-smoke-node'
|
|
268
|
+
})
|
|
269
|
+
});
|
|
270
|
+
assert.equal(duplicateReport.ok, true, JSON.stringify(duplicateReport.payload));
|
|
271
|
+
assert.equal(duplicateReport.payload?.wake?.reason, 'duplicate-device-active', JSON.stringify(duplicateReport.payload));
|
|
272
|
+
|
|
273
|
+
const duplicateState = await fetchJson(`${clientBridge.baseUrl}/api/remote/agent/status`);
|
|
274
|
+
assert.equal(duplicateState.payload?.ready, true, JSON.stringify(duplicateState.payload));
|
|
275
|
+
assert.equal(duplicateState.payload?.needsReconnect, false, JSON.stringify(duplicateState.payload));
|
|
252
276
|
|
|
253
277
|
const staleReport = await fetchJson(`${clientBridge.baseUrl}/api/remote/agent/sync-report`, {
|
|
254
278
|
method: 'POST',
|
|
@@ -602,7 +602,7 @@ try {
|
|
|
602
602
|
), 1000);
|
|
603
603
|
assert.equal(duplicateDisconnect.reason, 'duplicate-device-active');
|
|
604
604
|
assert.equal(duplicateDisconnect.activeSessionId, oldSessionId);
|
|
605
|
-
assert.equal(duplicateDisconnect.retryAfterMs >=
|
|
605
|
+
assert.equal(duplicateDisconnect.retryAfterMs >= 30000, true);
|
|
606
606
|
await wait(100);
|
|
607
607
|
const duplicateRejectedDevice = hub.listDevices()[0];
|
|
608
608
|
assert.equal(duplicateRejectedDevice.sessionId, oldSessionId);
|
|
@@ -533,6 +533,37 @@ async function main() {
|
|
|
533
533
|
const renewStatus = await fetchJson(`${renewHost.baseUrl}/api/status`);
|
|
534
534
|
assert.equal(renewStatus.payload?.remoteHostTargetRenew?.status, 'active', JSON.stringify(renewStatus.payload?.remoteHostTargetRenew));
|
|
535
535
|
assert.equal(renewStatus.payload?.remoteHostTargetRenew?.endpoint, renewPublicEndpoint, JSON.stringify(renewStatus.payload?.remoteHostTargetRenew));
|
|
536
|
+
const currentLeaseId = currentTarget?.lease_id;
|
|
537
|
+
const currentHostInstanceId = currentTarget?.host_instance_id;
|
|
538
|
+
currentTarget = {
|
|
539
|
+
...currentTarget,
|
|
540
|
+
pair_token: 'stale-local-pair-token',
|
|
541
|
+
lease_id: 'stale-local-lease',
|
|
542
|
+
host_instance_id: 'stale-local-host-instance',
|
|
543
|
+
expires_at: new Date(Date.now() + 60_000).toISOString()
|
|
544
|
+
};
|
|
545
|
+
const staleLocalResume = await fetchJson(`${renewHost.baseUrl}/api/remote/system/resume`, {
|
|
546
|
+
method: 'POST',
|
|
547
|
+
token: BRIDGE_TOKEN,
|
|
548
|
+
body: JSON.stringify({
|
|
549
|
+
driftMs: 65000
|
|
550
|
+
})
|
|
551
|
+
});
|
|
552
|
+
assert.equal(staleLocalResume.ok, true, JSON.stringify(staleLocalResume.payload));
|
|
553
|
+
assert.equal(staleLocalResume.payload?.ok, true, JSON.stringify(staleLocalResume.payload));
|
|
554
|
+
await waitFor(() => {
|
|
555
|
+
return currentTarget?.pair_token === PAIR_TOKEN
|
|
556
|
+
&& currentTarget?.lease_id === currentLeaseId
|
|
557
|
+
&& currentTarget?.host_instance_id === currentHostInstanceId
|
|
558
|
+
? currentTarget
|
|
559
|
+
: null;
|
|
560
|
+
}, 8000, `stale local host target republish\n${renewHost.details()}`);
|
|
561
|
+
const republishedStatus = await fetchJson(`${renewHost.baseUrl}/api/status`);
|
|
562
|
+
assert.equal(republishedStatus.payload?.remoteHub?.hostTargetActive, true, JSON.stringify(republishedStatus.payload?.remoteHub));
|
|
563
|
+
assert.equal(republishedStatus.payload?.remoteHostTargetRenew?.status, 'active', JSON.stringify(republishedStatus.payload?.remoteHostTargetRenew));
|
|
564
|
+
assert.equal(republishedStatus.payload?.remoteHostTargetRenew?.reason, 'local-monitor-host-republished', JSON.stringify(republishedStatus.payload?.remoteHostTargetRenew));
|
|
565
|
+
const renewAgent = await fetchJson(`${renewHost.baseUrl}/api/remote/agent/status`);
|
|
566
|
+
assert.equal(renewAgent.payload?.running, false, JSON.stringify(renewAgent.payload));
|
|
536
567
|
|
|
537
568
|
const renewClear = await fetchJson(`${renewHost.baseUrl}/api/remote/host-target`, {
|
|
538
569
|
method: 'DELETE',
|
|
@@ -584,7 +615,8 @@ async function main() {
|
|
|
584
615
|
assert.equal(firstAgent.usingNpx, false, JSON.stringify(firstAgent));
|
|
585
616
|
assert.match(String(firstAgent.launcher || ''), /mindexec-remote-fast/i);
|
|
586
617
|
assert.equal(firstAgent.manager, hostAEndpoint);
|
|
587
|
-
assert.equal(firstAgent.managerCandidates?.[0],
|
|
618
|
+
assert.equal(firstAgent.managerCandidates?.[0], hostAEndpoint, JSON.stringify(firstAgent.managerCandidates));
|
|
619
|
+
assert.equal(firstAgent.managerCandidates?.length, 1, JSON.stringify(firstAgent.managerCandidates));
|
|
588
620
|
assert.ok(
|
|
589
621
|
fakeSupabase.requests.some(request =>
|
|
590
622
|
request.method === 'POST'
|