@mindexec/cli 0.2.98 → 0.2.99

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.98",
3
+ "version": "0.2.99",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -701,7 +701,7 @@ try {
701
701
  manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
702
702
 
703
703
  let cards = bodyView.querySelectorAll('article[data-device-id]');
704
- assert.equal(cards.length, SYNTHETIC_COUNT);
704
+ assert.equal(cards.length, connectedCount);
705
705
  assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]'), null);
706
706
  assert.equal(bodyView.querySelector('[data-remote-fleet-search="true"]'), null);
707
707
  assert.equal(bodyView.querySelectorAll('select').length, 0);
@@ -745,7 +745,10 @@ try {
745
745
  assert.equal(bodyView.dataset.remoteFleetDensity, 'cards');
746
746
  assert.match(bodyView.querySelector('[data-remote-fleet-device-grid="true"]')?.style.cssText || '', /minmax\(132px,\s*1fr\)/);
747
747
  cards = bodyView.querySelectorAll('article[data-device-id]');
748
- const patchTarget = devices.find(device => device.ThumbnailEnabled === true && device.LiveStreamActive !== true);
748
+ const patchTarget = devices.find(device =>
749
+ device.Connected === true
750
+ && device.ThumbnailEnabled === true
751
+ && device.LiveStreamActive !== true);
749
752
  assert.ok(patchTarget);
750
753
  const patchCard = Array.from(cards).find(card => card.dataset.deviceId === patchTarget.DeviceId);
751
754
  const patchPreview = patchCard?.querySelector('[data-remote-fleet-device-preview="tile"]');
@@ -765,7 +768,9 @@ try {
765
768
  assert.equal(patchPreview.dataset.remoteFleetPendingFrameUrl || '', '');
766
769
  assert.ok(patchPreview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]'));
767
770
  assert.equal(patchPreview.querySelector('img[data-remote-fleet-frame-image="true"]')?.dataset.remoteFleetFrameUrl, patchUrl);
768
- const alternateDevice = devices.find(device => device.DeviceId !== focusedDevice.DeviceId);
771
+ const alternateDevice = devices.find(device =>
772
+ device.Connected === true
773
+ && device.DeviceId !== focusedDevice.DeviceId);
769
774
  assert.ok(alternateDevice);
770
775
  const alternateCard = Array.from(cards).find(card => card.dataset.deviceId === alternateDevice.DeviceId);
771
776
  assert.ok(alternateCard);
@@ -777,7 +782,7 @@ try {
777
782
  assert.equal(selectedAfterClick?.getAttribute('aria-pressed'), 'true');
778
783
  assert.ok(selectedAfterClick?.querySelector('[data-remote-fleet-selected-indicator="true"]'));
779
784
  const livePanel = bodyView.querySelector('[data-remote-fleet-live-panel="true"]');
780
- assert.equal(livePanel?.dataset.deviceId, focusedDevice.DeviceId);
785
+ assert.equal(livePanel, null);
781
786
  assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-visible"]'), null);
782
787
  assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-connected"]'), null);
783
788
  assert.equal(bodyView.querySelector('[data-remote-fleet-task-input="true"]'), null);
@@ -811,7 +816,7 @@ try {
811
816
  enabled: false
812
817
  });
813
818
 
814
- assert.ok(livePanel?.textContent.includes('RemoteFast 20 fps'), livePanel?.textContent || bodyView.textContent);
819
+ assert.equal(bodyView.querySelector('[data-remote-fleet-live-panel="true"]'), null);
815
820
  assert.equal(bodyView.querySelector('code'), null);
816
821
  assert.equal(bodyView.textContent.includes('npx @mindexec/remote connect'), false);
817
822
  assert.equal(bodyView.querySelector('[data-remote-fleet-action="copy-command"]'), null);
package/server.js CHANGED
@@ -2978,6 +2978,12 @@ function isRemoteAgentProcessRunning() {
2978
2978
  && !proc.killed;
2979
2979
  }
2980
2980
 
2981
+ function isLocalRemoteHostTargetActive() {
2982
+ const status = remoteHub.getStatus({ includeSecrets: false });
2983
+ return status?.hostTargetActive === true
2984
+ && (!status.hostTargetHostInstanceId || status.hostTargetHostInstanceId === BRIDGE_INSTANCE_ID);
2985
+ }
2986
+
2981
2987
  function resolveRemoteAgentLauncher() {
2982
2988
  const localLauncher = path.join(BRIDGE_ROOT, 'node_modules', '@mindexec', 'remote', 'bin', 'mindexec-remote.js');
2983
2989
  try {
@@ -3112,6 +3118,22 @@ async function startRemoteAgentConnection(options = {}) {
3112
3118
  return { ok: false, error: 'missing-pair-token', agent: serializeRemoteAgentState() };
3113
3119
  }
3114
3120
 
3121
+ if (isLocalRemoteHostTargetActive()) {
3122
+ if (isRemoteAgentProcessRunning()) {
3123
+ await stopRemoteAgentConnection('local-host-target-active');
3124
+ }
3125
+ logEvent(
3126
+ 'remote',
3127
+ 'managed RemoteAgent connect skipped because this bridge is the active host target',
3128
+ 'remote');
3129
+ return {
3130
+ ok: true,
3131
+ skipped: true,
3132
+ reason: 'local-host-target-active',
3133
+ agent: serializeRemoteAgentState()
3134
+ };
3135
+ }
3136
+
3115
3137
  const activeConnectionKeys = new Set(managers.map(manager => createRemoteAgentConnectionKey(manager, leaseId)));
3116
3138
  if (isRemoteAgentProcessRunning()
3117
3139
  && remoteAgentState.ready === true
@@ -7831,13 +7853,33 @@ app.post('/api/remote/frames', (req, res) => {
7831
7853
  });
7832
7854
  });
7833
7855
 
7834
- app.post('/api/remote/host-target', (req, res) => {
7856
+ app.post('/api/remote/host-target', async (req, res) => {
7835
7857
  res.setHeader('Cache-Control', 'no-store');
7836
- res.json(remoteHub.setHostTarget({
7837
- enabled: req.body?.enabled !== false,
7838
- nodeId: req.body?.nodeId,
7839
- leaseMs: req.body?.leaseMs
7840
- }));
7858
+ try {
7859
+ const result = remoteHub.setHostTarget({
7860
+ enabled: req.body?.enabled !== false,
7861
+ nodeId: req.body?.nodeId,
7862
+ leaseMs: req.body?.leaseMs
7863
+ });
7864
+ if (result?.ok === true && result?.active === true && isLocalRemoteHostTargetActive()) {
7865
+ if (isRemoteAgentProcessRunning()) {
7866
+ await stopRemoteAgentConnection('local-host-target-active');
7867
+ logEvent('remote', 'managed RemoteAgent held stopped while local host target is active', 'remote');
7868
+ }
7869
+ }
7870
+ res.json({
7871
+ ...result,
7872
+ agent: serializeRemoteAgentState()
7873
+ });
7874
+ } catch (err) {
7875
+ logError('remote', 'remote host target update failed.', err);
7876
+ res.status(500).json({
7877
+ ok: false,
7878
+ active: false,
7879
+ error: err?.message || String(err),
7880
+ agent: serializeRemoteAgentState()
7881
+ });
7882
+ }
7841
7883
  });
7842
7884
 
7843
7885
  app.delete('/api/remote/host-target', (req, res) => {
@@ -12974,6 +12974,7 @@
12974
12974
  }
12975
12975
 
12976
12976
  const REMOTE_FLEET_MONITOR_REFRESH_MS = 10000;
12977
+ const REMOTE_FLEET_EMPTY_MONITOR_REFRESH_MS = 2500;
12977
12978
  const REMOTE_FLEET_LIVE_REFRESH_MS = 30000;
12978
12979
  const REMOTE_FLEET_FOCUSED_LIVE_FPS = 10;
12979
12980
  const REMOTE_FLEET_LIVE_FRAME_REFRESH_MS = Math.round(1000 / REMOTE_FLEET_FOCUSED_LIVE_FPS);
@@ -12981,6 +12982,8 @@
12981
12982
  const REMOTE_FLEET_FRAME_CANVAS_MAX_DPR = 2;
12982
12983
  const REMOTE_FLEET_FRAME_BLOB_CACHE_MS = 10000;
12983
12984
  const REMOTE_FLEET_FRAME_BLOB_CACHE_LIMIT = 96;
12985
+ const REMOTE_FLEET_LIVE_FRAME_DECODE_TIMEOUT_MS = 180;
12986
+ const REMOTE_FLEET_THUMBNAIL_FRAME_DECODE_TIMEOUT_MS = 1200;
12984
12987
  const REMOTE_FLEET_TASK_FOLLOW_INITIAL_MS = 250;
12985
12988
  const REMOTE_FLEET_TASK_FOLLOW_REFRESH_MS = 2000;
12986
12989
  const REMOTE_FLEET_TASK_FOLLOW_MAX_TICKS = 60;
@@ -13299,6 +13302,10 @@
13299
13302
  return patches;
13300
13303
  }
13301
13304
 
13305
+ function shouldDisplayRemoteFleetMonitorDevice(device) {
13306
+ return isRemoteFleetDeviceConnected(device);
13307
+ }
13308
+
13302
13309
  function ensureRemoteFleetFrameImage(preview, frame) {
13303
13310
  let image = preview?.querySelector?.('img[data-remote-fleet-frame-image="true"]');
13304
13311
  if (image) {
@@ -13439,6 +13446,42 @@
13439
13446
  return true;
13440
13447
  }
13441
13448
 
13449
+ function getRemoteFleetFrameDecodeTimeoutMs(frame) {
13450
+ return String(frame?.kind || '').toLowerCase() === 'live'
13451
+ ? REMOTE_FLEET_LIVE_FRAME_DECODE_TIMEOUT_MS
13452
+ : REMOTE_FLEET_THUMBNAIL_FRAME_DECODE_TIMEOUT_MS;
13453
+ }
13454
+
13455
+ function withRemoteFleetFrameTimeout(promise, timeoutMs, onTimeout) {
13456
+ const duration = Math.max(50, Number(timeoutMs) || REMOTE_FLEET_LIVE_FRAME_DECODE_TIMEOUT_MS);
13457
+ return new Promise((resolve, reject) => {
13458
+ let settled = false;
13459
+ const timer = setTimeout(() => {
13460
+ if (settled) return;
13461
+ settled = true;
13462
+ try {
13463
+ onTimeout?.();
13464
+ } catch {
13465
+ // best effort only
13466
+ }
13467
+ reject(new Error('remote-frame-decode-timeout'));
13468
+ }, duration);
13469
+ Promise.resolve(promise)
13470
+ .then(value => {
13471
+ if (settled) return;
13472
+ settled = true;
13473
+ clearTimeout(timer);
13474
+ resolve(value);
13475
+ })
13476
+ .catch(error => {
13477
+ if (settled) return;
13478
+ settled = true;
13479
+ clearTimeout(timer);
13480
+ reject(error);
13481
+ });
13482
+ });
13483
+ }
13484
+
13442
13485
  async function loadRemoteFleetFrameBitmap(frame) {
13443
13486
  if (!frame || !isRemoteFleetFrameSource(frame.frameUrl)) {
13444
13487
  return null;
@@ -13482,8 +13525,12 @@
13482
13525
  entry.expiresAt = now + REMOTE_FLEET_FRAME_BLOB_CACHE_MS;
13483
13526
  }
13484
13527
 
13485
- const blob = await entry.promise;
13486
- return createImageBitmap(blob);
13528
+ const timeoutMs = getRemoteFleetFrameDecodeTimeoutMs(frame);
13529
+ const blob = await withRemoteFleetFrameTimeout(
13530
+ entry.promise,
13531
+ timeoutMs,
13532
+ () => remoteFleetFrameBlobCache.delete(identity));
13533
+ return await withRemoteFleetFrameTimeout(createImageBitmap(blob), timeoutMs);
13487
13534
  }
13488
13535
 
13489
13536
  function getRemoteFleetFrameIdentity(frame) {
@@ -13718,15 +13765,24 @@
13718
13765
  return;
13719
13766
  }
13720
13767
 
13768
+ const timeoutMs = getRemoteFleetFrameDecodeTimeoutMs(frame);
13769
+ let settled = false;
13770
+ const complete = (loaded, surface = 'img') => {
13771
+ if (settled) return;
13772
+ settled = true;
13773
+ clearTimeout(timeout);
13774
+ finish(loaded, surface);
13775
+ };
13776
+ const timeout = setTimeout(() => complete(false, 'img-timeout'), timeoutMs);
13721
13777
  const loader = new Image();
13722
13778
  loader.decoding = 'async';
13723
13779
  loader.onload = () => {
13724
13780
  const decoded = typeof loader.decode === 'function'
13725
13781
  ? loader.decode().catch(() => undefined)
13726
13782
  : Promise.resolve();
13727
- decoded.then(() => finish(true, 'img'));
13783
+ decoded.then(() => complete(true, 'img'));
13728
13784
  };
13729
- loader.onerror = () => finish(false, 'img');
13785
+ loader.onerror = () => complete(false, 'img');
13730
13786
  loader.src = frame.frameUrl;
13731
13787
  };
13732
13788
 
@@ -13782,7 +13838,19 @@
13782
13838
  }
13783
13839
  });
13784
13840
  })
