@mindexec/cli 0.2.158 → 0.2.160

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.158",
3
+ "version": "0.2.160",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -1043,6 +1043,7 @@ try {
1043
1043
  binaryDrawCalls.some(call => call.op === 'clearRect' || call.op === 'fillRect'),
1044
1044
  false,
1045
1045
  'binary Blob frame paint must not clear/fill the canvas before drawing');
1046
+ const binaryCanvasBeforeRerender = binaryCanvas;
1046
1047
 
1047
1048
  const liveOverlayCard = document.createElement('div');
1048
1049
  liveOverlayCard.dataset.nodeId = 'remote-fleet-render-smoke';
@@ -1105,7 +1106,12 @@ try {
1105
1106
  ?.querySelector('[data-remote-fleet-device-preview="tile"]');
1106
1107
  assert.ok(rerenderedPatchPreview);
1107
1108
  assert.equal(rerenderedPatchPreview.dataset.remoteFleetFrameSeq, String(websocketFrameSeq));
1108
- assert.equal(rerenderedPatchPreview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]')?.style.display, 'block');
1109
+ const rerenderedPatchCanvas = rerenderedPatchPreview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]');
1110
+ assert.equal(rerenderedPatchCanvas?.style.display, 'block');
1111
+ assert.equal(
1112
+ rerenderedPatchCanvas,
1113
+ binaryCanvasBeforeRerender,
1114
+ 'MDM rerender should reuse the already-painted canvas instead of flashing a blank replacement');
1109
1115
 
1110
1116
  const otherBoardNodeId = 'remote-fleet-render-smoke-other-board';
1111
1117
  const { nodeShell: otherBoardShell, bodyView: otherBoardBodyView } =
