@mindexec/cli 0.2.2 → 0.2.4

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.
Files changed (24) hide show
  1. package/README.md +50 -0
  2. package/package.json +6 -4
  3. package/remote-hub.js +737 -0
  4. package/scripts/remote-hub-smoke.mjs +146 -0
  5. package/server.js +144 -28
  6. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +11 -0
  7. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +732 -2
  8. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +3 -1
  9. package/wwwroot/_framework/MindExecution.Core.rydw4mhsbd.dll +0 -0
  10. package/wwwroot/_framework/{MindExecution.Kernel.gwwc40sc45.dll → MindExecution.Kernel.8sz1fl3k6s.dll} +0 -0
  11. package/wwwroot/_framework/{MindExecution.Plugins.Admin.0jgrn1sckv.dll → MindExecution.Plugins.Admin.iltai5c3i9.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Business.13mme2qcag.dll → MindExecution.Plugins.Business.mscgb1gwpf.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Concept.9al2g3v3f9.dll → MindExecution.Plugins.Concept.s888y8snr4.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Directory.3w4t6n3se0.dll → MindExecution.Plugins.Directory.281klijdzl.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.vfmfbygv5y.dll → MindExecution.Plugins.PlanMaster.2gy2ozelqp.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.32jyiqs383.dll → MindExecution.Plugins.YouTube.1v8o9nnlzq.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Shared.7ttmykvopx.dll → MindExecution.Shared.04anisxh35.dll} +0 -0
  18. package/wwwroot/_framework/MindExecution.Web.0qdidsf6sl.dll +0 -0
  19. package/wwwroot/_framework/blazor.boot.json +21 -21
  20. package/wwwroot/index.html +1 -1
  21. package/wwwroot/service-worker-assets.js +26 -26
  22. package/wwwroot/service-worker.js +1 -1
  23. package/wwwroot/_framework/MindExecution.Core.1q1trifbuu.dll +0 -0
  24. package/wwwroot/_framework/MindExecution.Web.ozzcqp30uy.dll +0 -0
@@ -3400,6 +3400,7 @@
3400
3400
  const MEDIA_GENERATION_STATUS_METADATA_KEY = 'MediaGenerationStatus';
3401
3401
  const MEDIA_GENERATION_STATUS_MESSAGE_METADATA_KEY = 'MediaGenerationStatusMessage';
3402
3402
  const BUSINESS_AUTOMATION_SEMANTIC_TYPE = 'BusinessAutomationNode';
3403
+ const REMOTE_FLEET_SEMANTIC_TYPE = 'RemoteFleetMonitor';
3403
3404
  const AUTOMATION_NODE_KIND_METADATA_KEY = 'AutomationNodeKind';
3404
3405
  const AUTOMATION_NODE_LABEL_METADATA_KEY = 'AutomationNodeLabel';
3405
3406
  const AUTOMATION_NODE_DESCRIPTION_METADATA_KEY = 'AutomationNodeDescription';
@@ -3569,6 +3570,10 @@
3569
3570
  return getNodeSemanticType(nodeModel) === BUSINESS_AUTOMATION_SEMANTIC_TYPE;
3570
3571
  }
3571
3572
 
3573
+ function isRemoteFleetMonitorNode(nodeModel) {
3574
+ return getNodeSemanticType(nodeModel) === REMOTE_FLEET_SEMANTIC_TYPE;
3575
+ }
3576
+
3572
3577
  function isBusinessAutomationContextSourceNode(nodeModel) {
3573
3578
  if (!nodeModel || isBusinessAutomationNode(nodeModel)) {
3574
3579
  return false;
@@ -3582,7 +3587,8 @@
3582
3587
  const semanticType = getNodeSemanticType(nodeModel);
3583
3588
  return semanticType === 'MindCanvasAgent'
3584
3589
  || semanticType === 'AgentCommand'
3585
- || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE;
3590
+ || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE
3591
+ || semanticType === REMOTE_FLEET_SEMANTIC_TYPE;
3586
3592
  }
3587
3593
 
3588
3594
  function allowsMemoExternalChrome(nodeModel) {
@@ -11834,6 +11840,18 @@
11834
11840
  function renderMemoBodyView(bodyView, nodeModel, content) {
11835
11841
  if (!bodyView) return;
11836
11842
 
11843
+ if (isRemoteFleetMonitorNode(nodeModel)) {
11844
+ renderRemoteFleetMonitor(bodyView, nodeModel);
11845
+ return;
11846
+ }
11847
+
11848
+ bodyView.classList.remove('map-node-remote-fleet__body');
11849
+ bodyView.style.cssText = `
11850
+ flex: 1 1 auto;
11851
+ min-height: 0;
11852
+ pointer-events: auto;
11853
+ `;
11854
+
11837
11855
  const nextValue = String(content || '');
11838
11856
  bodyView.dataset.src = nextValue;
11839
11857
 
@@ -11916,6 +11934,715 @@
11916
11934
  }, 220);
11917
11935
  }
