@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.395",
3
+ "version": "0.2.397",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
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 = 5000;
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(DUPLICATE_DEVICE_LOG_THROTTLE_MS, Math.min(30000, heartbeatMs));
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 >= DUPLICATE_DEVICE_LOG_THROTTLE_MS) {
1526
+ if (nowMs - lastLoggedAt >= duplicateDeviceLogThrottleMs) {
1516
1527
  duplicateDeviceLogAt.set(existing.deviceId, nowMs);
1517
- logWarn(
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.ok(
220
- firstConnectMs < 4500,
221
- `managed RemoteAgent must race slow endpoint candidates instead of waiting sequentially, got ${firstConnectMs}ms`);
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?.[1], staleEndpoint, JSON.stringify(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 >= 1000, true);
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], staleEndpoint, JSON.stringify(firstAgent.managerCandidates));
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'