13785
- .catch(() => loadFallbackImage());
13841
+ .catch(error => {
13842
+ if (frame.kind === 'live') {
13843
+ window.RuntimeTrace?.emit?.('remote.frame.decodeSkipped', {
13844
+ kind: frame.kind,
13845
+ seq: frame.frameSeq,
13846
+ error: error?.message || String(error || '')
13847
+ });
13848
+ finish(false, 'canvas-timeout');
13849
+ return;
13850
+ }
13851
+
13852
+ loadFallbackImage();
13853
+ });
13786
13854
  }
13787
13855
 
13788
13856
  function queueRemoteFleetFramePaint(preview, frame) {
@@ -15074,6 +15142,7 @@
15074
15142
  }
15075
15143
  bodyView.dataset.remoteFleetTaskFollowActive = latestTaskBatchActive ? 'true' : 'false';
15076
15144
  const sortedDevices = [...parseRemoteFleetDevices(nodeModel)]
15145
+ .filter(shouldDisplayRemoteFleetMonitorDevice)
15077
15146
  .sort((left, right) => compareRemoteFleetDevices(left, right, sortState));
15078
15147
  const devices = groupState === 'none'
15079
15148
  ? sortedDevices
@@ -15099,11 +15168,8 @@
15099
15168
  } else {
15100
15169
  delete bodyView.dataset.remoteFleetSelectedDeviceId;
15101
15170
  }