11918
11936
 
11937
+ function getRemoteFleetMetadataValue(nodeModel, key, fallback = '') {
11938
+ const metadata = getNodeMetadata(nodeModel) || {};
11939
+ const value = metadata[key];
11940
+ return value === undefined || value === null ? fallback : String(value);
11941
+ }
11942
+
11943
+ function parseRemoteFleetDevices(nodeModel) {
11944
+ const raw = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetDevicesJson', '[]');
11945
+ if (!raw.trim()) {
11946
+ return [];
11947
+ }
11948
+
11949
+ try {
11950
+ const parsed = JSON.parse(raw);
11951
+ return Array.isArray(parsed) ? parsed : [];
11952
+ } catch {
11953
+ return [];
11954
+ }
11955
+ }
11956
+
11957
+ function formatRemoteFleetNumber(value, digits = 0) {
11958
+ const number = Number(value);
11959
+ return Number.isFinite(number) ? number.toFixed(digits) : '-';
11960
+ }
11961
+
11962
+ function formatRemoteFleetPercent(value) {
11963
+ const number = Number(value);
11964
+ if (!Number.isFinite(number)) {
11965
+ return '-';
11966
+ }
11967
+
11968
+ return `${Math.round(Math.max(0, Math.min(1, number)) * 100)}%`;
11969
+ }
11970
+
11971
+ function formatRemoteFleetDuration(seconds) {
11972
+ const value = Number(seconds);
11973
+ if (!Number.isFinite(value) || value <= 0) {
11974
+ return '-';
11975
+ }
11976
+
11977
+ const days = Math.floor(value / 86400);
11978
+ const hours = Math.floor((value % 86400) / 3600);
11979
+ const minutes = Math.floor((value % 3600) / 60);
11980
+ if (days > 0) {
11981
+ return `${days}d ${hours}h`;
11982
+ }
11983
+ if (hours > 0) {
11984
+ return `${hours}h ${minutes}m`;
11985
+ }
11986
+ return `${minutes}m`;
11987
+ }
11988
+
11989
+ function formatRemoteFleetAge(value) {
11990
+ const timestamp = Date.parse(String(value || ''));
11991
+ if (!Number.isFinite(timestamp)) {
11992
+ return '-';
11993
+ }
11994
+
11995
+ const seconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000));
11996
+ if (seconds < 60) {
11997
+ return 'now';
11998
+ }
11999
+ if (seconds < 3600) {
12000
+ return `${Math.floor(seconds / 60)}m ago`;
12001
+ }
12002
+ if (seconds < 86400) {
12003
+ return `${Math.floor(seconds / 3600)}h ago`;
12004
+ }
12005
+ return `${Math.floor(seconds / 86400)}d ago`;
12006
+ }
12007
+
12008
+ function createRemoteFleetStat(label, value, tone = 'default') {
12009
+ const item = document.createElement('div');
12010
+ item.style.cssText = `
12011
+ display: flex;
12012
+ flex-direction: column;
12013
+ gap: 2px;
12014
+ min-width: 0;
12015
+ padding: 8px 10px;
12016
+ border-radius: 8px;
12017
+ background: ${tone === 'online' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(15, 23, 42, 0.05)'};
12018
+ border: 1px solid ${tone === 'online' ? 'rgba(16, 185, 129, 0.22)' : 'rgba(148, 163, 184, 0.22)'};
12019
+ `;
12020
+
12021
+ const labelEl = document.createElement('span');
12022
+ labelEl.textContent = label;
12023
+ labelEl.style.cssText = `
12024
+ color: #64748b;
12025
+ font-size: 10px;
12026
+ font-weight: 800;
12027
+ letter-spacing: 0;
12028
+ text-transform: uppercase;
12029
+ `;
12030
+
12031
+ const valueEl = document.createElement('strong');
12032
+ valueEl.textContent = value;
12033
+ valueEl.style.cssText = `
12034
+ color: ${tone === 'online' ? '#047857' : '#0f172a'};
12035
+ font-size: 18px;
12036
+ line-height: 1.1;
12037
+ font-weight: 900;
12038
+ letter-spacing: 0;
12039
+ overflow: hidden;
12040
+ text-overflow: ellipsis;
12041
+ white-space: nowrap;
12042
+ `;
12043
+
12044
+ item.appendChild(labelEl);
12045
+ item.appendChild(valueEl);
12046
+ return item;
12047
+ }
12048
+
12049
+ function createRemoteFleetButton(label, title, action) {
12050
+ const button = document.createElement('button');
12051
+ button.type = 'button';
12052
+ button.dataset.remoteFleetAction = action;
12053
+ button.textContent = label;
12054
+ button.title = title || label;
12055
+ button.setAttribute('aria-label', title || label);
12056
+ button.style.cssText = `
12057
+ display: inline-flex;
12058
+ align-items: center;
12059
+ justify-content: center;
12060
+ min-width: 0;
12061
+ height: 28px;
12062
+ padding: 0 10px;
12063
+ border-radius: 7px;
12064
+ border: 1px solid rgba(37, 99, 235, 0.32);
12065
+ background: #ffffff;
12066
+ color: #1d4ed8;
12067
+ font-size: 11px;
12068
+ font-weight: 800;
12069
+ letter-spacing: 0;
12070
+ cursor: pointer;
12071
+ pointer-events: auto;
12072
+ `;
12073
+ return button;
12074
+ }
12075
+
12076
+ async function syncRemoteFleetNodeStateFromResult(result) {
12077
+ const nodeState = result?.nodeState || result?.refresh?.nodeState;
12078
+ if (nodeState && window.mindMap?.syncNodeStates) {
12079
+ await window.mindMap.syncNodeStates([nodeState]);
12080
+ }
12081
+ }
12082
+
12083
+ function renderRemoteFleetMonitor(bodyView, nodeModel) {
12084
+ if (!bodyView) return;
12085
+
12086
+ const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
12087
+ const devices = parseRemoteFleetDevices(nodeModel);
12088
+ const total = Number(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetDeviceCount', devices.length));
12089
+ const connected = Number(getRemoteFleetMetadataValue(
12090
+ nodeModel,
12091
+ 'RemoteFleetConnectedDeviceCount',
12092
+ devices.filter(device => device?.connected === true || device?.Connected === true).length));
12093
+ const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
12094
+ const command = getRemoteFleetMetadataValue(
12095
+ nodeModel,
12096
+ 'RemoteFleetConnectCommand',
12097
+ `npx @mindexec/remote connect --manager ${endpoint} --pair <pair-token>`);
12098
+ const hubStatus = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubStatus', 'offline');
12099
+ const refreshedAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastRefreshAtUtc', '');
12100
+ const lastError = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastError', '');
12101
+
12102
+ bodyView.dataset.src = `remote-fleet:${refreshedAt}:${devices.length}:${connected}`;
12103
+ bodyView.classList.add('map-node-remote-fleet__body');
12104
+ bodyView.innerHTML = '';
12105
+ bodyView.style.cssText = `
12106
+ flex: 1 1 auto;
12107
+ min-height: 0;
12108
+ pointer-events: auto;
12109
+ display: flex;
12110
+ flex-direction: column;
12111
+ gap: 10px;
12112
+ padding: 12px 14px 14px;
12113
+ overflow: hidden;
12114
+ background: linear-gradient(180deg, rgba(248, 250, 252, 0.96), rgba(241, 245, 249, 0.92));
12115
+ `;
12116
+
12117
+ const top = document.createElement('div');
12118
+ top.style.cssText = `
12119
+ display: grid;
12120
+ grid-template-columns: repeat(4, minmax(0, 1fr));
12121
+ gap: 8px;
12122
+ flex: 0 0 auto;
12123
+ `;
12124
+ top.appendChild(createRemoteFleetStat('Hub', hubStatus === 'online' ? 'Online' : 'Offline', hubStatus === 'online' ? 'online' : 'default'));
12125
+ top.appendChild(createRemoteFleetStat('Connected', `${Number.isFinite(connected) ? connected : 0}/${Number.isFinite(total) ? total : devices.length}`, connected > 0 ? 'online' : 'default'));
12126
+ top.appendChild(createRemoteFleetStat('Mode', 'All', 'default'));
12127
+ top.appendChild(createRemoteFleetStat('Paging', 'None', 'default'));
12128
+ bodyView.appendChild(top);
12129
+
12130
+ const commandRow = document.createElement('div');
12131
+ commandRow.style.cssText = `
12132
+ display: grid;
12133
+ grid-template-columns: minmax(0, 1fr) auto auto;
12134
+ gap: 8px;
12135
+ align-items: center;
12136
+ flex: 0 0 auto;
12137
+ `;
12138
+
12139
+ const commandText = document.createElement('code');
12140
+ commandText.textContent = command;
12141
+ commandText.style.cssText = `
12142
+ min-width: 0;
12143
+ overflow: hidden;
12144
+ text-overflow: ellipsis;
12145
+ white-space: nowrap;
12146
+ padding: 8px 10px;
12147
+ border-radius: 7px;
12148
+ background: rgba(15, 23, 42, 0.92);
12149
+ color: #e2e8f0;
12150
+ font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
12151
+ font-size: 11px;
12152
+ line-height: 1.2;
12153
+ `;
12154
+ commandText.title = command;
12155
+
12156
+ const copyButton = createRemoteFleetButton('Copy', 'Copy agent command', 'copy-command');
12157
+ const refreshButton = createRemoteFleetButton('Refresh', 'Refresh remote devices', 'refresh');
12158
+ commandRow.appendChild(commandText);
12159
+ commandRow.appendChild(copyButton);
12160
+ commandRow.appendChild(refreshButton);
12161
+ bodyView.appendChild(commandRow);
12162
+
12163
+ const taskRow = document.createElement('div');
12164
+ taskRow.style.cssText = `
12165
+ display: grid;
12166
+ grid-template-columns: minmax(0, 1fr) auto;
12167
+ gap: 8px;
12168
+ align-items: stretch;
12169
+ flex: 0 0 auto;
12170
+ `;
12171
+ const taskInput = document.createElement('textarea');
12172
+ taskInput.dataset.remoteFleetTaskInput = 'true';
12173
+ taskInput.placeholder = 'Task for remote agents';
12174
+ taskInput.rows = 2;
12175
+ taskInput.style.cssText = `
12176
+ width: 100%;
12177
+ min-width: 0;
12178
+ height: 54px;
12179
+ resize: none;
12180
+ border-radius: 7px;
12181
+ border: 1px solid rgba(148, 163, 184, 0.34);
12182
+ background: rgba(255, 255, 255, 0.92);
12183
+ color: #0f172a;
12184
+ padding: 8px 10px;
12185
+ font-size: 12px;
12186
+ line-height: 1.35;
12187
+ font-weight: 700;
12188
+ letter-spacing: 0;
12189
+ outline: none;
12190
+ pointer-events: auto;
12191
+ `;
12192
+ const sendConnectedButton = createRemoteFleetButton('Send connected', 'Dispatch task to all connected task-capable devices', 'task-connected');
12193
+ sendConnectedButton.style.height = '54px';
12194
+ taskRow.appendChild(taskInput);
12195
+ taskRow.appendChild(sendConnectedButton);
12196
+ bodyView.appendChild(taskRow);
12197
+
12198
+ const taskFeedback = document.createElement('div');
12199
+ taskFeedback.dataset.remoteFleetTaskFeedback = 'true';
12200
+ taskFeedback.style.cssText = `
12201
+ display: none;
12202
+ flex: 0 0 auto;
12203
+ min-height: 22px;
12204
+ padding: 5px 8px;
12205
+ border-radius: 7px;
12206
+ background: rgba(37, 99, 235, 0.08);
12207
+ color: #1d4ed8;
12208
+ font-size: 11px;
12209
+ font-weight: 800;
12210
+ line-height: 1.25;
12211
+ overflow: hidden;
12212
+ text-overflow: ellipsis;
12213
+ white-space: nowrap;
12214
+ `;
12215
+ bodyView.appendChild(taskFeedback);
12216
+
12217
+ if (lastError.trim()) {
12218
+ const errorEl = document.createElement('div');
12219
+ errorEl.textContent = lastError;
12220
+ errorEl.style.cssText = `
12221
+ flex: 0 0 auto;
12222
+ padding: 8px 10px;
12223
+ border-radius: 7px;
12224
+ background: rgba(248, 113, 113, 0.12);
12225
+ color: #991b1b;
12226
+ font-size: 12px;
12227
+ line-height: 1.35;
12228
+ font-weight: 700;
12229
+ overflow-wrap: break-word;
12230
+ `;
12231
+ bodyView.appendChild(errorEl);
12232
+ }
12233
+
12234
+ const grid = document.createElement('div');
12235
+ grid.style.cssText = `
12236
+ flex: 1 1 auto;
12237
+ min-height: 0;
12238
+ overflow-y: auto;
12239
+ overflow-x: hidden;
12240
+ display: grid;
12241
+ grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
12242
+ align-content: start;
12243
+ gap: 8px;
12244
+ padding-right: 4px;
12245
+ `;
12246
+
12247
+ if (devices.length === 0) {
12248
+ const empty = document.createElement('div');
12249
+ empty.textContent = 'No devices connected yet.';
12250
+ empty.style.cssText = `
12251
+ grid-column: 1 / -1;
12252
+ display: flex;
12253
+ align-items: center;
12254
+ min-height: 94px;
12255
+ padding: 14px;
12256
+ border-radius: 8px;
12257
+ border: 1px dashed rgba(100, 116, 139, 0.36);
12258
+ color: #475569;
12259
+ font-size: 13px;
12260
+ font-weight: 800;
12261
+ background: rgba(255, 255, 255, 0.74);
12262
+ `;
12263
+ grid.appendChild(empty);
12264
+ } else {
12265
+ devices.forEach(device => {
12266
+ const connectedDevice = device?.connected === true || device?.Connected === true;
12267
+ const name = String(device?.name || device?.Name || device?.hostname || device?.Hostname || device?.deviceId || device?.DeviceId || 'device');
12268
+ const platform = [device?.platform || device?.Platform, device?.arch || device?.Arch]
12269
+ .filter(Boolean)
12270
+ .join(' / ') || 'unknown';
12271
+ const release = String(device?.release || device?.Release || '');
12272
+ const deviceId = String(device?.deviceId || device?.DeviceId || '');
12273
+ const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
12274
+ const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
12275
+ const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
12276
+ const hasThumbnail = /^data:image\/(png|jpe?g|webp|svg\+xml);base64,/i.test(thumbnailDataUrl);
12277
+ const taskEnabled = device?.computerAgentEnabled === true || device?.ComputerAgentEnabled === true;
12278
+ const latestTaskStatus = String(device?.latestTaskStatus || device?.LatestTaskStatus || '');
12279
+ const latestTaskTitle = String(device?.latestTaskTitle || device?.LatestTaskTitle || '');
12280
+ const latestTaskUpdatedAt = String(device?.latestTaskUpdatedAt || device?.LatestTaskUpdatedAt || '');
12281
+ const latestTaskError = String(device?.latestTaskError || device?.LatestTaskError || '');
12282
+ const latestTaskResult = String(device?.latestTaskResultSummary || device?.LatestTaskResultSummary || '');
12283
+ const card = document.createElement('article');
12284
+ card.dataset.deviceId = deviceId;
12285
+ card.style.cssText = `
12286
+ display: flex;
12287
+ flex-direction: column;
12288
+ gap: 8px;
12289
+ min-width: 0;
12290
+ min-height: 134px;
12291
+ padding: 10px;
12292
+ border-radius: 8px;
12293
+ background: #ffffff;
12294
+ border: 1px solid ${connectedDevice ? 'rgba(16, 185, 129, 0.34)' : 'rgba(148, 163, 184, 0.28)'};
12295
+ box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
12296
+ `;
12297
+
12298
+ const preview = document.createElement('div');
12299
+ preview.style.cssText = `
12300
+ position: relative;
12301
+ width: 100%;
12302
+ aspect-ratio: 16 / 9;
12303
+ overflow: hidden;
12304
+ border-radius: 7px;
12305
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
12306
+ border: 1px solid rgba(15, 23, 42, 0.12);
12307
+ `;
12308
+ if (hasThumbnail) {
12309
+ const image = document.createElement('img');
12310
+ image.src = thumbnailDataUrl;
12311
+ image.alt = `${name} thumbnail`;
12312
+ image.loading = 'lazy';
12313
+ image.decoding = 'async';
12314
+ image.style.cssText = `
12315
+ width: 100%;
12316
+ height: 100%;
12317
+ object-fit: cover;
12318
+ display: block;
12319
+ `;
12320
+ preview.appendChild(image);
12321
+ } else {
12322
+ const placeholder = document.createElement('div');
12323
+ placeholder.textContent = thumbnailEnabled ? 'No frame yet' : 'Status only';
12324
+ placeholder.style.cssText = `
12325
+ position: absolute;
12326
+ inset: 0;
12327
+ display: flex;
12328
+ align-items: center;
12329
+ justify-content: center;
12330
+ color: rgba(226, 232, 240, 0.78);
12331
+ font-size: 11px;
12332
+ font-weight: 900;
12333
+ letter-spacing: 0;
12334
+ `;
12335
+ preview.appendChild(placeholder);
12336
+ }
12337
+ if (thumbnailCapturedAt) {
12338
+ const badge = document.createElement('span');
12339
+ badge.textContent = formatRemoteFleetAge(thumbnailCapturedAt);
12340
+ badge.style.cssText = `
12341
+ position: absolute;
12342
+ right: 6px;
12343
+ bottom: 6px;
12344
+ max-width: calc(100% - 12px);
12345
+ padding: 3px 6px;
12346
+ border-radius: 999px;
12347
+ background: rgba(15, 23, 42, 0.74);
12348
+ color: #e2e8f0;
12349
+ font-size: 9px;
12350
+ font-weight: 900;
12351
+ line-height: 1;
12352
+ overflow: hidden;
12353
+ text-overflow: ellipsis;
12354
+ white-space: nowrap;
12355
+ `;
12356
+ preview.appendChild(badge);
12357
+ }
12358
+ card.appendChild(preview);
12359
+
12360
+ const cardHeader = document.createElement('div');
12361
+ cardHeader.style.cssText = 'display:flex;align-items:flex-start;gap:8px;min-width:0;';
12362
+ const dot = document.createElement('span');
12363
+ dot.style.cssText = `
12364
+ flex: 0 0 auto;
12365
+ width: 9px;
12366
+ height: 9px;
12367
+ margin-top: 4px;
12368
+ border-radius: 999px;
12369
+ background: ${connectedDevice ? '#10b981' : '#94a3b8'};
12370
+ box-shadow: 0 0 0 4px ${connectedDevice ? 'rgba(16,185,129,0.14)' : 'rgba(148,163,184,0.14)'};
12371
+ `;
12372
+
12373
+ const titleBox = document.createElement('div');
12374
+ titleBox.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:2px;';
12375
+ const title = document.createElement('strong');
12376
+ title.textContent = name;
12377
+ title.title = name;
12378
+ title.style.cssText = `
12379
+ color: #0f172a;
12380
+ font-size: 13px;
12381
+ font-weight: 900;
12382
+ line-height: 1.2;
12383
+ overflow: hidden;
12384
+ text-overflow: ellipsis;
12385
+ white-space: nowrap;
12386
+ letter-spacing: 0;
12387
+ `;
12388
+ const subtitle = document.createElement('span');
12389
+ subtitle.textContent = release ? `${platform} ${release}` : platform;
12390
+ subtitle.title = subtitle.textContent;
12391
+ subtitle.style.cssText = `
12392
+ color: #64748b;
12393
+ font-size: 11px;
12394
+ line-height: 1.2;
12395
+ overflow: hidden;
12396
+ text-overflow: ellipsis;
12397
+ white-space: nowrap;
12398
+ letter-spacing: 0;
12399
+ `;
12400
+ titleBox.appendChild(title);
12401
+ titleBox.appendChild(subtitle);
12402
+ cardHeader.appendChild(dot);
12403
+ cardHeader.appendChild(titleBox);
12404
+ card.appendChild(cardHeader);
12405
+
12406
+ const metrics = document.createElement('div');
12407
+ metrics.style.cssText = `
12408
+ display: grid;
12409
+ grid-template-columns: repeat(2, minmax(0, 1fr));
12410
+ gap: 6px;
12411
+ `;
12412
+ const addMetric = (label, value) => {
12413
+ const metric = document.createElement('div');
12414
+ metric.style.cssText = `
12415
+ min-width: 0;
12416
+ padding: 6px 7px;
12417
+ border-radius: 7px;
12418
+ background: rgba(241, 245, 249, 0.84);
12419
+ `;
12420
+ const labelEl = document.createElement('div');
12421
+ labelEl.textContent = label;
12422
+ labelEl.style.cssText = 'color:#64748b;font-size:9px;font-weight:800;letter-spacing:0;text-transform:uppercase;';
12423
+ const valueEl = document.createElement('div');
12424
+ valueEl.textContent = value;
12425
+ valueEl.title = value;
12426
+ valueEl.style.cssText = 'color:#0f172a;font-size:12px;font-weight:900;letter-spacing:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
12427
+ metric.appendChild(labelEl);
12428
+ metric.appendChild(valueEl);
12429
+ metrics.appendChild(metric);
12430
+ };
12431
+ addMetric('Seen', formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt));
12432
+ addMetric('Uptime', formatRemoteFleetDuration(device?.uptimeSec ?? device?.UptimeSec));
12433
+ addMetric('Mem', formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio));
12434
+ addMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
12435
+ card.appendChild(metrics);
12436
+
12437
+ if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
12438
+ const taskBox = document.createElement('div');
12439
+ const taskTone = latestTaskError || latestTaskStatus === 'failed'
12440
+ ? 'error'
12441
+ : (latestTaskStatus === 'completed' ? 'done' : 'pending');
12442
+ taskBox.style.cssText = `
12443
+ display: flex;
12444
+ flex-direction: column;
12445
+ gap: 3px;
12446
+ min-width: 0;
12447
+ padding: 7px 8px;
12448
+ border-radius: 7px;
12449
+ background: ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.12)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
12450
+ 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)'};
12451
+ `;
12452
+ const taskLine = document.createElement('div');
12453
+ taskLine.textContent = `${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}`;
12454
+ taskLine.style.cssText = `
12455
+ color: ${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};
12456
+ font-size: 10px;
12457
+ font-weight: 900;
12458
+ line-height: 1.2;
12459
+ overflow: hidden;
12460
+ text-overflow: ellipsis;
12461
+ white-space: nowrap;
12462
+ letter-spacing: 0;
12463
+ `;
12464
+ const taskSummary = document.createElement('div');
12465
+ taskSummary.textContent = latestTaskError || latestTaskResult || latestTaskTitle || 'Task queued';
12466
+ taskSummary.title = taskSummary.textContent;
12467
+ taskSummary.style.cssText = `
12468
+ color: #334155;
12469
+ font-size: 10px;
12470
+ font-weight: 700;
12471
+ line-height: 1.25;
12472
+ overflow: hidden;
12473
+ display: -webkit-box;
12474
+ -webkit-line-clamp: 2;
12475
+ -webkit-box-orient: vertical;
12476
+ letter-spacing: 0;
12477
+ `;
12478
+ taskBox.appendChild(taskLine);
12479
+ taskBox.appendChild(taskSummary);
12480
+ card.appendChild(taskBox);
12481
+ }
12482
+
12483
+ const actions = document.createElement('div');
12484
+ actions.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:auto;';
12485
+ const status = document.createElement('span');
12486
+ status.textContent = connectedDevice ? 'Connected' : 'Offline';
12487
+ status.style.cssText = `
12488
+ min-width: 0;
12489
+ color: ${connectedDevice ? '#047857' : '#64748b'};
12490
+ font-size: 11px;
12491
+ font-weight: 900;
12492
+ overflow: hidden;
12493
+ text-overflow: ellipsis;
12494
+ white-space: nowrap;
12495
+ `;
12496
+ actions.appendChild(status);
12497
+ if (connectedDevice && deviceId) {
12498
+ if (taskEnabled) {
12499
+ const taskButton = createRemoteFleetButton('Task', 'Dispatch task to this device', 'task-device');
12500
+ taskButton.dataset.deviceId = deviceId;
12501
+ taskButton.style.height = '24px';
12502
+ taskButton.style.fontSize = '10px';
12503
+ actions.appendChild(taskButton);
12504
+ }
12505
+ if (thumbnailEnabled) {
12506
+ const thumbnailButton = createRemoteFleetButton('Shot', 'Request thumbnail', 'thumbnail-device');
12507
+ thumbnailButton.dataset.deviceId = deviceId;
12508
+ thumbnailButton.style.height = '24px';
12509
+ thumbnailButton.style.fontSize = '10px';
12510
+ actions.appendChild(thumbnailButton);
12511
+ }
12512
+ const pingButton = createRemoteFleetButton('Ping', 'Ping device', 'ping-device');
12513
+ pingButton.dataset.deviceId = deviceId;
12514
+ pingButton.style.height = '24px';
12515
+ pingButton.style.fontSize = '10px';
12516
+ actions.appendChild(pingButton);
12517
+ }
12518
+ card.appendChild(actions);
12519
+ grid.appendChild(card);
12520
+ });
12521
+ }
12522
+
12523
+ bodyView.appendChild(grid);
12524
+
12525
+ const footer = document.createElement('div');
12526
+ footer.textContent = `Endpoint ${endpoint} · refreshed ${formatRemoteFleetAge(refreshedAt)}`;
12527
+ footer.style.cssText = `
12528
+ flex: 0 0 auto;
12529
+ color: #64748b;
12530
+ font-size: 11px;
12531
+ font-weight: 700;
12532
+ line-height: 1.2;
12533
+ overflow: hidden;
12534
+ text-overflow: ellipsis;
12535
+ white-space: nowrap;
12536
+ `;
12537
+ bodyView.appendChild(footer);
12538
+
12539
+ copyButton.addEventListener('click', event => {
12540
+ event.preventDefault();
12541
+ event.stopPropagation();
12542
+ navigator.clipboard?.writeText?.(command).catch(() => { });
12543
+ });
12544
+
12545
+ const setTaskFeedback = (message, tone = 'info') => {
12546
+ taskFeedback.textContent = message || '';
12547
+ taskFeedback.style.display = message ? 'block' : 'none';
12548
+ taskFeedback.style.background = tone === 'error'
12549
+ ? 'rgba(248, 113, 113, 0.12)'
12550
+ : tone === 'success'
12551
+ ? 'rgba(16, 185, 129, 0.10)'
12552
+ : 'rgba(37, 99, 235, 0.08)';
12553
+ taskFeedback.style.color = tone === 'error'
12554
+ ? '#991b1b'
12555
+ : tone === 'success'
12556
+ ? '#047857'
12557
+ : '#1d4ed8';
12558
+ };
12559
+
12560
+ const readTaskInstruction = () => String(taskInput.value || '').trim();
12561
+
12562
+ sendConnectedButton.addEventListener('click', async event => {
12563
+ event.preventDefault();
12564
+ event.stopPropagation();
12565
+ const instruction = readTaskInstruction();
12566
+ if (!instruction) {
12567
+ setTaskFeedback('Write a task first.', 'error');
12568
+ taskInput.focus();
12569
+ return;
12570
+ }
12571
+
12572
+ sendConnectedButton.disabled = true;
12573
+ setTaskFeedback('Dispatching task to connected devices...');
12574
+ try {
12575
+ const result = await invokeDotNetAsync('DispatchRemoteFleetTaskFromJs', nodeId, '', instruction);
12576
+ await syncRemoteFleetNodeStateFromResult(result);
12577
+ if (result?.success) {
12578
+ setTaskFeedback(`Queued ${result.queued || result.total || 1} remote task(s).`, 'success');
12579
+ } else {
12580
+ setTaskFeedback(result?.error || 'Task dispatch failed.', 'error');
12581
+ }
12582
+ } finally {
12583
+ sendConnectedButton.disabled = false;
12584
+ }
12585
+ });
12586
+
12587
+ refreshButton.addEventListener('click', async event => {
12588
+ event.preventDefault();
12589
+ event.stopPropagation();
12590
+ refreshButton.disabled = true;
12591
+ const result = await invokeDotNetAsync('RefreshRemoteFleetMonitorNodeFromJs', nodeId);
12592
+ await syncRemoteFleetNodeStateFromResult(result);
12593
+ refreshButton.disabled = false;
12594
+ });
12595
+
12596
+ grid.querySelectorAll('[data-remote-fleet-action="ping-device"]').forEach(button => {
12597
+ button.addEventListener('click', async event => {
12598
+ event.preventDefault();
12599
+ event.stopPropagation();
12600
+ button.disabled = true;
12601
+ const result = await invokeDotNetAsync('PingRemoteFleetDeviceFromJs', nodeId, button.dataset.deviceId || '');
12602
+ await syncRemoteFleetNodeStateFromResult(result);
12603
+ button.disabled = false;
12604
+ });
12605
+ });
12606
+
12607
+ grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
12608
+ button.addEventListener('click', async event => {
12609
+ event.preventDefault();
12610
+ event.stopPropagation();
12611
+ const instruction = readTaskInstruction();
12612
+ if (!instruction) {
12613
+ setTaskFeedback('Write a task first.', 'error');
12614
+ taskInput.focus();
12615
+ return;
12616
+ }
12617
+
12618
+ button.disabled = true;
12619
+ setTaskFeedback('Dispatching task to device...');
12620
+ try {
12621
+ const result = await invokeDotNetAsync('DispatchRemoteFleetTaskFromJs', nodeId, button.dataset.deviceId || '', instruction);
12622
+ await syncRemoteFleetNodeStateFromResult(result);
12623
+ if (result?.success) {
12624
+ setTaskFeedback('Queued remote task.', 'success');
12625
+ } else {
12626
+ setTaskFeedback(result?.error || 'Task dispatch failed.', 'error');
12627
+ }
12628
+ } finally {
12629
+ button.disabled = false;
12630
+ }
12631
+ });
12632
+ });
12633
+
12634
+ grid.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').forEach(button => {
12635
+ button.addEventListener('click', async event => {
12636
+ event.preventDefault();
12637
+ event.stopPropagation();
12638
+ button.disabled = true;
12639
+ const result = await invokeDotNetAsync('RequestRemoteFleetThumbnailFromJs', nodeId, button.dataset.deviceId || '');
12640
+ await syncRemoteFleetNodeStateFromResult(result);
12641
+ button.disabled = false;
12642
+ });
12643
+ });
12644
+ }
12645
+
11919
12646
  function bindMemoNodeEvents(container, nodeModel) {
11920
12647
  if (!container || !nodeModel) return;
11921
12648
  ensureMindCanvasAgentConsoleResize(container, nodeModel);
@@ -12147,11 +12874,12 @@
12147
12874
  const agentStyledMemo = isAgentStyledMemoNode(nodeModel);
12148
12875
  const mindCanvasAgent = isMindCanvasAgentNode(nodeModel);
12149
12876
  const businessAutomation = isBusinessAutomationNode(nodeModel);
12877
+ const remoteFleet = isRemoteFleetMonitorNode(nodeModel);
12150
12878
  const externalChrome = allowsMemoExternalChrome(nodeModel);
12151
12879
 
12152
12880
  const container = document.createElement('div');
12153
12881
  container.id = `node-${nodeModel.id}`;
12154
- container.className = `map-node css3d-dynamic-node map-node-memo${agentStyledMemo ? ' map-node-agent' : ''}${businessAutomation ? ' map-node-automation' : ''}`;
12882
+ container.className = `map-node css3d-dynamic-node map-node-memo${agentStyledMemo ? ' map-node-agent' : ''}${businessAutomation ? ' map-node-automation' : ''}${remoteFleet ? ' map-node-remote-fleet' : ''}`;
12155
12883
  container.dataset.nodeId = nodeModel.id;
12156
12884
  container.dataset.semanticType = getNodeSemanticType(nodeModel);
12157
12885
  container.dataset.agentConsoleOpen = isMindCanvasAgentConsoleOpen(nodeModel) ? 'true' : 'false';
@@ -15290,10 +16018,12 @@
15290
16018
  const iconKey = getMemoIconKey(nodeModel);
15291
16019
  const agentStyledMemo = isAgentStyledMemoNode(nodeModel);
15292
16020
  const businessAutomation = isBusinessAutomationNode(nodeModel);
16021
+ const remoteFleet = isRemoteFleetMonitorNode(nodeModel);
15293
16022
  const externalChrome = allowsMemoExternalChrome(nodeModel);
15294
16023
 
15295
16024
  memoEl.classList.toggle('map-node-agent', agentStyledMemo);
15296
16025
  memoEl.classList.toggle('map-node-automation', businessAutomation);
16026
+ memoEl.classList.toggle('map-node-remote-fleet', remoteFleet);
15297
16027
  memoEl.dataset.semanticType = getNodeSemanticType(nodeModel);
15298
16028
  memoEl.dataset.agentPlanOpen = getRenderedMindCanvasAgentPlanOpen(memoEl, nodeModel) ? 'true' : 'false';
15299
16029
  memoEl.dataset.agentConsoleOpen = getRenderedMindCanvasAgentConsoleOpen(memoEl, nodeModel) ? 'true' : 'false';