@mindexec/cli 0.2.155 → 0.2.156

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.155",
3
+ "version": "0.2.156",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -21,15 +21,15 @@
21
21
  "scripts": {
22
22
  "start": "node launch-bridge.cjs",
23
23
  "dev": "node launch-bridge.cjs --watch",
24
- "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-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
24
+ "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-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
25
25
  "test:auth": "node scripts/auth-session-smoke.mjs",
26
26
  "test:remote": "node scripts/remote-hub-smoke.mjs",
27
27
  "test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
28
28
  "test:remote:render": "node scripts/remote-fleet-render-smoke.mjs",
29
- "test:remote:http": "node scripts/remote-http-smoke.mjs",
30
- "test:remote:frame-ws": "node scripts/remote-frame-ws-smoke.mjs",
31
- "test:remote:input-ws": "node scripts/remote-input-ws-smoke.mjs",
32
- "test:remote:agent-ws": "node scripts/remote-agent-ws-smoke.mjs",
29
+ "test:remote:http": "node scripts/remote-http-smoke.mjs",
30
+ "test:remote:frame-ws": "node scripts/remote-frame-ws-smoke.mjs",
31
+ "test:remote:input-ws": "node scripts/remote-input-ws-smoke.mjs",
32
+ "test:remote:agent-ws": "node scripts/remote-agent-ws-smoke.mjs",
33
33
  "test:remote:managed": "node scripts/remote-agent-managed-smoke.mjs",
34
34
  "test:remote:registry": "node scripts/remote-registry-follower-smoke.mjs",
35
35
  "test:remote:package": "node scripts/remote-agent-package-smoke.mjs",
@@ -66,6 +66,7 @@ function spawnBridge({ bridgePort, workspacePath, authDataRoot }) {
66
66
  WORKSPACE_PATH: workspacePath,
67
67
  MINDEXEC_AUTH_DATA_ROOT: authDataRoot,
68
68
  MINDEXEC_REMOTE_HUB: '0',
69
+ MINDEXEC_REMOTE_REGISTRY_FOLLOWER: '0',
69
70
  NO_COLOR: '1'
70
71
  }
71
72
  });
@@ -464,6 +464,25 @@ async function main() {
464
464
  && request.pathname === '/rest/v1/rpc/set_remote_host_target').length;
465
465
  return publishCount >= 2 ? publishCount : null;
466
466
  }, 8000, `host-target auto renew publish\n${renewHost.details()}`);
467
+ const beforeResumePublishCount = fakeSupabase.requests.filter(request =>
468
+ request.method === 'POST'
469
+ && request.pathname === '/rest/v1/rpc/set_remote_host_target').length;
470
+ const renewResume = await fetchJson(`${renewHost.baseUrl}/api/remote/system/resume`, {
471
+ method: 'POST',
472
+ token: BRIDGE_TOKEN,
473
+ body: JSON.stringify({
474
+ driftMs: 65000
475
+ })
476
+ });
477
+ assert.equal(renewResume.ok, true, JSON.stringify(renewResume.payload));
478
+ assert.equal(renewResume.payload?.ok, true, JSON.stringify(renewResume.payload));
479
+ assert.equal(renewResume.payload?.remoteSystemResume?.localHost, true, JSON.stringify(renewResume.payload?.remoteSystemResume));
480
+ await waitFor(() => {
481
+ const publishCount = fakeSupabase.requests.filter(request =>
482
+ request.method === 'POST'
483
+ && request.pathname === '/rest/v1/rpc/set_remote_host_target').length;
484
+ return publishCount > beforeResumePublishCount ? publishCount : null;
485
+ }, 8000, `host-target resume republish\n${renewHost.details()}`);
467
486
  const renewStatus = await fetchJson(`${renewHost.baseUrl}/api/status`);
468
487
  assert.equal(renewStatus.payload?.remoteHostTargetRenew?.status, 'active', JSON.stringify(renewStatus.payload?.remoteHostTargetRenew));
469
488
  assert.equal(renewStatus.payload?.remoteHostTargetRenew?.endpoint, renewPublicEndpoint, JSON.stringify(renewStatus.payload?.remoteHostTargetRenew));