15102
- const total = Number(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetDeviceCount', devices.length));
15103
- const connected = Number(getRemoteFleetMetadataValue(
15104
- nodeModel,
15105
- 'RemoteFleetConnectedDeviceCount',
15106
- devices.filter(isRemoteFleetDeviceConnected).length));
15171
+ const total = devices.length;
15172
+ const connected = devices.filter(isRemoteFleetDeviceConnected).length;
15107
15173
  const taskCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceTaskCapable(device)).length;
15108
15174
  const aiCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceAiCapable(device)).length;
15109
15175
  const hubStatus = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubStatus', 'offline');
@@ -15542,150 +15608,6 @@
15542
15608
  bodyView.appendChild(resultPanel);
15543
15609
  }
15544
15610
 
15545
- if (focusedDevice) {
15546
- const focusedId = getRemoteFleetDeviceId(focusedDevice);
15547
- const focusedName = getRemoteFleetDeviceName(focusedDevice);
15548
- const liveActive = isRemoteFleetLiveActive(focusedDevice);
15549
- const liveFrameSource = getRemoteFleetFrameSource(focusedDevice, 'live');
15550
- const liveFrameAt = String(getRemoteFleetDeviceField(focusedDevice, 'liveFrameReceivedAt', 'LiveFrameReceivedAt', ''));
15551
- const liveStreamId = String(getRemoteFleetDeviceField(focusedDevice, 'liveStreamId', 'LiveStreamId', ''));
15552
- const hasLiveFrame = hasRemoteFleetLiveFrame(focusedDevice);
15553
- const livePanel = document.createElement('section');
15554
- livePanel.dataset.remoteFleetLivePanel = 'true';
15555
- livePanel.dataset.deviceId = focusedId;
15556
- livePanel.style.cssText = `
15557
- flex: 0 0 auto;
15558
- display: grid;
15559
- grid-template-columns: minmax(220px, 0.95fr) minmax(0, 1.25fr);
15560
- gap: 10px;
15561
- min-height: 170px;
15562
- padding: 10px;
15563
- border-radius: 8px;
15564
- border: 1px solid rgba(14, 165, 233, 0.24);
15565
- background: rgba(255, 255, 255, 0.82);
15566
- `;
15567
-
15568
- const livePreview = document.createElement('div');
15569
- livePreview.dataset.remoteFleetDevicePreview = 'focused-live';
15570
- livePreview.dataset.deviceId = focusedId;
15571
- livePreview.dataset.remoteFleetFrameKind = hasLiveFrame ? 'live' : '';
15572
- livePreview.dataset.remoteFleetFrameSeq = String(getRemoteFleetDeviceField(focusedDevice, 'liveFrameSeq', 'LiveFrameSeq', 0));
15573
- livePreview.style.cssText = `
15574
- position: relative;
15575
- min-width: 0;
15576
- aspect-ratio: 16 / 9;
15577
- overflow: hidden;
15578
- border-radius: 7px;
15579
- background: linear-gradient(135deg, #111827 0%, #1f2937 100%);
15580
- border: 1px solid rgba(15, 23, 42, 0.16);
15581
- `;
15582
- if (hasLiveFrame) {
15583
- const image = document.createElement('img');
15584
- image.dataset.remoteFleetFrameImage = 'true';
15585
- image.dataset.remoteFleetFrameKind = 'live';
15586
- image.dataset.remoteFleetFrameSeq = livePreview.dataset.remoteFleetFrameSeq;
15587
- image.src = liveFrameSource;
15588
- image.alt = `${focusedName} live screen`;
15589
- image.decoding = 'async';
15590
- image.style.cssText = `
15591
- width: 100%;
15592
- height: 100%;
15593
- object-fit: cover;
15594
- display: block;
15595
- `;
15596
- livePreview.appendChild(image);
15597
- } else {
15598
- const placeholder = document.createElement('div');
15599
- placeholder.dataset.remoteFleetScreenPlaceholder = 'true';
15600
- placeholder.textContent = liveActive ? 'Waiting for live frame' : 'Live view idle';
15601
- placeholder.style.cssText = `
15602
- position: absolute;
15603
- inset: 0;
15604
- display: flex;
15605
- align-items: center;
15606
- justify-content: center;
15607
- color: rgba(226, 232, 240, 0.82);
15608
- font-size: 12px;
15609
- font-weight: 900;
15610
- letter-spacing: 0;
15611
- `;
15612
- livePreview.appendChild(placeholder);
15613
- }
15614
- const liveBadge = document.createElement('span');
15615
- liveBadge.dataset.remoteFleetFrameBadge = 'true';
15616
- liveBadge.textContent = liveActive
15617
- ? `LIVE ${liveFrameAt ? formatRemoteFleetAge(liveFrameAt) : ''}`.trim()
15618
- : 'FOCUS';
15619
- liveBadge.style.cssText = `
15620
- position: absolute;
15621
- left: 7px;
15622
- top: 7px;
15623
- max-width: calc(100% - 14px);
15624
- padding: 4px 7px;
15625
- border-radius: 999px;
15626
- background: ${liveActive ? 'rgba(220, 38, 38, 0.86)' : 'rgba(15, 23, 42, 0.72)'};
15627
- color: #ffffff;
15628
- font-size: 9px;
15629
- font-weight: 950;
15630
- line-height: 1;
15631
- overflow: hidden;
15632
- text-overflow: ellipsis;
15633
- white-space: nowrap;
15634
- letter-spacing: 0;
15635
- `;
15636
- livePreview.appendChild(liveBadge);
15637
-
15638
- const liveInfo = document.createElement('div');
15639
- liveInfo.style.cssText = `
15640
- min-width: 0;
15641
- display: flex;
15642
- flex-direction: column;
15643
- justify-content: space-between;
15644
- gap: 8px;
15645
- `;
15646
- const liveTitle = document.createElement('div');
15647
- liveTitle.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:3px;';
15648
- const liveName = document.createElement('strong');
15649
- liveName.textContent = focusedName;
15650
- liveName.title = focusedName;
15651
- liveName.style.cssText = 'color:#0f172a;font-size:15px;font-weight:950;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
15652
- const liveMeta = document.createElement('span');
15653
- liveMeta.textContent = liveActive
15654
- ? `RemoteFast ${getRemoteFleetDeviceField(focusedDevice, 'liveStreamFps', 'LiveStreamFps', 0) || 20} fps`
15655
- : 'View-only focused screen stream';
15656
- liveMeta.style.cssText = 'color:#475569;font-size:11px;font-weight:800;line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
15657
- liveTitle.appendChild(liveName);
15658
- liveTitle.appendChild(liveMeta);
15659
-
15660
- const liveActions = document.createElement('div');
15661
- liveActions.style.cssText = 'display:flex;align-items:center;gap:8px;flex-wrap:wrap;';
15662
- if (isRemoteFleetDeviceConnected(focusedDevice) && isRemoteFleetLiveCapable(focusedDevice)) {
15663
- const startLiveButton = createRemoteFleetButton(liveActive ? 'Restart live' : 'Start live', 'Start focused view-only live stream', 'live-start');
15664
- startLiveButton.dataset.deviceId = focusedId;
15665
- startLiveButton.style.height = '30px';
15666
- liveActions.appendChild(startLiveButton);
15667
- if (liveActive) {
15668
- const stopLiveButton = createRemoteFleetButton('Stop live', 'Stop focused live stream', 'live-stop');
15669
- stopLiveButton.dataset.deviceId = focusedId;
15670
- stopLiveButton.dataset.streamId = liveStreamId;
15671
- stopLiveButton.style.height = '30px';
15672
- stopLiveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
15673
- stopLiveButton.style.color = '#b91c1c';
15674
- liveActions.appendChild(stopLiveButton);
15675
- }
15676
- } else {
15677
- const disabled = document.createElement('span');
15678
- disabled.textContent = 'Live unavailable';
15679
- disabled.style.cssText = 'color:#64748b;font-size:11px;font-weight:900;';
15680
- liveActions.appendChild(disabled);
15681
- }
15682
- liveInfo.appendChild(liveTitle);
15683
- liveInfo.appendChild(liveActions);
15684
- livePanel.appendChild(livePreview);
15685
- livePanel.appendChild(liveInfo);
15686
- bodyView.appendChild(livePanel);
15687
- }
15688
-
15689
15611
  const createDevicePreview = (device, mode = 'tile') => {
15690
15612
  const name = getRemoteFleetDeviceName(device);
15691
15613
  const connectedDevice = isRemoteFleetDeviceConnected(device);
@@ -16246,6 +16168,12 @@
16246
16168
  && (isRemoteFleetLiveCapable(focusedDevice) || isRemoteFleetLiveActive(focusedDevice))) {
16247
16169
  pushUniqueFrameDeviceId(ids, focusedDevice);
16248
16170
  }