@@ -1322,10 +1328,21 @@ try {
1322
1328
  const drawCountBeforeControlNode = document.canvasDrawCalls.length;
1323
1329
  manager.renderRemoteFleetDeviceForTest(deviceBody, buildDeviceNode(focusedDevice, hub.getStatus()));
1324
1330
  assert.ok(deviceBody.textContent.includes(focusedDevice.Name));
1331
+ assert.ok(deviceBody.querySelector('[data-remote-fleet-device-toolbar="true"]'));
1332
+ assert.ok(deviceBody.querySelector('[data-remote-fleet-device-actions="true"]'));
1333
+ assert.ok(deviceBody.querySelector('[data-remote-fleet-device-task-panel="true"]'));
1334
+ const deviceStatusBar = deviceBody.querySelector('[data-remote-fleet-device-status-bar="true"]')
1335
+ || deviceBody.querySelector('[data-remote-fleet-device-statusbar="true"]');
1336
+ assert.ok(deviceStatusBar);
1325
1337
  const deviceControlStage = deviceBody.querySelector('[data-remote-fleet-control-node-stage="true"]');
1326
1338
  const deviceControlCanvas = deviceBody.querySelector('[data-remote-fleet-control-node-canvas="true"]');
1327
1339
  assert.ok(deviceControlStage);
1328
1340
  assert.ok(deviceControlCanvas);
1341
+ assert.equal(
1342
+ deviceControlStage.parentElement,
1343
+ deviceBody,
1344
+ 'RemoteFleetDevice should spend its main body area on the control screen');
1345
+ assert.equal(deviceBody.querySelectorAll('[data-remote-fleet-status-card], [data-remote-fleet-stat]').length, 0);
1329
1346
  assert.equal(document.activeElement, deviceControlStage);
1330
1347
  await wait(30);
1331
1348
  const deviceControlSubscribe = diagnostics.webSocketSends
@@ -1433,7 +1450,19 @@ try {
1433
1450
  assert.ok(deviceBody.querySelector('[data-remote-fleet-action="refresh-device"]'));
1434
1451
  assert.ok(deviceBody.querySelector('[data-remote-fleet-action="task-device"]'));
1435
1452
  assert.ok(deviceBody.textContent.includes('Task timed out waiting for the agent response.'));
1436
- assert.ok(deviceBody.textContent.includes(focusedDevice.DeviceId));
1453
+ assert.ok(deviceStatusBar.title.includes(focusedDevice.DeviceId));
1454
+ const controlCanvasBeforeRerender = deviceControlCanvas;
1455
+ manager.renderRemoteFleetDeviceForTest(deviceBody, buildDeviceNode(focusedDevice, hub.getStatus()));
1456
+ await wait(30);
1457
+ const controlCanvasAfterRerender = deviceBody.querySelector('[data-remote-fleet-control-node-canvas="true"]');
1458
+ assert.equal(
1459
+ controlCanvasAfterRerender,
1460
+ controlCanvasBeforeRerender,
1461
+ 'RemoteFleetDevice rerender should reuse the already-painted control canvas instead of flashing black');
1462
+ assert.equal(
1463
+ deviceBody.querySelector('[data-remote-fleet-control-node-placeholder="true"]'),
1464
+ null,
1465
+ 'RemoteFleetDevice rerender should not cover a restored control canvas with a waiting placeholder');
1437
1466
 
1438
1467
  nodeShell.remove();
1439
1468
  emptyShell.remove();
@@ -2585,6 +2585,17 @@ body.mindcanvas-is-dense .css3d-resolution-wrapper.node-type-templatelauncher.se
2585
2585
  background: #ffffff;
2586
2586
  }
2587
2587
 
2588
+ .map-node-template-card.map-node-remote-device .template-card__shell--remote-fleet {
2589
+ gap: 0;
2590
+ padding: 8px;
2591
+ }
2592
+
2593
+ .map-node-template-card.map-node-remote-device .template-card__remote-fleet-body {
2594
+ border: 0;
2595
+ border-radius: 0;
2596
+ background: transparent;
2597
+ }
2598
+
2588
2599
  .template-card__generate:hover {
2589
2600
  background: #115e59;
2590
2601
  }
@@ -4008,7 +4008,9 @@
4008
4008
 
4009
4009
  function isTemplateLauncherNode(nodeModel) {
4010
4010
  const contentType = String(nodeModel?.contentType ?? nodeModel?.ContentType ?? '').trim().toLowerCase();
4011
- return contentType === 'templatelauncher' || isRemoteFleetMonitorNode(nodeModel);
4011
+ return contentType === 'templatelauncher'
4012
+ || isRemoteFleetMonitorNode(nodeModel)
4013
+ || isRemoteFleetDeviceNode(nodeModel);
4012
4014
  }
4013
4015
 
4014
4016
  function isLiveOverlayPortalNode(nodeModel) {
@@ -4031,8 +4033,7 @@
4031
4033
  const semanticType = getNodeSemanticType(nodeModel);
4032
4034
  return semanticType === 'MindCanvasAgent'
4033
4035
  || semanticType === 'AgentCommand'
4034
- || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE
4035
- || semanticType === REMOTE_FLEET_DEVICE_SEMANTIC_TYPE;
4036
+ || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE;
4036
4037
  }
4037
4038
 
4038
4039
  function allowsMemoExternalChrome(nodeModel) {
@@ -11977,8 +11978,7 @@
11977
11978
  const isAgentStyled = element?.classList?.contains('map-node-agent')
11978
11979
  || semanticType === 'MindCanvasAgent'
11979
11980
  || semanticType === 'AgentCommand'
11980
- || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE
11981
- || semanticType === REMOTE_FLEET_DEVICE_SEMANTIC_TYPE;
11981
+ || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE;
11982
11982
  wrapper.classList.toggle('selection-style-agent', !!isAgentStyled);
11983
11983
  const nextColorKey = MEMO_COLOR_THEMES[colorKey] ? colorKey : 'coral';
11984
11984
  const theme = MEMO_COLOR_THEMES[nextColorKey] || MEMO_COLOR_THEMES.gray;
@@ -12264,8 +12264,13 @@
12264
12264
  '[data-template-option-id]',
12265
12265
  '[data-remote-fleet-action]',
12266
12266
  '[data-remote-fleet-control-popup]',
12267
+ '[data-remote-fleet-control-node-stage]',
12268
+ '[data-remote-fleet-control-node-canvas]',
12269
+ '[data-remote-fleet-control-node-placeholder]',
12267
12270
  '[data-remote-fleet-task-input]',
12271
+ '[data-remote-fleet-device-task-input]',
12268
12272
  '[data-remote-fleet-ai-toggle]',
12273
+ '[data-remote-fleet-device-ai-toggle]',
12269
12274
  '[data-remote-fleet-search]',
12270
12275
  '.csv-table-scroll',
12271
12276
  '.csv-table-header-button',
@@ -12426,7 +12431,7 @@
12426
12431
  }
12427
12432
 
12428
12433
  function createTemplateLauncherNodeElement(nodeModel) {
12429
- if (isRemoteFleetMonitorNode(nodeModel)) {
12434
+ if (isRemoteFleetNode(nodeModel)) {
12430
12435
  return createRemoteFleetTemplateNodeElement(nodeModel);
12431
12436
  }
12432
12437
 
@@ -12532,10 +12537,11 @@
12532
12537
  const width = Number(nodeModel.width ?? nodeModel.Width ?? 960);
12533
12538
  const height = Number(nodeModel.height ?? nodeModel.Height ?? 620);
12534
12539
  const title = getRemoteFleetDisplayTitle(nodeModel.prompt ?? nodeModel.Prompt);
12540
+ const isDeviceNode = isRemoteFleetDeviceNode(nodeModel);
12535
12541
 
12536
12542
  const container = document.createElement('div');
12537
12543
  container.id = `node-${nodeModel.id}`;
12538
- container.className = 'map-node css3d-dynamic-node map-node-template-card map-node-remote-fleet';
12544
+ container.className = `map-node css3d-dynamic-node map-node-template-card map-node-remote-fleet${isDeviceNode ? ' map-node-remote-device' : ''}`;
12539
12545
  container.dataset.nodeId = nodeModel.id;
12540
12546
  container.dataset.contentType = 'templateLauncher';
12541
12547
  container.dataset.semanticType = getNodeSemanticType(nodeModel);
@@ -12550,25 +12556,31 @@
12550
12556
  const shell = document.createElement('div');
12551
12557
  shell.className = 'template-card__shell template-card__shell--remote-fleet';
12552
12558
 
12553
- const header = document.createElement('div');
12554
- header.className = 'template-card__header template-card__header--remote-fleet';
12559
+ if (!isDeviceNode) {
12560
+ const header = document.createElement('div');
12561
+ header.className = 'template-card__header template-card__header--remote-fleet';
12555
12562
 
12556
- const icon = document.createElement('div');
12557
- icon.className = 'template-card__icon template-card__icon--remote-fleet';
12558
- icon.innerHTML = '<i class="fa-solid fa-network-wired" aria-hidden="true"></i>';
12559
- header.appendChild(icon);
12563
+ const icon = document.createElement('div');
12564
+ icon.className = 'template-card__icon template-card__icon--remote-fleet';
12565
+ icon.innerHTML = '<i class="fa-solid fa-network-wired" aria-hidden="true"></i>';
12566
+ header.appendChild(icon);
12560
12567
 
12561
- const titleBlock = document.createElement('div');
12562
- titleBlock.className = 'template-card__title-block';
12563
- titleBlock.appendChild(createTemplateCardText('div', 'template-card__eyebrow', 'Remote'));
12564
- titleBlock.appendChild(createTemplateCardText('div', 'template-card__title', title));
12565
- header.appendChild(titleBlock);
12566
- shell.appendChild(header);
12568
+ const titleBlock = document.createElement('div');
12569
+ titleBlock.className = 'template-card__title-block';
12570
+ titleBlock.appendChild(createTemplateCardText('div', 'template-card__eyebrow', 'Remote'));
12571
+ titleBlock.appendChild(createTemplateCardText('div', 'template-card__title', title));
12572
+ header.appendChild(titleBlock);
12573
+ shell.appendChild(header);
12574
+ }
12567
12575
 
12568
12576
  const bodyView = document.createElement('div');
12569
12577
  bodyView.className = 'template-card__remote-fleet-body markdown-body mind-map-text-scrollable-view';
12570
12578
  bodyView.dataset.nodeId = nodeModel.id;
12571
- renderRemoteFleetMonitor(bodyView, nodeModel);
12579
+ if (isDeviceNode) {
12580
+ renderRemoteFleetDevice(bodyView, nodeModel);
12581
+ } else {
12582
+ renderRemoteFleetMonitor(bodyView, nodeModel);
12583
+ }
12572
12584
  shell.appendChild(bodyView);
12573
12585
 
12574
12586
  container.appendChild(shell);
@@ -14842,6 +14854,104 @@
14842
14854
  return canvas;
14843
14855
  }
14844
14856
 
14857
+ function collectRemoteFleetPreviewFrameSurfaces(root) {
14858
+ const surfaces = new Map();
14859
+ if (!root?.querySelectorAll) {
14860
+ return surfaces;
14861
+ }
14862
+
14863
+ root.querySelectorAll('[data-remote-fleet-device-preview][data-device-id]').forEach(preview => {
14864
+ const deviceId = String(preview.dataset.deviceId || '').trim();
14865
+ if (!deviceId) {
14866
+ return;
14867
+ }
14868
+
14869
+ const canvas = preview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]');
14870
+ const image = preview.querySelector('img[data-remote-fleet-frame-image="true"]');
14871
+ surfaces.set(deviceId, {
14872
+ canvas,
14873
+ image,
14874
+ kind: String(preview.dataset.remoteFleetFrameKind || canvas?.dataset?.remoteFleetFrameKind || image?.dataset?.remoteFleetFrameKind || '').trim().toLowerCase(),
14875
+ seq: Number(preview.dataset.remoteFleetFrameSeq || canvas?.dataset?.remoteFleetFrameSeq || image?.dataset?.remoteFleetFrameSeq || 0) || 0,
14876
+ url: String(preview.dataset.remoteFleetFrameUrl || canvas?.dataset?.remoteFleetFrameUrl || image?.dataset?.remoteFleetFrameUrl || '').trim()
14877
+ });
14878
+ });
14879
+
14880
+ return surfaces;
14881
+ }
14882
+
14883
+ function restoreRemoteFleetPreviewFrameSurface(preview, surfaces, deviceId) {
14884
+ const id = String(deviceId || preview?.dataset?.deviceId || '').trim();
14885
+ const surface = id && surfaces instanceof Map ? surfaces.get(id) : null;
14886
+ if (!preview || !surface) {
14887
+ return false;
14888
+ }
14889
+
14890
+ const existingSeq = Number(surface.seq || 0) || 0;
14891
+ const nextSeq = Number(preview.dataset.remoteFleetFrameSeq || 0) || 0;
14892
+ if (existingSeq <= 0 || (nextSeq > 0 && existingSeq < nextSeq)) {
14893
+ return false;
14894
+ }
14895
+
14896
+ const currentCanvas = preview.querySelector('canvas[data-remote-fleet-frame-canvas="true"]');
14897
+ if (surface.canvas && surface.canvas !== currentCanvas) {
14898
+ currentCanvas?.remove?.();
14899
+ surface.canvas.dataset.remoteFleetFrameKind = surface.kind || preview.dataset.remoteFleetFrameKind || 'live';
14900
+ surface.canvas.dataset.remoteFleetFrameSeq = String(existingSeq);
14901
+ surface.canvas.dataset.remoteFleetFrameUrl = surface.url || '';
14902
+ surface.canvas.style.display = 'block';
14903
+ if (typeof preview.insertBefore === 'function') {
14904
+ preview.insertBefore(surface.canvas, preview.firstChild || null);
14905
+ } else {
14906
+ preview.appendChild?.(surface.canvas);
14907
+ }
14908
+ }
14909
+
14910
+ const currentImage = preview.querySelector('img[data-remote-fleet-frame-image="true"]');
14911
+ if (surface.canvas) {
14912
+ preview.querySelector('[data-remote-fleet-screen-placeholder="true"]')?.remove?.();
14913
+ if (currentImage && currentImage !== surface.image) {
14914
+ currentImage.style.display = 'none';
14915
+ }
14916
+ if (surface.image && surface.image !== currentImage && surface.image.parentElement === preview) {
14917
+ surface.image.style.display = 'none';
14918
+ }
14919
+ } else if (surface.image && surface.image !== currentImage) {
14920
+ currentImage?.remove?.();
14921
+ surface.image.style.display = 'block';
14922
+ if (typeof preview.insertBefore === 'function') {
14923
+ preview.insertBefore(surface.image, preview.firstChild || null);
14924
+ } else {
14925
+ preview.appendChild?.(surface.image);
14926
+ }
14927
+ }
14928
+
14929
+ preview.dataset.remoteFleetFrameKind = surface.kind || preview.dataset.remoteFleetFrameKind || '';
14930
+ preview.dataset.remoteFleetFrameSeq = String(existingSeq);
14931
+ preview.dataset.remoteFleetFrameUrl = surface.url || '';
14932
+ preview.dataset.remoteFleetRestoredFrame = 'true';
14933
+ return true;
14934
+ }
14935
+
14936
+ function collectRemoteFleetControlNodeSurface(root) {
14937
+ const canvas = root?.querySelector?.('canvas[data-remote-fleet-control-node-canvas="true"]');
14938
+ if (!canvas) {
14939
+ return null;
14940
+ }
14941
+
14942
+ return {
14943
+ canvas,
14944
+ kind: String(canvas.dataset.remoteFleetFrameKind || 'live').trim().toLowerCase(),
14945
+ seq: Number(canvas.dataset.remoteFleetFrameSeq || 0) || 0,
14946
+ contentRect: canvas._remoteFleetContentRect || null
14947
+ };
14948
+ }
14949
+
14950
+ function hasRemoteFleetReusableControlSurface(surface) {
14951
+ return !!surface?.canvas
14952
+ && (Number(surface.seq || 0) > 0 || !!surface.contentRect);
14953
+ }
14954
+
14845
14955
  function syncRemoteFleetFrameCanvasSize(canvas) {
14846
14956
  if (!canvas) {
14847
14957
  return null;
@@ -14853,8 +14963,18 @@
14853
14963
  const cssWidth = Math.max(1, Math.round(rect?.width || canvas.clientWidth || 320));
14854
14964
  const cssHeight = Math.max(1, Math.round(rect?.height || canvas.clientHeight || 180));
14855
14965
  const dpr = Math.max(1, Math.min(REMOTE_FLEET_FRAME_CANVAS_MAX_DPR, Number(window.devicePixelRatio || 1) || 1));
14856
- const nextWidth = Math.max(1, Math.round(cssWidth * dpr));
14857
- const nextHeight = Math.max(1, Math.round(cssHeight * dpr));
14966
+ let nextWidth = Math.max(1, Math.round(cssWidth * dpr));
14967
+ let nextHeight = Math.max(1, Math.round(cssHeight * dpr));
14968
+ const currentWidth = Number(canvas.width || 0) || 0;
14969
+ const currentHeight = Number(canvas.height || 0) || 0;
14970
+ const widthDelta = Math.abs(currentWidth - nextWidth);
14971
+ const heightDelta = Math.abs(currentHeight - nextHeight);
14972
+ if (currentWidth > 0 && widthDelta > 0 && widthDelta <= 2) {
14973
+ nextWidth = currentWidth;
14974
+ }
14975
+ if (currentHeight > 0 && heightDelta > 0 && heightDelta <= 2) {
14976
+ nextHeight = currentHeight;
14977
+ }
14858
14978
  if (canvas.width !== nextWidth) {
14859
14979
  canvas.width = nextWidth;
14860
14980
  }
@@ -15970,7 +16090,12 @@
15970
16090
  }
15971
16091
 
15972
16092
  drawRemoteFleetFrameToCanvas(session.canvas, bitmap, 'contain');
16093
+ session.canvas.dataset.remoteFleetFrameKind = String(frame.kind || 'live').toLowerCase();
16094
+ session.canvas.dataset.remoteFleetFrameSeq = String(Number(frame.frameSeq || frame.FrameSeq || 0) || 0);
16095
+ session.canvas.dataset.remoteFleetFrameUrl = String(frame.frameUrl || '');
15973
16096
  session.canvas.style.display = 'block';
16097
+ session.lastControlFrameSeq = Number(frame.frameSeq || frame.FrameSeq || session.lastControlFrameSeq || 0) || 0;
16098
+ session.lastControlFrameAt = Date.now();
15974
16099
  session.placeholder?.remove?.();
15975
16100
  session.placeholder = null;
15976
16101
  if (typeof bitmap.close === 'function') {
@@ -16020,6 +16145,9 @@
16020
16145
  }
16021
16146
 
16022
16147
  drawRemoteFleetFrameToCanvas(session.canvas, bitmap, 'contain');
16148
+ session.canvas.dataset.remoteFleetFrameKind = String(frame.kind || 'live').toLowerCase();
16149
+ session.canvas.dataset.remoteFleetFrameSeq = String(frameSeq || 0);
16150
+ session.canvas.dataset.remoteFleetFrameUrl = String(frame.frameUrl || '');
16023
16151
  session.canvas.style.display = 'block';
16024
16152
  session.lastControlFrameAt = Date.now();
16025
16153
  session.status.textContent = `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
@@ -16971,6 +17099,7 @@
16971
17099
  function renderRemoteFleetDevice(bodyView, nodeModel) {
16972
17100
  if (!bodyView) return;
16973
17101
  clearRemoteFleetTimers(bodyView, { keepBinaryFrameSocket: true });
17102
+ const preservedControlSurface = collectRemoteFleetControlNodeSurface(bodyView);
16974
17103
 
16975
17104
  const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
16976
17105
  const device = parseRemoteFleetPinnedDevice(nodeModel);
@@ -16978,7 +17107,6 @@
16978
17107
  const refreshedAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastRefreshAtUtc', '');
16979
17108
  const lastError = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastError', '');
16980
17109
  const deviceId = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetPinnedDeviceId', device ? getRemoteFleetDeviceId(device) : '');
16981
- const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
16982
17110
 
16983
17111
  bodyView.dataset.src = `remote-device:${deviceId}:${refreshedAt}`;
16984
17112
  bodyView.classList.add('map-node-remote-fleet__body');
@@ -16987,12 +17115,13 @@
16987
17115
  flex: 1 1 auto;
16988
17116
  min-height: 0;
16989
17117
  pointer-events: auto;
17118
+ position: relative;
16990
17119
  display: flex;
16991
17120
  flex-direction: column;
16992
- gap: 10px;
16993
- padding: 12px 14px 14px;
17121
+ gap: 6px;
17122
+ padding: 6px;
16994
17123
  overflow: hidden;
16995
- background: linear-gradient(180deg, rgba(248, 250, 252, 0.96), rgba(236, 253, 245, 0.78));
17124
+ background: #ffffff;
16996
17125
  `;
16997
17126
 
16998
17127
  const feedback = document.createElement('div');
@@ -17111,42 +17240,82 @@
17111
17240
  const release = String(getRemoteFleetDeviceField(device, 'release', 'Release', ''));
17112
17241
  const latestTaskStatus = String(getRemoteFleetDeviceField(device, 'latestTaskStatus', 'LatestTaskStatus', ''));
17113
17242
  const latestTaskTitle = String(getRemoteFleetDeviceField(device, 'latestTaskTitle', 'LatestTaskTitle', ''));
17114
- const latestTaskApproval = String(getRemoteFleetDeviceField(device, 'latestTaskApprovalLevel', 'LatestTaskApprovalLevel', ''));
17115
17243
  const latestTaskUpdatedAt = String(getRemoteFleetDeviceField(device, 'latestTaskUpdatedAt', 'LatestTaskUpdatedAt', ''));
17116
17244
  const latestTaskError = String(getRemoteFleetDeviceField(device, 'latestTaskError', 'LatestTaskError', ''));
17117
17245
  const latestTaskResultModel = String(getRemoteFleetDeviceField(device, 'latestTaskResultModel', 'LatestTaskResultModel', ''));
17118
17246
  const latestTaskResultResponseId = String(getRemoteFleetDeviceField(device, 'latestTaskResultResponseId', 'LatestTaskResultResponseId', ''));
17119
17247
  const latestTaskResult = String(getRemoteFleetDeviceField(device, 'latestTaskResultSummary', 'LatestTaskResultSummary', ''));
17120
17248
 
17121
- const header = document.createElement('div');
17122
- header.style.cssText = 'display:flex;align-items:flex-start;justify-content:space-between;gap:10px;flex:0 0 auto;';
17123
- const titleBox = document.createElement('div');
17124
- titleBox.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:3px;';
17249
+ const toolbar = document.createElement('div');
17250
+ toolbar.dataset.remoteFleetDeviceToolbar = 'true';
17251
+ toolbar.style.cssText = `
17252
+ flex: 0 0 32px;
17253
+ min-height: 32px;
17254
+ display: flex;
17255
+ align-items: center;
17256
+ gap: 8px;
17257
+ min-width: 0;
17258
+ padding: 0 2px;
17259
+ `;
17260
+ const deviceTitle = document.createElement('div');
17261
+ deviceTitle.style.cssText = `
17262
+ min-width: 0;
17263
+ flex: 1 1 auto;
17264
+ display: flex;
17265
+ align-items: center;
17266
+ gap: 7px;
17267
+ overflow: hidden;
17268
+ `;
17269
+ const stateDot = document.createElement('span');
17270
+ stateDot.title = connected ? 'Online' : 'Offline';
17271
+ stateDot.style.cssText = `
17272
+ width: 8px;
17273
+ height: 8px;
17274
+ border-radius: 50%;
17275
+ flex: 0 0 auto;
17276
+ background: ${connected ? '#10b981' : '#94a3b8'};
17277
+ box-shadow: 0 0 0 2px rgba(226, 232, 240, 0.9);
17278
+ `;
17125
17279
  const title = document.createElement('strong');
17126
17280
  title.textContent = name;
17127
17281
  title.title = `${name} (${deviceId})`;
17128
- title.style.cssText = 'color:#0f172a;font-size:16px;font-weight:950;line-height:1.12;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
17129
- const subtitle = document.createElement('span');
17130
- subtitle.textContent = `${connected ? 'Online' : 'Offline'} - ${release ? `${platform} ${release}` : platform}`;
17131
- subtitle.title = subtitle.textContent;
17132
- subtitle.style.cssText = `color:${connected ? '#047857' : '#64748b'};font-size:11px;font-weight:850;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
17133
- titleBox.appendChild(title);
17134
- titleBox.appendChild(subtitle);
17135
- const statusBadge = document.createElement('span');
17136
- statusBadge.textContent = liveActive ? 'LIVE' : (connected ? 'READY' : 'OFFLINE');
17137
- statusBadge.style.cssText = `
17282
+ title.style.cssText = `
17283
+ min-width: 0;
17284
+ color: #0f172a;
17285
+ font-size: 13px;
17286
+ font-weight: 950;
17287
+ line-height: 1;
17288
+ overflow: hidden;
17289
+ text-overflow: ellipsis;
17290
+ white-space: nowrap;
17291
+ letter-spacing: 0;
17292
+ `;
17293
+ const titleStatus = document.createElement('span');
17294
+ titleStatus.textContent = liveActive ? 'Live' : (connected ? 'Ready' : 'Offline');
17295
+ titleStatus.style.cssText = `
17138
17296
  flex: 0 0 auto;
17139
- padding: 5px 8px;
17140
- border-radius: 999px;
17141
- background: ${liveActive ? 'rgba(220, 38, 38, 0.12)' : connected ? 'rgba(16, 185, 129, 0.12)' : 'rgba(100, 116, 139, 0.12)'};
17142
- color: ${liveActive ? '#b91c1c' : connected ? '#047857' : '#475569'};
17297
+ color: ${connected ? '#047857' : '#64748b'};
17143
17298
  font-size: 10px;
17144
- font-weight: 950;
17299
+ font-weight: 900;
17300
+ line-height: 1;
17145
17301
  letter-spacing: 0;
17146
17302
  `;
17147
- header.appendChild(titleBox);
17148
- header.appendChild(statusBadge);
17149
- bodyView.appendChild(header);
17303
+ deviceTitle.appendChild(stateDot);
17304
+ deviceTitle.appendChild(title);
17305
+ deviceTitle.appendChild(titleStatus);
17306
+ const actions = document.createElement('div');
17307
+ actions.dataset.remoteFleetDeviceActions = 'true';
17308
+ actions.style.cssText = `
17309
+ flex: 0 0 auto;
17310
+ display: flex;
17311
+ align-items: center;
17312
+ justify-content: flex-end;
17313
+ gap: 5px;
17314
+ min-width: 0;
17315
+ `;
17316
+ toolbar.appendChild(deviceTitle);
17317
+ toolbar.appendChild(actions);
17318
+ bodyView.appendChild(toolbar);
17150
17319
 
17151
17320
  const controlStage = document.createElement('div');
17152
17321
  controlStage.dataset.remoteFleetControlNodeStage = 'true';
@@ -17156,17 +17325,17 @@
17156
17325
  controlStage.style.cssText = `
17157
17326
  position: relative;
17158
17327
  flex: 1 1 auto;
17159
- min-height: 260px;
17328
+ min-height: 0;
17160
17329
  width: 100%;
17161
17330
  overflow: hidden;
17162
17331
  border-radius: 0;
17163
17332
  background: #020617;
17164
- border: 1px solid rgba(15, 23, 42, 0.18);
17333
+ border: 1px solid rgba(15, 23, 42, 0.20);
17165
17334
  cursor: crosshair;
17166
17335
  touch-action: none;
17167
17336
  outline: none;
17168
17337
  `;
17169
- const controlCanvas = document.createElement('canvas');
17338
+ const controlCanvas = preservedControlSurface?.canvas || document.createElement('canvas');
17170
17339
  controlCanvas.dataset.remoteFleetControlNodeCanvas = 'true';
17171
17340
  controlCanvas.style.cssText = `
17172
17341
  position: absolute;
@@ -17226,7 +17395,10 @@
17226
17395
  controlOverlay.appendChild(controlDot);
17227
17396
  controlOverlay.appendChild(controlStatus);
17228
17397
  controlStage.appendChild(controlCanvas);
17229
- controlStage.appendChild(controlPlaceholder);
17398
+ const restoredControlSurface = hasRemoteFleetReusableControlSurface(preservedControlSurface);
17399
+ if (!restoredControlSurface) {
17400
+ controlStage.appendChild(controlPlaceholder);
17401
+ }
17230
17402
  controlStage.appendChild(controlOverlay);
17231
17403
  bodyView.appendChild(controlStage);
17232
17404
 
@@ -17238,7 +17410,17 @@
17238
17410
  controlStage,
17239
17411
  controlCanvas,
17240
17412
  controlStatus,
17241
- controlPlaceholder);
17413
+ restoredControlSurface ? null : controlPlaceholder);
17414
+ if (session && restoredControlSurface) {
17415
+ session.lastControlFrameSeq = Number(preservedControlSurface?.seq || session.lastControlFrameSeq || 0) || 0;
17416
+ session.lastControlFrameAt = Date.now();
17417
+ controlStatus.textContent = `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
17418
+ window.RuntimeTrace?.emit?.('remote.control.surfaceRestored', {
17419
+ nodeId,
17420
+ deviceId,
17421
+ seq: session.lastControlFrameSeq
17422
+ });
17423
+ }
17242
17424
  if (session && isRemoteFleetFrameSource(previewSource)) {
17243
17425
  paintRemoteFleetControlFrame(session, {
17244
17426
  deviceId,
@@ -17252,103 +17434,81 @@
17252
17434
  }
17253
17435
  }
17254
17436
 
17255
- const stats = document.createElement('div');
17256
- stats.style.cssText = 'display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px;flex:0 0 auto;';
17257
- stats.appendChild(createRemoteFleetStat('Seen', formatRemoteFleetAge(getRemoteFleetDeviceField(device, 'lastSeenAt', 'LastSeenAt', '')), connected ? 'online' : 'default'));
17258
- stats.appendChild(createRemoteFleetStat('Uptime', formatRemoteFleetDuration(getRemoteFleetDeviceField(device, 'uptimeSec', 'UptimeSec', Number.NaN))));
17259
- stats.appendChild(createRemoteFleetStat('Mem', formatRemoteFleetPercent(getRemoteFleetDeviceField(device, 'usedMemRatio', 'UsedMemRatio', Number.NaN))));
17260
- stats.appendChild(createRemoteFleetStat('Load', formatRemoteFleetNumber(getRemoteFleetDeviceField(device, 'load1', 'Load1', Number.NaN), 2)));
17261
- bodyView.appendChild(stats);
17262
-
17263
- if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
17264
- const taskBox = document.createElement('div');
17265
- const taskTone = latestTaskError || latestTaskStatus === 'failed'
17266
- ? 'error'
17267
- : (latestTaskStatus === 'completed' ? 'done' : 'pending');
17268
- taskBox.style.cssText = `
17269
- display: flex;
17270
- flex-direction: column;
17271
- gap: 3px;
17272
- min-width: 0;
17273
- padding: 8px 9px;
17274
- border-radius: 8px;
17275
- background: ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.12)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
17276
- border: 1px solid ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.24)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.20)' : 'rgba(37, 99, 235, 0.16)'};
17277
- flex: 0 0 auto;
17278
- `;
17279
- const taskLine = document.createElement('div');
17280
- const taskModeLabel = latestTaskApproval === 'ai-assist' ? 'AI' : 'Task';
17281
- taskLine.textContent = `${taskModeLabel} ${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}${latestTaskResultModel ? ` - ${latestTaskResultModel}` : ''}`;
17282
- taskLine.title = latestTaskResultResponseId
17283
- ? `${taskLine.textContent} (${latestTaskResultResponseId})`
17284
- : taskLine.textContent;
17285
- taskLine.style.cssText = `color:${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};font-size:10px;font-weight:900;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
17286
- const taskSummary = document.createElement('div');
17287
- taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
17288
- taskSummary.title = taskSummary.textContent;
17289
- taskSummary.style.cssText = 'color:#334155;font-size:11px;font-weight:750;line-height:1.3;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;letter-spacing:0;';
17290
- taskBox.appendChild(taskLine);
17291
- taskBox.appendChild(taskSummary);
17292
- bodyView.appendChild(taskBox);
17293
- }
17294
-
17295
- const taskInput = document.createElement('textarea');
17296
- taskInput.dataset.remoteFleetDeviceTaskInput = 'true';
17297
- taskInput.placeholder = 'Task for this computer';
17298
- taskInput.rows = 2;
17299
- taskInput.style.cssText = `
17300
- width: 100%;
17301
- min-width: 0;
17302
- height: 50px;
17303
- resize: none;
17304
- border-radius: 8px;
17305
- border: 1px solid rgba(148, 163, 184, 0.34);
17306
- background: rgba(255, 255, 255, 0.92);
17307
- color: #0f172a;
17308
- padding: 8px 10px;
17309
- font-size: 12px;
17310
- line-height: 1.35;
17311
- font-weight: 700;
17312
- letter-spacing: 0;
17313
- outline: none;
17314
- pointer-events: auto;
17315
- `;
17316
- bodyView.appendChild(taskInput);
17317
-
17318
- const actions = document.createElement('div');
17319
- actions.style.cssText = 'display:flex;align-items:center;gap:7px;flex-wrap:wrap;flex:0 0 auto;';
17320
17437
  const refreshButton = createRemoteFleetButton('Refresh', 'Refresh this remote control device', 'refresh-device');
17321
- refreshButton.style.height = '30px';
17438
+ refreshButton.style.height = '26px';
17322
17439
  actions.appendChild(refreshButton);
17323
17440
  if (connected && thumbnailEnabled) {
17324
17441
  const shotButton = createRemoteFleetButton('Shot', 'Request thumbnail', 'thumbnail-device');
17325
17442
  shotButton.dataset.deviceId = deviceId;
17326
- shotButton.style.height = '30px';
17443
+ shotButton.style.height = '26px';
17327
17444
  actions.appendChild(shotButton);
17328
17445
  }
17329
17446
  if (connected && liveStreamEnabled) {
17330
17447
  const liveButton = createRemoteFleetButton(liveActive ? 'Stop' : 'Live', liveActive ? 'Stop live stream' : 'Start live stream', liveActive ? 'live-stop' : 'live-start');
17331
17448
  liveButton.dataset.deviceId = deviceId;
17332
17449
  liveButton.dataset.streamId = liveStreamId;
17333
- liveButton.style.height = '30px';
17450
+ liveButton.style.height = '26px';
17334
17451
  if (liveActive) {
17335
17452
  liveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
17336
17453
  liveButton.style.color = '#b91c1c';
17337
17454
  }
17338
17455
  actions.appendChild(liveButton);
17339
17456
  }
17457
+ let taskToggleButton = null;
17340
17458
  if (connected && taskEnabled) {
17341
- const taskButton = createRemoteFleetButton(aiAssistEnabled ? 'Task/AI' : 'Task', 'Dispatch task to this device', 'task-device');
17342
- taskButton.dataset.deviceId = deviceId;
17343
- taskButton.style.height = '30px';
17344
- actions.appendChild(taskButton);
17459
+ taskToggleButton = createRemoteFleetButton(aiAssistEnabled ? 'Task/AI' : 'Task', 'Open task input for this device', 'task-open');
17460
+ taskToggleButton.dataset.deviceId = deviceId;
17461
+ taskToggleButton.style.height = '26px';
17462
+ actions.appendChild(taskToggleButton);
17345
17463
  }
17346
17464
  if (connected) {
17347
17465
  const pingButton = createRemoteFleetButton('Ping', 'Ping device', 'ping-device');
17348
17466
  pingButton.dataset.deviceId = deviceId;
17349
- pingButton.style.height = '30px';
17467
+ pingButton.style.height = '26px';
17350
17468
  actions.appendChild(pingButton);
17351
17469
  }
17470
+
17471
+ const taskPanel = document.createElement('div');
17472
+ taskPanel.dataset.remoteFleetDeviceTaskPanel = 'true';
17473
+ taskPanel.style.cssText = `
17474
+ position: absolute;
17475
+ right: 8px;
17476
+ top: 42px;
17477
+ z-index: 6;
17478
+ width: min(360px, calc(100% - 16px));
17479
+ display: none;
17480
+ grid-template-columns: minmax(0, 1fr) auto auto;
17481
+ gap: 6px;
17482
+ align-items: stretch;
17483
+ padding: 7px;
17484
+ border-radius: 8px;
17485
+ border: 1px solid rgba(148, 163, 184, 0.30);
17486
+ background: rgba(255, 255, 255, 0.97);
17487
+ box-shadow: 0 16px 32px rgba(15, 23, 42, 0.18);
17488
+ pointer-events: auto;
17489
+ `;
17490
+ const taskInput = document.createElement('textarea');
17491
+ taskInput.dataset.remoteFleetDeviceTaskInput = 'true';
17492
+ taskInput.placeholder = 'Task for this computer';
17493
+ taskInput.rows = 2;
17494
+ taskInput.style.cssText = `
17495
+ width: 100%;
17496
+ min-width: 0;
17497
+ height: 42px;
17498
+ resize: none;
17499
+ border-radius: 7px;
17500
+ border: 1px solid rgba(148, 163, 184, 0.34);
17501
+ background: rgba(255, 255, 255, 0.94);
17502
+ color: #0f172a;
17503
+ padding: 6px 8px;
17504
+ font-size: 11px;
17505
+ line-height: 1.3;
17506
+ font-weight: 700;
17507
+ letter-spacing: 0;
17508
+ outline: none;
17509
+ pointer-events: auto;
17510
+ `;
17511
+ taskPanel.appendChild(taskInput);
17352
17512
  const aiToggleLabel = document.createElement('label');
17353
17513
  aiToggleLabel.title = 'Use AI assist when this agent exposes it';
17354
17514
  aiToggleLabel.style.cssText = `
@@ -17356,7 +17516,7 @@
17356
17516
  align-items: center;
17357
17517
  justify-content: center;
17358
17518
  gap: 6px;
17359
- height: 30px;
17519
+ height: 42px;
17360
17520
  padding: 0 9px;
17361
17521
  border-radius: 7px;
17362
17522
  border: 1px solid rgba(14, 165, 233, 0.28);
@@ -17377,37 +17537,73 @@
17377
17537
  aiToggleText.textContent = 'AI';
17378
17538
  aiToggleLabel.appendChild(aiToggle);
17379
17539
  aiToggleLabel.appendChild(aiToggleText);
17380
- actions.appendChild(aiToggleLabel);
17381
- bodyView.appendChild(actions);
17540
+ taskPanel.appendChild(aiToggleLabel);
17541
+ const taskSendButton = createRemoteFleetButton('Send', 'Dispatch task to this device', 'task-device');
17542
+ taskSendButton.dataset.deviceId = deviceId;
17543
+ taskSendButton.style.height = '42px';
17544
+ taskPanel.appendChild(taskSendButton);
17545
+ bodyView.appendChild(taskPanel);
17382
17546
  bodyView.appendChild(feedback);
17383
17547
 
17384
- const footer = document.createElement('div');
17385
- footer.textContent = `Endpoint ${endpoint} - ${deviceId} - refreshed ${formatRemoteFleetAge(refreshedAt)}`;
17386
- footer.title = footer.textContent;
17387
- footer.style.cssText = `
17388
- margin-top: auto;
17548
+ const statusBar = document.createElement('div');
17549
+ statusBar.dataset.remoteFleetDeviceStatusBar = 'true';
17550
+ const latestTaskText = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || '';
17551
+ const statusItems = [
17552
+ `${connected ? 'Online' : 'Offline'}`,
17553
+ `Seen ${formatRemoteFleetAge(getRemoteFleetDeviceField(device, 'lastSeenAt', 'LastSeenAt', ''))}`,
17554
+ `Uptime ${formatRemoteFleetDuration(getRemoteFleetDeviceField(device, 'uptimeSec', 'UptimeSec', Number.NaN))}`,
17555
+ `Mem ${formatRemoteFleetPercent(getRemoteFleetDeviceField(device, 'usedMemRatio', 'UsedMemRatio', Number.NaN))}`,
17556
+ `Load ${formatRemoteFleetNumber(getRemoteFleetDeviceField(device, 'load1', 'Load1', Number.NaN), 2)}`,
17557
+ release ? `${platform} ${release}` : platform,
17558
+ latestTaskStatus ? `Task ${latestTaskStatus}${latestTaskUpdatedAt ? ` ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}` : '',
17559
+ latestTaskText ? latestTaskText : ''
17560
+ ].filter(text => String(text || '').trim());
17561
+ statusBar.textContent = statusItems.join(' | ');
17562
+ statusBar.title = `${name} (${deviceId}) - ${statusBar.textContent}${latestTaskResultModel ? ` - ${latestTaskResultModel}` : ''}${latestTaskResultResponseId ? ` - ${latestTaskResultResponseId}` : ''}`;
17563
+ statusBar.style.cssText = `
17389
17564
  flex: 0 0 auto;
17390
- color: #64748b;
17391
- font-size: 10px;
17565
+ min-height: 22px;
17566
+ display: flex;
17567
+ align-items: center;
17568
+ min-width: 0;
17569
+ padding: 0 6px;
17570
+ border-radius: 0;
17571
+ border: 1px solid rgba(148, 163, 184, 0.18);
17572
+ background: rgba(248, 250, 252, 0.92);
17573
+ color: #334155;
17574
+ font-size: 10.5px;
17392
17575
  font-weight: 750;
17393
- line-height: 1.2;
17576
+ line-height: 1;
17394
17577
  overflow: hidden;
17395
17578
  text-overflow: ellipsis;
17396
17579
  white-space: nowrap;
17580
+ letter-spacing: 0;
17397
17581
  `;
17398
- bodyView.appendChild(footer);
17582
+ bodyView.appendChild(statusBar);
17399
17583
 
17400
17584
  if (lastError.trim()) {
17401
17585
  setFeedback(lastError, 'error');
17402
17586
  }
17403
17587
 
17404
- [taskInput, aiToggleLabel, aiToggle, ...Array.from(actions.querySelectorAll('button'))].forEach(control => {
17588
+ [taskPanel, taskInput, aiToggleLabel, aiToggle, ...Array.from(actions.querySelectorAll('button')), taskSendButton].forEach(control => {
17405
17589
  if (!control) return;
17406
17590
  ['mousedown', 'mouseup', 'click', 'dblclick', 'keydown'].forEach(eventName => {
17407
17591
  control.addEventListener(eventName, event => event.stopPropagation());
17408
17592
  });
17409
17593
  });
17410
17594
 
17595
+ if (taskToggleButton) {
17596
+ taskToggleButton.addEventListener('click', event => {
17597
+ event.preventDefault();
17598
+ event.stopPropagation();
17599
+ const nextVisible = taskPanel.style.display === 'none';
17600
+ taskPanel.style.display = nextVisible ? 'grid' : 'none';
17601
+ if (nextVisible) {
17602
+ taskInput.focus({ preventScroll: true });
17603
+ }
17604
+ });
17605
+ }
17606
+
17411
17607
  refreshButton.addEventListener('click', async event => {
17412
17608
  event.preventDefault();
17413
17609
  event.stopPropagation();
@@ -17483,7 +17679,7 @@
17483
17679
  });
17484
17680
  });
17485
17681
 
17486
- actions.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
17682
+ bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
17487
17683
  button.addEventListener('click', async event => {
17488
17684
  event.preventDefault();
17489
17685
  event.stopPropagation();
@@ -17526,6 +17722,7 @@
17526
17722
  function renderRemoteFleetMonitor(bodyView, nodeModel) {
17527
17723
  if (!bodyView) return;
17528
17724
  clearRemoteFleetTimers(bodyView, { keepBinaryFrameSocket: true });
17725
+ const preservedFrameSurfaces = collectRemoteFleetPreviewFrameSurfaces(bodyView);
17529
17726
 
17530
17727
  const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
17531
17728
  const searchState = bodyView.dataset.remoteFleetSearch || '';
@@ -18080,6 +18277,7 @@
18080
18277
  preview.appendChild(placeholder);
18081
18278
  }
18082
18279
 
18280
+ restoreRemoteFleetPreviewFrameSurface(preview, preservedFrameSurfaces, getRemoteFleetDeviceId(device));
18083
18281
  return preview;
18084
18282
  };
18085
18283
 
@@ -19290,7 +19488,7 @@
19290
19488
  }
19291
19489
 
19292
19490
  function createMemoNodeElement(nodeModel) {
19293
- if (isRemoteFleetMonitorNode(nodeModel)) {
19491
+ if (isRemoteFleetNode(nodeModel)) {
19294
19492
  return createRemoteFleetTemplateNodeElement(nodeModel);
19295
19493
  }
19296
19494
 
@@ -22480,7 +22678,7 @@
22480
22678
  dividerEl.style.display = prompt ? (isLoading || hasContent ? 'block' : 'none') : 'none';
22481
22679
  }
22482
22680
 
22483
- if (isRemoteFleetMonitorNode(nodeModel)) {
22681
+ if (isRemoteFleetNode(nodeModel)) {
22484
22682
  el.classList.remove('node-type-memo', 'has-context-source-pins');
22485
22683
  el.classList.add('node-type-templatelauncher');
22486
22684
  el.dataset.contentType = 'templateLauncher';
@@ -22507,6 +22705,7 @@
22507
22705
 
22508
22706
  card.dataset.nodeId = nodeId;
22509
22707
  card.dataset.semanticType = getNodeSemanticType(nodeModel);
22708
+ card.classList.toggle('map-node-remote-device', isRemoteFleetDeviceNode(nodeModel));
22510
22709
  card.style.width = `${worldWidth}px`;
22511
22710
  card.style.height = `${worldHeight}px`;
22512
22711
  card.style.minWidth = `${worldWidth}px`;
@@ -22525,7 +22724,11 @@
22525
22724
  }
22526
22725
 
22527
22726
  const bodyView = card.querySelector('.template-card__remote-fleet-body');
22528
- renderRemoteFleetMonitor(bodyView, nodeModel);
22727
+ if (isRemoteFleetDeviceNode(nodeModel)) {
22728
+ renderRemoteFleetDevice(bodyView, nodeModel);
22729
+ } else {
22730
+ renderRemoteFleetMonitor(bodyView, nodeModel);
22731
+ }
22529
22732
  bindTemplateCardCanvasNavigation(card, nodeModel);
22530
22733
  return;
22531
22734
  }
@@ -382,7 +382,8 @@
382
382
  const contentType = getNodeContentType(model);
383
383
  return isTemplateLauncherModel(model) ||
384
384
  contentType === 'csv-table' ||
385
- isBusinessAutomationModel(model);
385
+ isBusinessAutomationModel(model) ||
386
+ isRemoteFleetNodeModel(model);
386
387
  }
387
388
 
388
389
  function isRemoteFleetNodeModel(model) {
@@ -43,7 +43,10 @@
43
43
  }
44
44
 
45
45
  function isTemplateLauncherNodeModel(nodeModel) {
46
- return getNodeContentTypeLower(nodeModel) === 'templatelauncher';
46
+ const semanticType = getNodeSemanticType(nodeModel);
47
+ return getNodeContentTypeLower(nodeModel) === 'templatelauncher'
48
+ || semanticType === REMOTE_FLEET_SEMANTIC_TYPE
49
+ || semanticType === REMOTE_FLEET_DEVICE_SEMANTIC_TYPE;
47
50
  }
48
51
 
49
52
  function getNodeMetadata(nodeModel) {
@@ -103,8 +106,7 @@
103
106
  const semanticType = getNodeSemanticType(nodeModel);
104
107
  return semanticType === 'MindCanvasAgent'
105
108
  || semanticType === 'AgentCommand'
106
- || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE
107
- || semanticType === REMOTE_FLEET_DEVICE_SEMANTIC_TYPE;
109
+ || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE;
108
110
  }
109
111
 
110
112
  function isAgentNodeModel(nodeModel) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-oN9NB5XxbZ2xSDkf/iKkLdth/StnRySGcRVgEHblI6U=",
4
+ "hash": "sha256-fuZfmnPbFwdls7P4hSQJc1lN5QGs/DTxCqcNmFrIMjs=",
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.3zom09ir6j.dll": "MindExecution.Kernel.dll",
128
128
  "MindExecution.Plugins.Admin.bk23zzy6wc.dll": "MindExecution.Plugins.Admin.dll",
129
129
  "MindExecution.Plugins.Business.zwcvc8wufe.dll": "MindExecution.Plugins.Business.dll",
130
- "MindExecution.Plugins.Concept.4ncya2e794.dll": "MindExecution.Plugins.Concept.dll",
130
+ "MindExecution.Plugins.Concept.9igh5dsuw9.dll": "MindExecution.Plugins.Concept.dll",
131
131
  "MindExecution.Plugins.Directory.jc0g7fvyzv.dll": "MindExecution.Plugins.Directory.dll",
132
- "MindExecution.Plugins.PlanMaster.0yfii1nh3p.dll": "MindExecution.Plugins.PlanMaster.dll",
133
- "MindExecution.Plugins.YouTube.y4vxx807fo.dll": "MindExecution.Plugins.YouTube.dll",
134
- "MindExecution.Shared.50ptsgk9eg.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.hucq2w2ism.dll": "MindExecution.Web.dll",
132
+ "MindExecution.Plugins.PlanMaster.g9r2lbhihg.dll": "MindExecution.Plugins.PlanMaster.dll",
133
+ "MindExecution.Plugins.YouTube.6ni889qas8.dll": "MindExecution.Plugins.YouTube.dll",
134
+ "MindExecution.Shared.xtnh42yedc.dll": "MindExecution.Shared.dll",
135
+ "MindExecution.Web.cg3mse80dr.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.asayb4hw1o.dll": "sha256-5gla4JTR/zbvkGpneSt6S7ItE51+MjWgjW6L+B6Yevk=",
282
282
  "MindExecution.Kernel.3zom09ir6j.dll": "sha256-78vR2SNkF9FwdQv0fEItSyz4HzgyzwGUROJAnpripPA=",
283
- "MindExecution.Plugins.Concept.4ncya2e794.dll": "sha256-gKad12tQh1p7kInUiSIECobEZiD4VIuxmmskGE3ambo=",
284
- "MindExecution.Plugins.PlanMaster.0yfii1nh3p.dll": "sha256-OMcIYPKBnnJCW6QRN8nGykgCloUl1FRnFx6lma85ZGs=",
285
- "MindExecution.Shared.50ptsgk9eg.dll": "sha256-1X+o7c7jCm4MVorCrq52beiyMe+C6qFw6EyIGfZGQdo=",
286
- "MindExecution.Web.hucq2w2ism.dll": "sha256-iSV90XAYUoYq7Rfm7gf4tzJBsIy2zbfVfxadAFS8pSY="
283
+ "MindExecution.Plugins.Concept.9igh5dsuw9.dll": "sha256-L9kDRKc/l7qDyrpHv3nP2eZJFl3hiTKzobGq+1Ir1u4=",
284
+ "MindExecution.Plugins.PlanMaster.g9r2lbhihg.dll": "sha256-hMdypae7YThv34t4g0TL/iI1GF/vQsZVQgEehe1AXt8=",
285
+ "MindExecution.Shared.xtnh42yedc.dll": "sha256-GwFDvUKSi/L8U9L2QsvOVjOOZYAaQInM5CLVYhD0BFk=",
286
+ "MindExecution.Web.cg3mse80dr.dll": "sha256-BDF8hVymVem3X/dB9iKv2S6Vohz7y9CKSYMQOattgaA="
287
287
  },
288
288
  "lazyAssembly": {
289
289
  "MindExecution.Plugins.Admin.bk23zzy6wc.dll": "sha256-uz7Nz0OZ8BBESp2Nr9YocyzV0dUrn7szPuD4/m/RWL8=",
290
290
  "MindExecution.Plugins.Business.zwcvc8wufe.dll": "sha256-3YDsB2/uNI+feWqJk+cesLHgNt8s2/8hXxGPO6CRZZE=",
291
291
  "MindExecution.Plugins.Directory.jc0g7fvyzv.dll": "sha256-PSe7135JPpTBmu4j+rGLF0Dc/6w/wDJNPi8zlfTmSMk=",
292
- "MindExecution.Plugins.YouTube.y4vxx807fo.dll": "sha256-HkMC8BltV070e0Uaskpmyu6+TIs04y0G2t4E62RJdcc="
292
+ "MindExecution.Plugins.YouTube.6ni889qas8.dll": "sha256-ngO7cBhZwzW2Sx9TqcTZj/3TW3lOkcluXr76P2oH9X8="
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=20260617-mdm-tab-fast-v591" />
11
- <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260617-mdm-tab-fast-v591" />
10
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260617-remote-control-screen-v593" />
11
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260617-remote-control-screen-v593" />
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 = '20260617-mdm-tab-fast-v591';
582
+ const scriptVersion = '20260617-remote-control-screen-v593';
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": "2guH6APA",
2
+ "version": "xi510uSd",
3
3
  "assets": [
4
4
  {
5
5
  "hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
@@ -42,7 +42,7 @@
42
42
  "url": "_content/MindExecution.Shared/css/app.css"
43
43
  },
44
44
  {
45
- "hash": "sha256-FxtluumoQV2QXTCFtSR+/dc0Xp/CFDcF1cXkaeikt4s=",
45
+ "hash": "sha256-Qe8ZasFVu0/cCvryv6hkXRbNvdEQFO8yqiIOKN5w4G4=",
46
46
  "url": "_content/MindExecution.Shared/css/mind-map-overrides.css"
47
47
  },
48
48
  {
@@ -86,7 +86,7 @@
86
86
  "url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
87
87
  },
88
88
  {
89
- "hash": "sha256-piKsRYcnSTaqs+vd7QATTt7cGBN5hHPbY6P8Xjl2J6k=",
89
+ "hash": "sha256-5NeWmkeG5tE20WMmpOO59SkGuCFv+yk/h6kOeYeOmJA=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -114,7 +114,7 @@
114
114
  "url": "_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js"
115
115
  },
116
116
  {
117
- "hash": "sha256-Lo7v8nqlOhYydM8OzuMLHllA29+Q76y/PIV+QaB1uyo=",
117
+ "hash": "sha256-pI3Kf/v2vRRR1SMDKCjMZcLtR8ACg7XAROlgwHBGg50=",
118
118
  "url": "_content/MindExecution.Shared/js/mind-map-lod-renderer.js"
119
119
  },
120
120
  {
@@ -134,7 +134,7 @@
134
134
  "url": "_content/MindExecution.Shared/js/mind-map-node-search-worker.js"
135
135
  },
136
136
  {
137
- "hash": "sha256-Du/v38HmdprkhvETqiO0mx3Ez3rYmEvjQnDMIQclta0=",
137
+ "hash": "sha256-e1IsTR6acAA6OdptYbIJWTS3km0IJGh/VYBU75rDMXU=",
138
138
  "url": "_content/MindExecution.Shared/js/mind-map-nodes.js"
139
139
  },
140
140
  {
@@ -426,28 +426,28 @@
426
426
  "url": "_framework/MindExecution.Plugins.Business.zwcvc8wufe.dll"
427
427
  },
428
428
  {
429
- "hash": "sha256-gKad12tQh1p7kInUiSIECobEZiD4VIuxmmskGE3ambo=",
430
- "url": "_framework/MindExecution.Plugins.Concept.4ncya2e794.dll"
429
+ "hash": "sha256-L9kDRKc/l7qDyrpHv3nP2eZJFl3hiTKzobGq+1Ir1u4=",
430
+ "url": "_framework/MindExecution.Plugins.Concept.9igh5dsuw9.dll"
431
431
  },
432
432
  {
433
433
  "hash": "sha256-PSe7135JPpTBmu4j+rGLF0Dc/6w/wDJNPi8zlfTmSMk=",
434
434
  "url": "_framework/MindExecution.Plugins.Directory.jc0g7fvyzv.dll"
435
435
  },
436
436
  {
437
- "hash": "sha256-OMcIYPKBnnJCW6QRN8nGykgCloUl1FRnFx6lma85ZGs=",
438
- "url": "_framework/MindExecution.Plugins.PlanMaster.0yfii1nh3p.dll"
437
+ "hash": "sha256-hMdypae7YThv34t4g0TL/iI1GF/vQsZVQgEehe1AXt8=",
438
+ "url": "_framework/MindExecution.Plugins.PlanMaster.g9r2lbhihg.dll"
439
439
  },
440
440
  {
441
- "hash": "sha256-HkMC8BltV070e0Uaskpmyu6+TIs04y0G2t4E62RJdcc=",
442
- "url": "_framework/MindExecution.Plugins.YouTube.y4vxx807fo.dll"
441
+ "hash": "sha256-ngO7cBhZwzW2Sx9TqcTZj/3TW3lOkcluXr76P2oH9X8=",
442
+ "url": "_framework/MindExecution.Plugins.YouTube.6ni889qas8.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-1X+o7c7jCm4MVorCrq52beiyMe+C6qFw6EyIGfZGQdo=",
446
- "url": "_framework/MindExecution.Shared.50ptsgk9eg.dll"
445
+ "hash": "sha256-GwFDvUKSi/L8U9L2QsvOVjOOZYAaQInM5CLVYhD0BFk=",
446
+ "url": "_framework/MindExecution.Shared.xtnh42yedc.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-iSV90XAYUoYq7Rfm7gf4tzJBsIy2zbfVfxadAFS8pSY=",
450
- "url": "_framework/MindExecution.Web.hucq2w2ism.dll"
449
+ "hash": "sha256-BDF8hVymVem3X/dB9iKv2S6Vohz7y9CKSYMQOattgaA=",
450
+ "url": "_framework/MindExecution.Web.cg3mse80dr.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-l0xPjAbrLfbdmB69gqtRn+JMPssryiM0fh1uDxZ/NNo=",
773
+ "hash": "sha256-1T/+tduwHalERbmteG+ZOkOPvGnie3dWjxoK9p0myto=",
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-hq0qYVhXCK09hkKo9EkKj84gbxqFCuf2l4XAWk1gfL0=",
837
+ "hash": "sha256-tgf6hPsk0rfP/I8Wx88fu4cEJGVIRNrgAztKJfw35Ms=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: 2guH6APA */
1
+ /* Manifest version: xi510uSd */
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