@@ -523,6 +542,24 @@ async function main() {
523
542
  const firstStatus = await fetchJson(`${client.baseUrl}/api/status`);
524
543
  assert.equal(firstStatus.payload?.remoteRegistryRealtime?.subscribed, true, JSON.stringify(firstStatus.payload?.remoteRegistryRealtime));
525
544
  assert.ok(fakeSupabase.realtimeClients.size >= 1, 'expected LocalBridge realtime watcher to connect');
545
+ const firstPid = Number(firstAgent.pid || 0);
546
+ assert.ok(firstPid > 0, JSON.stringify(firstAgent));
547
+
548
+ const clientResume = await fetchJson(`${client.baseUrl}/api/remote/system/resume`, {
549
+ method: 'POST',
550
+ token: BRIDGE_TOKEN,
551
+ body: JSON.stringify({
552
+ driftMs: 65000
553
+ })
554
+ });
555
+ assert.equal(clientResume.ok, true, JSON.stringify(clientResume.payload));
556
+ assert.equal(clientResume.payload?.ok, true, JSON.stringify(clientResume.payload));
557
+ assert.equal(clientResume.payload?.remoteSystemResume?.registryAgentRestarted, true, JSON.stringify(clientResume.payload?.remoteSystemResume));
558
+ const resumedAgent = await waitForManagedAgentRestart(client, hostAEndpoint, firstPid, 'host-a after resume', 12000);
559
+ assert.equal(resumedAgent.usingNpx, false, JSON.stringify(resumedAgent));
560
+ assert.match(String(resumedAgent.launcher || ''), /mindexec-remote-fast/i);
561
+ assert.equal(resumedAgent.leaseId, 'lease-a');
562
+ await waitForConnectedDevice(hostA, 'host-a after resume');
526
563
 
527
564
  currentTarget = createRegistryTarget({
528
565
  endpoint: hostBEndpoint,
package/server.js CHANGED
@@ -3374,6 +3374,10 @@ const REMOTE_HOST_TARGET_RENEW_MS = Math.max(
3374
3374
  Math.floor(REMOTE_HOST_TARGET_LEASE_MS / 2),
3375
3375
  Number(process.env.MINDEXEC_REMOTE_HOST_TARGET_RENEW_MS || 25000) || 25000));
3376
3376
  const REMOTE_HOST_TARGET_RENEW_LOG_REPEAT_MS = 60000;
3377
+ const REMOTE_SYSTEM_RESUME_CHECK_MS = Math.max(1000, Number(process.env.MINDEXEC_REMOTE_RESUME_CHECK_MS || 5000) || 5000);
3378
+ const REMOTE_SYSTEM_RESUME_DRIFT_MS = Math.max(
3379
+ REMOTE_SYSTEM_RESUME_CHECK_MS + 5000,
3380
+ Number(process.env.MINDEXEC_REMOTE_RESUME_DRIFT_MS || 20000) || 20000);
3377
3381
  let remoteAgentState = createRemoteAgentIdleState();
3378
3382
  let remoteAgentSyncReportState = null;
3379
3383
  let remoteAgentSyncReportLogKey = '';
@@ -3396,10 +3400,27 @@ let remoteRegistryRealtimeHeartbeatTimer = null;
3396
3400
  let remoteRegistryRealtimeReconnectTimer = null;
3397
3401
  let remoteRegistryRealtimeReconnectContext = null;
3398
3402
  let remoteRegistryRealtimeLastWakeAt = 0;
3403
+ let remoteSupabaseRuntimeConfigCache = null;
3399
3404
  let remoteHostTargetRenewTimer = null;
3400
3405
  let remoteHostTargetRenewInFlight = false;
3401
3406
  let remoteHostTargetRenewLogKey = '';
3402
3407
  let remoteHostTargetRenewLogAt = 0;
3408
+ let remoteSystemResumeTimer = null;
3409
+ let remoteSystemResumeExpectedAt = 0;
3410
+ let remoteSystemResumeInFlight = false;
3411
+ let remoteSystemResumeState = {
3412
+ enabled: true,
3413
+ status: 'idle',
3414
+ reason: '',
3415
+ trigger: '',
3416
+ lastDetectedAt: '',
3417
+ lastCompletedAt: '',
3418
+ lastError: '',
3419
+ driftMs: 0,
3420
+ localHost: false,
3421
+ registryAgentRestarted: false,
3422
+ disconnectedDevices: 0
3423
+ };
3403
3424
  let remoteRegistryFollowerState = {
3404
3425
  enabled: REMOTE_REGISTRY_FOLLOWER_ENABLED,
3405
3426
  status: REMOTE_REGISTRY_FOLLOWER_ENABLED ? 'idle' : 'disabled',
@@ -4762,6 +4783,23 @@ function updateRemoteHostTargetRenewState(patch = {}) {
4762
4783
  emitBridgeEvent('RemoteHostTargetRenewUpdated', serializeRemoteHostTargetRenewState());
4763
4784
  }
4764
4785
 
4786
+ function serializeRemoteSystemResumeState() {
4787
+ return {
4788
+ ...remoteSystemResumeState,
4789
+ checkMs: REMOTE_SYSTEM_RESUME_CHECK_MS,
4790
+ driftThresholdMs: REMOTE_SYSTEM_RESUME_DRIFT_MS
4791
+ };
4792
+ }
4793
+
4794
+ function updateRemoteSystemResumeState(patch = {}) {
4795
+ remoteSystemResumeState = {
4796
+ ...remoteSystemResumeState,
4797
+ ...patch,
4798
+ enabled: true
4799
+ };
4800
+ emitBridgeEvent('RemoteSystemResumeUpdated', serializeRemoteSystemResumeState());
4801
+ }
4802
+
4765
4803
  function serializeRemoteRegistryRealtimeState() {
4766
4804
  return {
4767
4805
  ...remoteRegistryRealtimeState,
@@ -5189,12 +5227,19 @@ function readSupabaseRuntimeConfig() {
5189
5227
  || process.env.MINDEXEC_SUPABASE_KEY
5190
5228
  || '').trim();
5191
5229
  if (envUrl && envKey) {
5192
- return { url: envUrl.replace(/\/+$/, ''), key: envKey };
5230
+ remoteSupabaseRuntimeConfigCache = {
5231
+ url: envUrl.replace(/\/+$/, ''),
5232
+ key: envKey,
5233
+ source: 'env'
5234
+ };
5235
+ return { ...remoteSupabaseRuntimeConfigCache };
5193
5236
  }
5194
5237
 
5195
5238
  const candidates = [
5196
5239
  path.join(WEB_APP_ROOT || '', 'appsettings.json'),
5197
5240
  path.join(DEFAULT_WEB_APP_ROOT, 'appsettings.json'),
5241
+ path.join(BRIDGE_ROOT, 'wwwroot', 'appsettings.json'),
5242
+ path.join(process.cwd(), 'wwwroot', 'appsettings.json'),
5198
5243
  path.resolve(BRIDGE_ROOT, '..', 'MindExecution.Web', 'wwwroot', 'appsettings.json')
5199
5244
  ].filter(Boolean);
5200
5245
 
@@ -5204,13 +5249,25 @@ function readSupabaseRuntimeConfig() {
5204
5249
  const url = String(payload?.Supabase?.Url || '').trim().replace(/\/+$/, '');
5205
5250
  const key = String(payload?.Supabase?.Key || payload?.Supabase?.AnonKey || payload?.Supabase?.PublishableKey || '').trim();
5206
5251
  if (url && key) {
5207
- return { url, key };
5252
+ remoteSupabaseRuntimeConfigCache = {
5253
+ url,
5254
+ key,
5255
+ source: candidate
5256
+ };
5257
+ return { ...remoteSupabaseRuntimeConfigCache };
5208
5258
  }
5209
5259
  } catch {
5210
5260
  // Try the next appsettings candidate.
5211
5261
  }
5212
5262
  }
5213
5263
 
5264
+ if (remoteSupabaseRuntimeConfigCache?.url && remoteSupabaseRuntimeConfigCache?.key) {
5265
+ return {
5266
+ ...remoteSupabaseRuntimeConfigCache,
5267
+ cached: true
5268
+ };
5269
+ }
5270
+
5214
5271
  return { url: '', key: '' };
5215
5272
  }
5216
5273
 
@@ -5603,6 +5660,145 @@ function stopRemoteHostTargetRenew(reason = 'stopped') {
5603
5660
  });
5604
5661
  }
5605
5662
 
5663
+ function disconnectRemoteHubDevicesForResume(reason = 'system-resume') {
5664
+ const devices = remoteHub.listDevices();
5665
+ let disconnected = 0;
5666
+ for (const device of devices) {
5667
+ if (device?.connected !== true || !device?.deviceId) {
5668
+ continue;
5669
+ }
5670
+
5671
+ if (remoteHub.disconnectDevice(device.deviceId, reason)) {
5672
+ disconnected += 1;
5673
+ }
5674
+ }
5675
+
5676
+ return disconnected;
5677
+ }
5678
+
5679
+ async function reconcileRemoteAfterSystemResume(trigger = 'timer-drift', driftMs = 0) {
5680
+ if (remoteSystemResumeInFlight || isShuttingDown) {
5681
+ return;
5682
+ }
5683
+
5684
+ remoteSystemResumeInFlight = true;
5685
+ try {
5686
+ const localHub = remoteHub.getStatus({ includeSecrets: true });
5687
+ const isLocalHost = localHub?.hostTargetActive === true && !!localHub.hostTargetNodeId;
5688
+ const wasRegistryAgentRunning = isRemoteAgentRegistryOwned();
5689
+ const disconnectedDevices = isLocalHost
5690
+ ? disconnectRemoteHubDevicesForResume('system-resume')
5691
+ : 0;
5692
+
5693
+ updateRemoteSystemResumeState({
5694
+ status: 'detected',
5695
+ reason: 'system-resume',
5696
+ trigger,
5697
+ lastDetectedAt: new Date().toISOString(),
5698
+ lastError: '',
5699
+ driftMs: Math.round(Number(driftMs) || 0),
5700
+ localHost: isLocalHost,
5701
+ registryAgentRestarted: wasRegistryAgentRunning,
5702
+ disconnectedDevices
5703
+ });
5704
+
5705
+ logEvent(
5706
+ 'remote',
5707
+ `system resume detected ${formatKeyValue('trigger', trigger)} ${formatKeyValue('driftMs', Math.round(Number(driftMs) || 0))} ${formatKeyValue('host', isLocalHost ? 'yes' : 'no')} ${formatKeyValue('agent', wasRegistryAgentRunning ? 'registry' : '-')}`,
5708
+ 'remote');
5709
+
5710
+ closeRemoteRegistryRealtime('system-resume');
5711
+
5712
+ if (wasRegistryAgentRunning) {
5713
+ await stopRemoteAgentConnection('system-resume-reconnect');
5714
+ }
5715
+
5716
+ if (isLocalHost) {
5717
+ updateRemoteHostTargetRenewState({
5718
+ status: 'waking',
5719
+ reason: 'system-resume',
5720
+ nodeId: localHub.hostTargetNodeId,
5721
+ leaseId: localHub.hostTargetLeaseId,
5722
+ hostInstanceId: localHub.hostTargetHostInstanceId || localHub.hostInstanceId,
5723
+ endpoint: localHub.hostTargetEndpoint,
5724
+ endpointCandidates: localHub.hostTargetEndpointCandidates || [],
5725
+ expiresAt: localHub.hostTargetExpiresAt,
5726
+ lastError: ''
5727
+ });
5728
+ await runRemoteHostTargetRenewOnce('system-resume', { takeover: false });
5729
+ }
5730
+
5731
+ updateRemoteRegistryFollowerState({
5732
+ status: 'waking',
5733
+ reason: 'system-resume',
5734
+ lastError: '',
5735
+ lastAttemptAt: new Date().toISOString()
5736
+ });
5737
+ await wakeRemoteRegistryFollower('system-resume');
5738
+
5739
+ updateRemoteSystemResumeState({
5740
+ status: 'reconciled',
5741
+ reason: 'system-resume',
5742
+ lastCompletedAt: new Date().toISOString(),
5743
+ lastError: ''
5744
+ });
5745
+
5746
+ emitBridgeEvent('RemoteSystemResumeReconciled', {
5747
+ trigger,
5748
+ driftMs: Math.round(Number(driftMs) || 0),
5749
+ localHost: isLocalHost,
5750
+ registryAgentRestarted: wasRegistryAgentRunning,
5751
+ disconnectedDevices
5752
+ });
5753
+ } catch (error) {
5754
+ logWarn('remote', `system resume reconcile failed: ${error?.message || error}`);
5755
+ updateRemoteSystemResumeState({
5756
+ status: 'error',
5757
+ reason: 'system-resume-failed',
5758
+ lastError: error?.message || String(error || ''),
5759
+ lastCompletedAt: new Date().toISOString()
5760
+ });
5761
+ updateRemoteRegistryFollowerState({
5762
+ status: 'error',
5763
+ reason: 'system-resume-failed',
5764
+ lastError: error?.message || String(error || ''),
5765
+ lastAttemptAt: new Date().toISOString()
5766
+ });
5767
+ scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS, 'system-resume-error');
5768
+ } finally {
5769
+ remoteSystemResumeInFlight = false;
5770
+ }
5771
+ }
5772
+
5773
+ function startRemoteSystemResumeMonitor() {
5774
+ if (remoteSystemResumeTimer || isShuttingDown) {
5775
+ return;
5776
+ }
5777
+
5778
+ remoteSystemResumeExpectedAt = Date.now() + REMOTE_SYSTEM_RESUME_CHECK_MS;
5779
+ remoteSystemResumeTimer = setInterval(() => {
5780
+ const now = Date.now();
5781
+ const driftMs = now - remoteSystemResumeExpectedAt;
5782
+ remoteSystemResumeExpectedAt = now + REMOTE_SYSTEM_RESUME_CHECK_MS;
5783
+ if (driftMs < REMOTE_SYSTEM_RESUME_DRIFT_MS) {
5784
+ return;
5785
+ }
5786
+
5787
+ reconcileRemoteAfterSystemResume('timer-drift', driftMs).catch(error => {
5788
+ logWarn('remote', `system resume handler failed: ${error?.message || error}`);
5789
+ });
5790
+ }, REMOTE_SYSTEM_RESUME_CHECK_MS);
5791
+ remoteSystemResumeTimer?.unref?.();
5792
+ }
5793
+
5794
+ function stopRemoteSystemResumeMonitor() {
5795
+ if (remoteSystemResumeTimer) {
5796
+ clearInterval(remoteSystemResumeTimer);
5797
+ remoteSystemResumeTimer = null;
5798
+ }
5799
+ remoteSystemResumeExpectedAt = 0;
5800
+ }
5801
+
5606
5802
  async function runRemoteHostTargetRenewOnce(trigger = 'timer', options = {}) {
5607
5803
  if (!REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED || remoteHostTargetRenewInFlight) {
5608
5804
  return serializeRemoteHostTargetRenewState();
@@ -10509,6 +10705,7 @@ app.get('/api/status', async (req, res) => {
10509
10705
  remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
10510
10706
  remoteRegistryRealtime: serializeRemoteRegistryRealtimeState(),
10511
10707
  remoteHostTargetRenew: serializeRemoteHostTargetRenewState(),
10708
+ remoteSystemResume: serializeRemoteSystemResumeState(),
10512
10709
  shellJobsPath: '/api/shell/jobs',
10513
10710
  companyCore: {
10514
10711
  baseUrl: companyCoreBaseUrl,
@@ -10551,6 +10748,26 @@ app.get('/api/remote/status', (req, res) => {
10551
10748
  res.json(remoteHub.getStatus({ includeSecrets: true }));
10552
10749
  });
10553
10750
 
10751
+ app.post('/api/remote/system/resume', async (req, res) => {
10752
+ res.setHeader('Cache-Control', 'no-store');
10753
+ try {
10754
+ await reconcileRemoteAfterSystemResume('manual-api', Number(req.body?.driftMs || 0));
10755
+ res.json({
10756
+ ok: true,
10757
+ remoteSystemResume: serializeRemoteSystemResumeState(),
10758
+ remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
10759
+ remoteHostTargetRenew: serializeRemoteHostTargetRenewState(),
10760
+ remoteAgent: serializeRemoteAgentState()
10761
+ });
10762
+ } catch (err) {
10763
+ res.status(500).json({
10764
+ ok: false,
10765
+ error: err?.message || String(err),
10766
+ remoteSystemResume: serializeRemoteSystemResumeState()
10767
+ });
10768
+ }
10769
+ });
10770
+
10554
10771
  app.get('/api/remote/devices', (req, res) => {
10555
10772
  res.setHeader('Cache-Control', 'no-store');
10556
10773
  const includeDataUrl = /^(1|true|yes|on|data|base64)$/i.test(String(req.query?.includeDataUrl ?? req.query?.frameData ?? ''));
@@ -12313,8 +12530,9 @@ async function startBridgeServer() {
12313
12530
  : `${tone('app', 'muted')} ${tone('not packaged', 'warn')}`,
12314
12531
  `${tone('listen', 'muted')} ${tone(`http://127.0.0.1:${PORT}`, 'path')}`,
12315
12532
  `${tone('stop', 'muted')} Ctrl+C`
12316
- ]);
12533
+ ]);
12317
12534
  startRemoteRegistryFollower();
12535
+ startRemoteSystemResumeMonitor();
12318
12536
  });
12319
12537
  }
12320
12538
 
@@ -12382,6 +12600,12 @@ async function shutdownBridge(signal) {
12382
12600
  // Ignore registry follower shutdown errors
12383
12601
  }
12384
12602
 
12603
+ try {
12604
+ stopRemoteSystemResumeMonitor();
12605
+ } catch {
12606
+ // Ignore system resume monitor shutdown errors
12607
+ }
12608
+
12385
12609
  try {
12386
12610
  stopRemoteHostTargetRenew('bridge-shutdown');
12387
12611
  } catch {