16171
+ const autoLiveDevice = getAutoLiveDevice();
16172
+ if (autoLiveDevice
16173
+ && isRemoteFleetDeviceConnected(autoLiveDevice)
16174
+ && isRemoteFleetLiveCapable(autoLiveDevice)) {
16175
+ pushUniqueFrameDeviceId(ids, autoLiveDevice);
16176
+ }
16249
16177
  devices
16250
16178
  .filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetLiveActive(device))
16251
16179
  .slice(0, 6)
@@ -16259,7 +16187,7 @@
16259
16187
  if (focusedDevice && isRemoteFleetDeviceConnected(focusedDevice) && isRemoteFleetLiveCapable(focusedDevice)) {
16260
16188
  return focusedDevice;
16261
16189
  }
16262
- return null;
16190
+ return devices.find(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetLiveCapable(device)) || null;
16263
16191
  };
16264
16192
  const ensureFocusedRemoteFleetLiveStream = async () => {
16265
16193
  const target = getAutoLiveDevice();
@@ -16323,6 +16251,27 @@
16323
16251
  applyRemoteFleetFramePatches(bodyView, patches);
16324
16252
  return result;
16325
16253
  };
16254
+ const startRemoteFleetMonitorRefreshTimer = (intervalMs, reason = 'monitor') => {
16255
+ const interval = Math.max(1000, Number(intervalMs) || REMOTE_FLEET_MONITOR_REFRESH_MS);
16256
+ bodyView.dataset.remoteFleetRefreshReason = reason;
16257
+ bodyView._remoteFleetMonitorRefreshTimer = setInterval(async () => {
16258
+ if (!document.body.contains(bodyView)) {
16259
+ clearRemoteFleetTimers(bodyView);
16260
+ return;
16261
+ }
16262
+
16263
+ try {
16264
+ await refreshRemoteFleetNode();
16265
+ } catch (error) {
16266
+ window.RuntimeTrace?.emit?.('remote.monitor.refreshFailed', {
16267
+ nodeId,
16268
+ reason,
16269
+ error: error?.message || String(error || '')
16270
+ });
16271
+ clearRemoteFleetTimers(bodyView);
16272
+ }
16273
+ }, interval);
16274
+ };
16326
16275
  const prepareRemoteFleetTaskFollow = batch => {
16327
16276
  if (!isRemoteFleetTaskBatchActive(batch)) {
16328
16277
  bodyView.dataset.remoteFleetTaskFollowActive = 'false';
@@ -16837,18 +16786,9 @@
16837
16786
  }, REMOTE_FLEET_LIVE_REFRESH_MS);
16838
16787
  } else if (autoMonitorState && hasMonitorTargets) {
16839
16788
  startRemoteFleetFrameLoop(bodyView, REMOTE_FLEET_THUMBNAIL_FRAME_REFRESH_MS, () => refreshRemoteFleetVisibleFrames(true));
16840
- bodyView._remoteFleetMonitorRefreshTimer = setInterval(async () => {
16841
- if (!document.body.contains(bodyView)) {
16842
- clearRemoteFleetTimers(bodyView);
16843
- return;
16844
- }
16845
-
16846
- try {
16847
- await refreshRemoteFleetNode();
16848
- } catch {
16849
- clearRemoteFleetTimers(bodyView);
16850
- }
16851
- }, REMOTE_FLEET_MONITOR_REFRESH_MS);
16789
+ startRemoteFleetMonitorRefreshTimer(REMOTE_FLEET_MONITOR_REFRESH_MS, 'visible-devices');
16790
+ } else if (autoMonitorState) {
16791
+ startRemoteFleetMonitorRefreshTimer(REMOTE_FLEET_EMPTY_MONITOR_REFRESH_MS, 'empty-discovery');
16852
16792
  }
16853
16793
  }
16854
16794
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-aehUBy4ZKXMCMfDXLIq7332pdALPad8G/hoFfyL1YcY=",
4
+ "hash": "sha256-we9qenVtePc2CABTfoN+/D96wdPuqIl4CgKuk3znAUA=",
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.7k773jan4j.dll": "MindExecution.Kernel.dll",
128
128
  "MindExecution.Plugins.Admin.nr5ioavpvn.dll": "MindExecution.Plugins.Admin.dll",
129
129
  "MindExecution.Plugins.Business.qq63kom73x.dll": "MindExecution.Plugins.Business.dll",
130
- "MindExecution.Plugins.Concept.y4ipk4t13u.dll": "MindExecution.Plugins.Concept.dll",
130
+ "MindExecution.Plugins.Concept.w4znwcrl6x.dll": "MindExecution.Plugins.Concept.dll",
131
131
  "MindExecution.Plugins.Directory.u6p6pxpwxp.dll": "MindExecution.Plugins.Directory.dll",
132
- "MindExecution.Plugins.PlanMaster.3gd195fq5t.dll": "MindExecution.Plugins.PlanMaster.dll",
133
- "MindExecution.Plugins.YouTube.sb0cb9cans.dll": "MindExecution.Plugins.YouTube.dll",
134
- "MindExecution.Shared.ib8s249hay.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.bfrevq2q4y.dll": "MindExecution.Web.dll",
132
+ "MindExecution.Plugins.PlanMaster.0w4vr40lnz.dll": "MindExecution.Plugins.PlanMaster.dll",
133
+ "MindExecution.Plugins.YouTube.sdl5n9uv68.dll": "MindExecution.Plugins.YouTube.dll",
134
+ "MindExecution.Shared.fprczldv6z.dll": "MindExecution.Shared.dll",
135
+ "MindExecution.Web.xhlvm1d8ex.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.cdplo34sb2.dll": "sha256-sLc0yEeGYQhFPxxNKLQsHllAhwjKhNn+pfgRY0bHbpo=",
282
282
  "MindExecution.Kernel.7k773jan4j.dll": "sha256-B5H6hzB6g8sWLqXUoXifv8CbLqVt7g1lLkkinTBKF8E=",
283
- "MindExecution.Plugins.Concept.y4ipk4t13u.dll": "sha256-ElyrQZwOP6pofpDFl6jxXzggowpO1TJO2Rg7ipnXrVE=",
284
- "MindExecution.Plugins.PlanMaster.3gd195fq5t.dll": "sha256-U0cqGsVxa696XZuKmipYMdlZ3Da761XTPmB5H+7oX7I=",
285
- "MindExecution.Shared.ib8s249hay.dll": "sha256-HrtN17KC7pAEJ7wf9AihBGmtftOOKR+S1iaj4FTtlwU=",
286
- "MindExecution.Web.bfrevq2q4y.dll": "sha256-qbbJ5AlXO8D8IEMvXHyA9a64eJEidL8Ksn4a/SD7pu8="
283
+ "MindExecution.Plugins.Concept.w4znwcrl6x.dll": "sha256-oNGAmbTjkFdVfZeYbc4jYWtyQa3c8Tle5LIXoFye4yo=",
284
+ "MindExecution.Plugins.PlanMaster.0w4vr40lnz.dll": "sha256-gPWCEzDE7cJLOFTJOhja3JXJzkHFWd4dzfRdsdTxwps=",
285
+ "MindExecution.Shared.fprczldv6z.dll": "sha256-veGmVlpQw2b1zDaUWReHRSLbiZ2IloG79uo1GLlduEo=",
286
+ "MindExecution.Web.xhlvm1d8ex.dll": "sha256-azcD8wLJIxuNSplzTVvh2ndLwdcu20xz1t8Jh23Dr7Q="
287
287
  },
288
288
  "lazyAssembly": {
289
289
  "MindExecution.Plugins.Admin.nr5ioavpvn.dll": "sha256-TWIfHGuEUUD1JtyuhfH4NM5JkvqAt8KcDVrWxvmwnVc=",
290
290
  "MindExecution.Plugins.Business.qq63kom73x.dll": "sha256-4lgIcKclWQIjijrBJJFu84fgAH4MsDCN/myhG/K9OOU=",
291
291
  "MindExecution.Plugins.Directory.u6p6pxpwxp.dll": "sha256-BjJ6/XNfp5wd/1RTdh99X9RR0I1GV6t4bp5A+RlAK9s=",
292
- "MindExecution.Plugins.YouTube.sb0cb9cans.dll": "sha256-WIQjip313fTDOOZ82NrcgM3dSrtgE8+A3grbIyBDMDE="
292
+ "MindExecution.Plugins.YouTube.sdl5n9uv68.dll": "sha256-gNZE7M6cqiF9aEJYdZtUzt5bYPkh5Be7LKuyh286u2Q="
293
293
  }
294
294
  },
295
295
  "cacheBootResources": true,
@@ -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-mdm-live-10fps-v557" />
11
- <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-mdm-live-10fps-v557" />
10
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260616-mdm-latest-frame-v559" />
11
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-mdm-latest-frame-v559" />
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-mdm-live-10fps-v557';
582
+ const scriptVersion = '20260616-mdm-latest-frame-v559';
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": "ubjvvUaQ",
2
+ "version": "EUMTR2WK",
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-jqoAthq+GECm6zHrHdOnHyBxLrin0deFSEmEXlCpIqg=",
89
+ "hash": "sha256-moU5KG/6LqgoCKviugaQ703R/oh1u8n08Xkoih7tDUg=",
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.qq63kom73x.dll"
427
427
  },
428
428
  {
429
- "hash": "sha256-ElyrQZwOP6pofpDFl6jxXzggowpO1TJO2Rg7ipnXrVE=",
430
- "url": "_framework/MindExecution.Plugins.Concept.y4ipk4t13u.dll"
429
+ "hash": "sha256-oNGAmbTjkFdVfZeYbc4jYWtyQa3c8Tle5LIXoFye4yo=",
430
+ "url": "_framework/MindExecution.Plugins.Concept.w4znwcrl6x.dll"
431
431
  },
432
432
  {
433
433
  "hash": "sha256-BjJ6/XNfp5wd/1RTdh99X9RR0I1GV6t4bp5A+RlAK9s=",
434
434
  "url": "_framework/MindExecution.Plugins.Directory.u6p6pxpwxp.dll"
435
435
  },
436
436
  {
437
- "hash": "sha256-U0cqGsVxa696XZuKmipYMdlZ3Da761XTPmB5H+7oX7I=",
438
- "url": "_framework/MindExecution.Plugins.PlanMaster.3gd195fq5t.dll"
437
+ "hash": "sha256-gPWCEzDE7cJLOFTJOhja3JXJzkHFWd4dzfRdsdTxwps=",
438
+ "url": "_framework/MindExecution.Plugins.PlanMaster.0w4vr40lnz.dll"
439
439
  },
440
440
  {
441
- "hash": "sha256-WIQjip313fTDOOZ82NrcgM3dSrtgE8+A3grbIyBDMDE=",
442
- "url": "_framework/MindExecution.Plugins.YouTube.sb0cb9cans.dll"
441
+ "hash": "sha256-gNZE7M6cqiF9aEJYdZtUzt5bYPkh5Be7LKuyh286u2Q=",
442
+ "url": "_framework/MindExecution.Plugins.YouTube.sdl5n9uv68.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-HrtN17KC7pAEJ7wf9AihBGmtftOOKR+S1iaj4FTtlwU=",
446
- "url": "_framework/MindExecution.Shared.ib8s249hay.dll"
445
+ "hash": "sha256-veGmVlpQw2b1zDaUWReHRSLbiZ2IloG79uo1GLlduEo=",
446
+ "url": "_framework/MindExecution.Shared.fprczldv6z.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-qbbJ5AlXO8D8IEMvXHyA9a64eJEidL8Ksn4a/SD7pu8=",
450
- "url": "_framework/MindExecution.Web.bfrevq2q4y.dll"
449
+ "hash": "sha256-azcD8wLJIxuNSplzTVvh2ndLwdcu20xz1t8Jh23Dr7Q=",
450
+ "url": "_framework/MindExecution.Web.xhlvm1d8ex.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-lwdIDm7MRgjojZnnKDxY0mgEXEahNVpbFzIiLxbPh9k=",
773
+ "hash": "sha256-vjnwzCMMAs+8it6vEUPeOD7pI/ekFwMr484KaDdpugE=",
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-oH92qacgFgianR88nktzfiIq92f7S1DnX+p/mh/U5yQ=",
837
+ "hash": "sha256-FjUaVgGOa4fyf3cF3WIvk0qYkTDuxmB0aE3BTcR7aYU=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: ubjvvUaQ */
1
+ /* Manifest version: EUMTR2WK */
2
2
  // Hosted deployments should prefer the network over stale offline caches.
3
3
  // This service worker immediately clears old Blazor offline caches and unregisters itself.
4
4