@mindexec/cli 0.2.18 → 0.2.20

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.18",
3
+ "version": "0.2.20",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -412,7 +412,7 @@ function wait(ms = 0) {
412
412
  return new Promise(resolve => setTimeout(resolve, ms));
413
413
  }
414
414
 
415
- function buildMonitorNode(devices, hubStatus, latestTaskBatch = null) {
415
+ function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTaskBatches = []) {
416
416
  const connected = devices.filter(device => device.Connected).length;
417
417
  const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5197';
418
418
  const pairToken = hubStatus.pairToken || 'render-smoke-token';
@@ -434,6 +434,7 @@ function buildMonitorNode(devices, hubStatus, latestTaskBatch = null) {
434
434
  RemoteFleetCanvasPagination: 'none',
435
435
  RemoteFleetDevicesJson: JSON.stringify(devices),
436
436
  RemoteFleetLatestTaskBatchJson: latestTaskBatch ? JSON.stringify(latestTaskBatch) : '',
437
+ RemoteFleetRecentTaskBatchesJson: JSON.stringify(recentTaskBatches),
437
438
  RemoteFleetLastError: ''
438
439
  }
439
440
  };
@@ -541,6 +542,20 @@ try {
541
542
  focusedDevice.LatestTaskUpdatedAt = new Date().toISOString();
542
543
  focusedDevice.LatestTaskError = 'task-timeout';
543
544
  focusedDevice.LatestTaskResultSummary = '';
545
+ const aiResultDevice = devices.find(device =>
546
+ device.DeviceId !== focusedDevice.DeviceId
547
+ && device.Connected);
548
+ assert.ok(aiResultDevice);
549
+ aiResultDevice.LatestTaskId = 'render-smoke-overview-ai-task';
550
+ aiResultDevice.LatestTaskCommandId = 'render-smoke-overview-ai-command';
551
+ aiResultDevice.LatestTaskTitle = 'Overview AI result';
552
+ aiResultDevice.LatestTaskStatus = 'completed';
553
+ aiResultDevice.LatestTaskApprovalLevel = 'ai-assist';
554
+ aiResultDevice.LatestTaskUpdatedAt = new Date(Date.now() - 1000).toISOString();
555
+ aiResultDevice.LatestTaskError = '';
556
+ aiResultDevice.LatestTaskResultModel = 'synthetic-render-ai';
557
+ aiResultDevice.LatestTaskResultResponseId = 'synthetic-response-overview';
558
+ aiResultDevice.LatestTaskResultSummary = 'Overview AI result visible in the fleet monitor.';
544
559
  const latestTaskBatch = {
545
560
  batchId: 'render-smoke-batch',
546
561
  title: 'Render smoke batch',
@@ -557,6 +572,25 @@ try {
557
572
  timedOut: 1,
558
573
  results: []
559
574
  };
575
+ const recentTaskBatches = [
576
+ latestTaskBatch,
577
+ {
578
+ batchId: 'render-smoke-previous-ai-batch',
579
+ title: 'Previous AI batch',
580
+ instructionPreview: 'Previous AI batch instruction preview.',
581
+ approvalLevel: 'ai-assist',
582
+ status: 'completed',
583
+ requestedAt: new Date(Date.now() - 180000).toISOString(),
584
+ updatedAt: new Date(Date.now() - 150000).toISOString(),
585
+ total: 42,
586
+ queued: 42,
587
+ pending: 0,
588
+ completed: 42,
589
+ failed: 0,
590
+ timedOut: 0,
591
+ results: []
592
+ }
593
+ ];
560
594
 
561
595
  const { manager, document } = await loadCss3DManager();
562
596
  assert.equal(typeof manager?.renderRemoteFleetMonitorForTest, 'function');
@@ -590,7 +624,7 @@ try {
590
624
  bodyView.dataset.remoteFleetFocusDeviceId = focusedDevice.DeviceId;
591
625
  document.body.appendChild(bodyView);
592
626
 
593
- manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch));
627
+ manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
594
628
 
595
629
  let cards = bodyView.querySelectorAll('article[data-device-id]');
596
630
  assert.equal(cards.length, SYNTHETIC_COUNT);
@@ -609,7 +643,19 @@ try {
609
643
  assert.ok(batchSummary.textContent.includes('Render smoke batch'));
610
644
  assert.ok(batchSummary.textContent.includes('208/210 done'));
611
645
  assert.ok(batchSummary.textContent.includes('1 timed out'));
646
+ const batchHistory = bodyView.querySelector('[data-remote-fleet-task-history="true"]');
647
+ assert.ok(batchHistory);
648
+ assert.ok(batchHistory.textContent.includes('Previous AI batch'));
649
+ assert.ok(batchHistory.textContent.includes('42/42 done'));
650
+ const resultPanel = bodyView.querySelector('[data-remote-fleet-task-results="true"]');
651
+ assert.ok(resultPanel);
652
+ assert.ok(resultPanel.textContent.includes('synthetic-render-ai'));
653
+ assert.ok(resultPanel.textContent.includes('Overview AI result visible'));
612
654
  assert.ok(devices.some(device => /^synthetic-response-/.test(device.LatestTaskResultResponseId)));
655
+ await wait(320);
656
+ assert.ok(dotNetCalls.some(call => call.methodName === 'RefreshRemoteFleetMonitorNodeFromJs'));
657
+ assert.equal(bodyView.dataset.remoteFleetTaskFollowKey, 'render-smoke-batch');
658
+ assert.ok(Number(bodyView.dataset.remoteFleetTaskFollowTicks || '0') >= 1);
613
659
 
614
660
  const searchInput = bodyView.querySelector('[data-remote-fleet-search="true"]');
615
661
  searchInput.value = 'synthetic pc 0001';
@@ -11991,6 +11991,87 @@
11991
11991
  }
11992
11992
  }
11993
11993
 
11994
+ function parseRemoteFleetRecentTaskBatches(nodeModel) {
11995
+ const raw = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetRecentTaskBatchesJson', '[]');
11996
+ if (!raw.trim()) {
11997
+ return [];
11998
+ }
11999
+
12000
+ try {
12001
+ const parsed = JSON.parse(raw);
12002
+ return Array.isArray(parsed) ? parsed.filter(item => item && typeof item === 'object') : [];
12003
+ } catch {
12004
+ return [];
12005
+ }
12006
+ }
12007
+
12008
+ function getRemoteFleetTaskBatchValue(batch, camelKey, pascalKey, fallback = '') {
12009
+ const value = batch?.[camelKey] ?? batch?.[pascalKey] ?? fallback;
12010
+ return value === undefined || value === null ? fallback : value;
12011
+ }
12012
+
12013
+ function getRemoteFleetTaskBatchNumber(batch, camelKey, pascalKey) {
12014
+ const value = Number(getRemoteFleetTaskBatchValue(batch, camelKey, pascalKey, 0));
12015
+ return Number.isFinite(value) ? value : 0;
12016
+ }
12017
+
12018
+ function getRemoteFleetTaskBatchKey(batch) {
12019
+ if (!batch || typeof batch !== 'object') {
12020
+ return '';
12021
+ }
12022
+
12023
+ const batchId = String(getRemoteFleetTaskBatchValue(batch, 'batchId', 'BatchId', '')).trim();
12024
+ if (batchId) {
12025
+ return batchId;
12026
+ }
12027
+
12028
+ const requestedAt = String(getRemoteFleetTaskBatchValue(batch, 'requestedAt', 'RequestedAt', '')).trim();
12029
+ const updatedAt = String(getRemoteFleetTaskBatchValue(batch, 'updatedAt', 'UpdatedAt', '')).trim();
12030
+ const title = String(getRemoteFleetTaskBatchValue(batch, 'title', 'Title', '')).trim();
12031
+ const total = getRemoteFleetTaskBatchNumber(batch, 'total', 'Total');
12032
+ return [title, requestedAt, updatedAt, total].filter(Boolean).join(':');
12033
+ }
12034
+
12035
+ function isRemoteFleetTaskBatchActive(batch) {
12036
+ if (!batch || typeof batch !== 'object') {
12037
+ return false;
12038
+ }
12039
+
12040
+ const status = String(getRemoteFleetTaskBatchValue(batch, 'status', 'Status', '')).trim().toLowerCase();
12041
+ const pending = getRemoteFleetTaskBatchNumber(batch, 'pending', 'Pending');
12042
+ const total = getRemoteFleetTaskBatchNumber(batch, 'total', 'Total');
12043
+ const completed = getRemoteFleetTaskBatchNumber(batch, 'completed', 'Completed');
12044
+ const failed = getRemoteFleetTaskBatchNumber(batch, 'failed', 'Failed');
12045
+ const terminalStatuses = new Set(['completed', 'completed-with-failures', 'failed', 'cancelled', 'canceled', 'done']);
12046
+ if (terminalStatuses.has(status)) {
12047
+ return false;
12048
+ }
12049
+
12050
+ if (pending > 0) {
12051
+ return true;
12052
+ }
12053
+
12054
+ if (['running', 'queued', 'pending', 'targeted', 'dispatching', 'in-progress'].includes(status)) {
12055
+ return true;
12056
+ }
12057
+
12058
+ return total > 0 && completed + failed < total;
12059
+ }
12060
+
12061
+ function getRemoteFleetTaskBatchFromResult(result) {
12062
+ const direct = result?.batch
12063
+ ?? result?.Batch
12064
+ ?? result?.taskBatch
12065
+ ?? result?.TaskBatch
12066
+ ?? result?.latestTaskBatch
12067
+ ?? result?.LatestTaskBatch
12068
+ ?? result?.refresh?.batch
12069
+ ?? result?.refresh?.Batch;
12070
+ return direct && typeof direct === 'object' && !Array.isArray(direct)
12071
+ ? direct
12072
+ : null;
12073
+ }
12074
+
11994
12075
  function parseRemoteFleetPinnedDevice(nodeModel) {
11995
12076
  const raw = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetPinnedDeviceJson', '{}');
11996
12077
  if (!raw.trim()) {
@@ -12160,6 +12241,9 @@
12160
12241
 
12161
12242
  const REMOTE_FLEET_MONITOR_REFRESH_MS = 5000;
12162
12243
  const REMOTE_FLEET_LIVE_REFRESH_MS = 1000;
12244
+ const REMOTE_FLEET_TASK_FOLLOW_INITIAL_MS = 250;
12245
+ const REMOTE_FLEET_TASK_FOLLOW_REFRESH_MS = 2000;
12246
+ const REMOTE_FLEET_TASK_FOLLOW_MAX_TICKS = 60;
12163
12247
 
12164
12248
  function clearRemoteFleetTimers(bodyView) {
12165
12249
  if (!bodyView) return;
@@ -12171,6 +12255,10 @@
12171
12255
  clearInterval(bodyView._remoteFleetMonitorRefreshTimer);
12172
12256
  bodyView._remoteFleetMonitorRefreshTimer = null;
12173
12257
  }
12258
+ if (bodyView._remoteFleetTaskFollowTimer) {
12259
+ clearTimeout(bodyView._remoteFleetTaskFollowTimer);
12260
+ bodyView._remoteFleetTaskFollowTimer = null;
12261
+ }
12174
12262
  }
12175
12263
 
12176
12264
  function getRemoteFleetDeviceField(device, camelKey, pascalKey, fallback = '') {
@@ -12885,6 +12973,14 @@
12885
12973
  const autoMonitorState = bodyView.dataset.remoteFleetAutoMonitor !== 'false';
12886
12974
  const focusState = bodyView.dataset.remoteFleetFocusDeviceId || '';
12887
12975
  const latestTaskBatch = parseRemoteFleetLatestTaskBatch(nodeModel);
12976
+ const recentTaskBatches = parseRemoteFleetRecentTaskBatches(nodeModel);
12977
+ const latestTaskBatchKey = getRemoteFleetTaskBatchKey(latestTaskBatch);
12978
+ const latestTaskBatchActive = isRemoteFleetTaskBatchActive(latestTaskBatch);
12979
+ if (latestTaskBatchKey && bodyView.dataset.remoteFleetTaskFollowKey !== latestTaskBatchKey) {
12980
+ bodyView.dataset.remoteFleetTaskFollowKey = latestTaskBatchKey;
12981
+ bodyView.dataset.remoteFleetTaskFollowTicks = '0';
12982
+ }
12983
+ bodyView.dataset.remoteFleetTaskFollowActive = latestTaskBatchActive ? 'true' : 'false';
12888
12984
  const sortedDevices = [...parseRemoteFleetDevices(nodeModel)]
12889
12985
  .sort((left, right) => compareRemoteFleetDevices(left, right, sortState));
12890
12986
  const devices = groupState === 'none'
@@ -13250,6 +13346,114 @@
13250
13346
  bodyView.appendChild(batchBox);
13251
13347
  }
13252
13348
 
13349
+ const visibleRecentBatches = recentTaskBatches
13350
+ .filter(batch => batch && String(batch.batchId ?? batch.BatchId ?? '').trim())
13351
+ .slice(0, 3);
13352
+ if (visibleRecentBatches.length > 0) {
13353
+ const history = document.createElement('div');
13354
+ history.dataset.remoteFleetTaskHistory = 'true';
13355
+ history.style.cssText = `
13356
+ flex: 0 0 auto;
13357
+ display: grid;
13358
+ grid-template-columns: repeat(${Math.min(3, visibleRecentBatches.length)}, minmax(0, 1fr));
13359
+ gap: 7px;
13360
+ min-width: 0;
13361
+ `;
13362
+ visibleRecentBatches.forEach(batch => {
13363
+ const totalCount = Number(batch.total ?? batch.Total ?? 0);
13364
+ const completedCount = Number(batch.completed ?? batch.Completed ?? 0);
13365
+ const failedCount = Number(batch.failed ?? batch.Failed ?? 0);
13366
+ const pendingCount = Number(batch.pending ?? batch.Pending ?? 0);
13367
+ const timedOutCount = Number(batch.timedOut ?? batch.TimedOut ?? 0);
13368
+ const statusText = String(batch.status ?? batch.Status ?? '').trim() || 'batch';
13369
+ const titleText = String(batch.title ?? batch.Title ?? 'Remote task batch').trim();
13370
+ const updatedText = String(batch.updatedAt ?? batch.UpdatedAt ?? batch.requestedAt ?? batch.RequestedAt ?? '').trim();
13371
+ const item = document.createElement('div');
13372
+ item.dataset.remoteFleetTaskHistoryItem = 'true';
13373
+ item.style.cssText = `
13374
+ min-width: 0;
13375
+ padding: 7px 8px;
13376
+ border-radius: 7px;
13377
+ background: ${failedCount > 0 ? 'rgba(254, 242, 242, 0.82)' : 'rgba(248, 250, 252, 0.90)'};
13378
+ border: 1px solid ${failedCount > 0 ? 'rgba(248, 113, 113, 0.22)' : 'rgba(148, 163, 184, 0.22)'};
13379
+ `;
13380
+ const line = document.createElement('div');
13381
+ line.textContent = `${titleText} - ${statusText}`;
13382
+ line.title = line.textContent;
13383
+ line.style.cssText = `color:${failedCount > 0 ? '#991b1b' : '#334155'};font-size:10px;font-weight:950;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
13384
+ const stats = document.createElement('div');
13385
+ stats.textContent = `${completedCount}/${totalCount || completedCount + failedCount + pendingCount} done - ${failedCount} failed${timedOutCount > 0 ? ` - ${timedOutCount} timed out` : ''}${updatedText ? ` - ${formatRemoteFleetAge(updatedText)}` : ''}`;
13386
+ stats.title = stats.textContent;
13387
+ stats.style.cssText = 'color:#64748b;font-size:9px;font-weight:850;line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
13388
+ item.appendChild(line);
13389
+ item.appendChild(stats);
13390
+ history.appendChild(item);
13391
+ });
13392
+ bodyView.appendChild(history);
13393
+ }
13394
+
13395
+ const recentTaskResults = devices
13396
+ .map(device => {
13397
+ const updatedAt = String(getRemoteFleetDeviceField(device, 'latestTaskUpdatedAt', 'LatestTaskUpdatedAt', ''));
13398
+ const status = String(getRemoteFleetDeviceField(device, 'latestTaskStatus', 'LatestTaskStatus', ''));
13399
+ const title = String(getRemoteFleetDeviceField(device, 'latestTaskTitle', 'LatestTaskTitle', ''));
13400
+ const approval = String(getRemoteFleetDeviceField(device, 'latestTaskApprovalLevel', 'LatestTaskApprovalLevel', ''));
13401
+ const error = String(getRemoteFleetDeviceField(device, 'latestTaskError', 'LatestTaskError', ''));
13402
+ const model = String(getRemoteFleetDeviceField(device, 'latestTaskResultModel', 'LatestTaskResultModel', ''));
13403
+ const summary = String(getRemoteFleetDeviceField(device, 'latestTaskResultSummary', 'LatestTaskResultSummary', ''));
13404
+ return {
13405
+ device,
13406
+ updatedAt,
13407
+ status,
13408
+ title,
13409
+ approval,
13410
+ error,
13411
+ model,
13412
+ summary,
13413
+ updatedMs: getRemoteFleetTimestampMs(updatedAt)
13414
+ };
13415
+ })
13416
+ .filter(item => item.status || item.title || item.summary || item.error)
13417
+ .sort((left, right) => right.updatedMs - left.updatedMs)
13418
+ .slice(0, 4);
13419
+ if (recentTaskResults.length > 0) {
13420
+ const resultPanel = document.createElement('div');
13421
+ resultPanel.dataset.remoteFleetTaskResults = 'true';
13422
+ resultPanel.style.cssText = `
13423
+ flex: 0 0 auto;
13424
+ display: grid;
13425
+ grid-template-columns: repeat(2, minmax(0, 1fr));
13426
+ gap: 7px;
13427
+ min-width: 0;
13428
+ `;
13429
+ recentTaskResults.forEach(item => {
13430
+ const failed = !!item.error || item.status === 'failed';
13431
+ const row = document.createElement('div');
13432
+ row.dataset.remoteFleetTaskResultItem = 'true';
13433
+ row.dataset.deviceId = getRemoteFleetDeviceId(item.device);
13434
+ row.style.cssText = `
13435
+ min-width: 0;
13436
+ padding: 7px 8px;
13437
+ border-radius: 7px;
13438
+ background: ${failed ? 'rgba(248, 113, 113, 0.10)' : 'rgba(16, 185, 129, 0.08)'};
13439
+ border: 1px solid ${failed ? 'rgba(248, 113, 113, 0.22)' : 'rgba(16, 185, 129, 0.16)'};
13440
+ `;
13441
+ const headline = document.createElement('div');
13442
+ const mode = item.approval === 'ai-assist' ? 'AI' : 'Task';
13443
+ headline.textContent = `${mode} ${item.status || 'task'} - ${getRemoteFleetDeviceName(item.device)}${item.model ? ` - ${item.model}` : ''}`;
13444
+ headline.title = headline.textContent;
13445
+ headline.style.cssText = `color:${failed ? '#991b1b' : '#047857'};font-size:10px;font-weight:950;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
13446
+ const summary = document.createElement('div');
13447
+ summary.textContent = formatRemoteFleetTaskError(item.error) || item.summary || item.title || 'Task queued';
13448
+ summary.title = summary.textContent;
13449
+ summary.style.cssText = 'color:#334155;font-size:10px;font-weight:750;line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
13450
+ row.appendChild(headline);
13451
+ row.appendChild(summary);
13452
+ resultPanel.appendChild(row);
13453
+ });
13454
+ bodyView.appendChild(resultPanel);
13455
+ }
13456
+
13253
13457
  if (focusedDevice) {
13254
13458
  const focusedId = getRemoteFleetDeviceId(focusedDevice);
13255
13459
  const focusedName = getRemoteFleetDeviceName(focusedDevice);
@@ -13912,6 +14116,84 @@
13912
14116
  bodyView._remoteFleetRefreshInFlight = false;
13913
14117
  }
13914
14118
  };
14119
+ const prepareRemoteFleetTaskFollow = batch => {
14120
+ if (!isRemoteFleetTaskBatchActive(batch)) {
14121
+ bodyView.dataset.remoteFleetTaskFollowActive = 'false';
14122
+ return false;
14123
+ }
14124
+
14125
+ const batchKey = getRemoteFleetTaskBatchKey(batch);
14126
+ if (batchKey && bodyView.dataset.remoteFleetTaskFollowKey !== batchKey) {
14127
+ bodyView.dataset.remoteFleetTaskFollowKey = batchKey;
14128
+ bodyView.dataset.remoteFleetTaskFollowTicks = '0';
14129
+ }
14130
+
14131
+ bodyView.dataset.remoteFleetTaskFollowActive = 'true';
14132
+ return true;
14133
+ };
14134
+ const scheduleRemoteFleetTaskFollow = (delayMs = REMOTE_FLEET_TASK_FOLLOW_INITIAL_MS) => {
14135
+ if (bodyView._remoteFleetTaskFollowTimer) {
14136
+ clearTimeout(bodyView._remoteFleetTaskFollowTimer);
14137
+ bodyView._remoteFleetTaskFollowTimer = null;
14138
+ }
14139
+
14140
+ if (bodyView.dataset.remoteFleetTaskFollowActive !== 'true') {
14141
+ return;
14142
+ }
14143
+
14144
+ const ticks = Number(bodyView.dataset.remoteFleetTaskFollowTicks || '0');
14145
+ if (Number.isFinite(ticks) && ticks >= REMOTE_FLEET_TASK_FOLLOW_MAX_TICKS) {
14146
+ bodyView.dataset.remoteFleetTaskFollowActive = 'false';
14147
+ window.RuntimeTrace?.emit?.('remote.task.followStopped', {
14148
+ nodeId,
14149
+ batchKey: bodyView.dataset.remoteFleetTaskFollowKey || '',
14150
+ reason: 'max-ticks'
14151
+ });
14152
+ return;
14153
+ }
14154
+
14155
+ const timer = setTimeout(async () => {
14156
+ bodyView._remoteFleetTaskFollowTimer = null;
14157
+ if (!document.body.contains(bodyView)) {
14158
+ clearRemoteFleetTimers(bodyView);
14159
+ return;
14160
+ }
14161
+
14162
+ const currentTicks = Number(bodyView.dataset.remoteFleetTaskFollowTicks || '0');
14163
+ bodyView.dataset.remoteFleetTaskFollowTicks = String((Number.isFinite(currentTicks) ? currentTicks : 0) + 1);
14164
+ window.RuntimeTrace?.emit?.('remote.task.followRefresh', {
14165
+ nodeId,
14166
+ batchKey: bodyView.dataset.remoteFleetTaskFollowKey || '',
14167
+ tick: Number(bodyView.dataset.remoteFleetTaskFollowTicks || '0')
14168
+ });
14169
+
14170
+ try {
14171
+ await refreshRemoteFleetNode();
14172
+ } catch {
14173
+ clearRemoteFleetTimers(bodyView);
14174
+ return;
14175
+ }
14176
+
14177
+ if (!document.body.contains(bodyView)) {
14178
+ clearRemoteFleetTimers(bodyView);
14179
+ return;
14180
+ }
14181
+
14182
+ const nextTicks = Number(bodyView.dataset.remoteFleetTaskFollowTicks || '0');
14183
+ if (bodyView.dataset.remoteFleetTaskFollowActive === 'true'
14184
+ && (!Number.isFinite(nextTicks) || nextTicks < REMOTE_FLEET_TASK_FOLLOW_MAX_TICKS)) {
14185
+ scheduleRemoteFleetTaskFollow(REMOTE_FLEET_TASK_FOLLOW_REFRESH_MS);
14186
+ }
14187
+ }, Math.max(0, Number(delayMs) || 0));
14188
+ timer?.unref?.();
14189
+ bodyView._remoteFleetTaskFollowTimer = timer;
14190
+ };
14191
+ const startRemoteFleetTaskFollowFromResult = result => {
14192
+ const batch = getRemoteFleetTaskBatchFromResult(result) || latestTaskBatch;
14193
+ if (prepareRemoteFleetTaskFollow(batch)) {
14194
+ scheduleRemoteFleetTaskFollow();
14195
+ }
14196
+ };
13915
14197
  const getVisibleEligibleDeviceIds = () => {
13916
14198
  const wantsAi = useAiAssist();
13917
14199
  return getDeviceCards()
@@ -14027,6 +14309,9 @@
14027
14309
  renderRemoteFleetMonitor(bodyView, nodeModel);
14028
14310
  });
14029
14311
  applyRemoteFleetFilters();
14312
+ if (latestTaskBatchActive) {
14313
+ scheduleRemoteFleetTaskFollow();
14314
+ }
14030
14315
 
14031
14316
  sendVisibleButton.addEventListener('click', async event => {
14032
14317
  event.preventDefault();
@@ -14052,6 +14337,7 @@
14052
14337
  const mode = useAiAssist() ? 'visible AI task' : 'visible remote task';
14053
14338
  const feedback = formatRemoteFleetDispatchFeedback(result, targetIds.length, mode);
14054
14339
  setTaskFeedback(feedback.text, feedback.tone);
14340
+ startRemoteFleetTaskFollowFromResult(result);
14055
14341
  } finally {
14056
14342
  applyRemoteFleetFilters();
14057
14343
  }
@@ -14075,6 +14361,7 @@
14075
14361
  const mode = useAiAssist() ? 'AI task' : 'remote task';
14076
14362
  const feedback = formatRemoteFleetDispatchFeedback(result, result?.total || 0, mode);
14077
14363
  setTaskFeedback(feedback.text, feedback.tone);
14364
+ startRemoteFleetTaskFollowFromResult(result);
14078
14365
  } finally {
14079
14366
  applyRemoteFleetFilters();
14080
14367
  }
@@ -14198,6 +14485,7 @@
14198
14485
  await syncRemoteFleetNodeStateFromResult(result);
14199
14486
  if (result?.success) {
14200
14487
  setTaskFeedback(useAiAssist() ? 'Queued AI task.' : 'Queued remote task.', 'success');
14488
+ startRemoteFleetTaskFollowFromResult(result);
14201
14489
  } else {
14202
14490
  setTaskFeedback(result?.error || 'Task dispatch failed.', 'error');
14203
14491
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-x+zuHEgludyG90VHm7vAvh24gxrcXgpLsHEMSmn3EZA=",
4
+ "hash": "sha256-uiy622hnTDJkSexxzoeajNAT7LDKXgnVH7DvRNAiX9w=",
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.lm7kgq8l8h.dll": "MindExecution.Kernel.dll",
128
128
  "MindExecution.Plugins.Admin.k085kt3dls.dll": "MindExecution.Plugins.Admin.dll",
129
129
  "MindExecution.Plugins.Business.dsz9uwkxih.dll": "MindExecution.Plugins.Business.dll",
130
- "MindExecution.Plugins.Concept.6gcazss69r.dll": "MindExecution.Plugins.Concept.dll",
130
+ "MindExecution.Plugins.Concept.wnkch6jqjz.dll": "MindExecution.Plugins.Concept.dll",
131
131
  "MindExecution.Plugins.Directory.ufbriq6js4.dll": "MindExecution.Plugins.Directory.dll",
132
- "MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll": "MindExecution.Plugins.PlanMaster.dll",
133
- "MindExecution.Plugins.YouTube.5ojg8vbf9w.dll": "MindExecution.Plugins.YouTube.dll",
134
- "MindExecution.Shared.ahf7j9dymw.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.6i9vy7xy1k.dll": "MindExecution.Web.dll",
132
+ "MindExecution.Plugins.PlanMaster.ygbsp1z4z0.dll": "MindExecution.Plugins.PlanMaster.dll",
133
+ "MindExecution.Plugins.YouTube.a992061fhz.dll": "MindExecution.Plugins.YouTube.dll",
134
+ "MindExecution.Shared.y27t6njaw0.dll": "MindExecution.Shared.dll",
135
+ "MindExecution.Web.bqamgu5gq1.dll": "MindExecution.Web.dll",
136
136
  "dotnet.js": "dotnet.js",
137
137
  "dotnet.native.xsn1d6x2kd.js": "dotnet.native.js",
138
138
  "dotnet.native.vz0adxojrz.wasm": "dotnet.native.wasm",
@@ -280,16 +280,16 @@
280
280
  "netstandard.0xet7jg7ky.dll": "sha256-xENDv620uJ8fHwLJ2bdhrTHz4QPjvqXOztnk2a4wr0c=",
281
281
  "MindExecution.Core.ckgjbdvvxl.dll": "sha256-ZDas50sgMQGCNt0UC1ZErP+uv7PQ7z/skK7zCwnmU2I=",
282
282
  "MindExecution.Kernel.lm7kgq8l8h.dll": "sha256-ndscepu8d8g/nrJmBP8VrkIOTIkSA57t5shOq/rp/ys=",
283
- "MindExecution.Plugins.Concept.6gcazss69r.dll": "sha256-hpCyKxiQxk50zHE8iOWtoIiBapBG83wfaos3jZzeaYg=",
284
- "MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll": "sha256-iwATrRDt3Wwr/DKLxUl7GzqwKdhMLZNBKYVJPAkrhdM=",
285
- "MindExecution.Shared.ahf7j9dymw.dll": "sha256-HlFaI9iZybI0VDe6KpsgRqLb3GTfUVXoU3KMlx0gQ0o=",
286
- "MindExecution.Web.6i9vy7xy1k.dll": "sha256-IlDxFyeBhZWgici5UD0RtmqRNwynyD6n9Q0IJ8f4eBE="
283
+ "MindExecution.Plugins.Concept.wnkch6jqjz.dll": "sha256-nCycvHAVswddeF1J0XSbpQSsVsutbCpx+52o7ne+kZw=",
284
+ "MindExecution.Plugins.PlanMaster.ygbsp1z4z0.dll": "sha256-ErbKkuXxJANIiJahFjEUZq9TH1Kz/y2LEuepW6ZxJnU=",
285
+ "MindExecution.Shared.y27t6njaw0.dll": "sha256-Gvuf30dJMtENks7FSoBydnPkKJp4NS10DjvJmsTLEV0=",
286
+ "MindExecution.Web.bqamgu5gq1.dll": "sha256-LYhJEP8rDmDCLhf8aQxkiGEmpsffoNzT6S37CxSfNQ8="
287
287
  },
288
288
  "lazyAssembly": {
289
289
  "MindExecution.Plugins.Admin.k085kt3dls.dll": "sha256-LLDLGzE3yaP+rL+8CMZGWBmGQZBfrRWgUMijX/fxTxw=",
290
290
  "MindExecution.Plugins.Business.dsz9uwkxih.dll": "sha256-bfvFu7Pq58deN7TnwfhsUZdvWhS4RpA0LdO77UsBnY8=",
291
291
  "MindExecution.Plugins.Directory.ufbriq6js4.dll": "sha256-qoxaegrVr9oaXJgQqU7t7CrCf0EYgsd22BcWXg/+91I=",
292
- "MindExecution.Plugins.YouTube.5ojg8vbf9w.dll": "sha256-a+S4Xha/L7um0foEwb8E0p8VqEw21M6ETzjrEUpIYTk="
292
+ "MindExecution.Plugins.YouTube.a992061fhz.dll": "sha256-orw8lB1/+iy3s9EoeG+MDueyomsbPTC7vq0UIjUwtfQ="
293
293
  }
294
294
  },
295
295
  "cacheBootResources": true,
@@ -558,7 +558,7 @@
558
558
  }
559
559
 
560
560
  const base = '_content/MindExecution.Shared/js/';
561
- const scriptVersion = '20260612-remote-task-batch-v476';
561
+ const scriptVersion = '20260612-remote-task-follow-v478';
562
562
  const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
563
563
  console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
564
564
  const criticalScripts = [
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "wUCVSTgn",
2
+ "version": "dr5Y7e+X",
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-MaP05yuNb/XsfihViUh/bL9svf11oMZGMZiAWnpm/Gk=",
89
+ "hash": "sha256-r/rKyK7pwOufIiynKaiu7pt8CgoBdLd7u+FmbZHVbko=",
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.dsz9uwkxih.dll"
427
427
  },
428
428
  {
429
- "hash": "sha256-hpCyKxiQxk50zHE8iOWtoIiBapBG83wfaos3jZzeaYg=",
430
- "url": "_framework/MindExecution.Plugins.Concept.6gcazss69r.dll"
429
+ "hash": "sha256-nCycvHAVswddeF1J0XSbpQSsVsutbCpx+52o7ne+kZw=",
430
+ "url": "_framework/MindExecution.Plugins.Concept.wnkch6jqjz.dll"
431
431
  },
432
432
  {
433
433
  "hash": "sha256-qoxaegrVr9oaXJgQqU7t7CrCf0EYgsd22BcWXg/+91I=",
434
434
  "url": "_framework/MindExecution.Plugins.Directory.ufbriq6js4.dll"
435
435
  },
436
436
  {
437
- "hash": "sha256-iwATrRDt3Wwr/DKLxUl7GzqwKdhMLZNBKYVJPAkrhdM=",
438
- "url": "_framework/MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll"
437
+ "hash": "sha256-ErbKkuXxJANIiJahFjEUZq9TH1Kz/y2LEuepW6ZxJnU=",
438
+ "url": "_framework/MindExecution.Plugins.PlanMaster.ygbsp1z4z0.dll"
439
439
  },
440
440
  {
441
- "hash": "sha256-a+S4Xha/L7um0foEwb8E0p8VqEw21M6ETzjrEUpIYTk=",
442
- "url": "_framework/MindExecution.Plugins.YouTube.5ojg8vbf9w.dll"
441
+ "hash": "sha256-orw8lB1/+iy3s9EoeG+MDueyomsbPTC7vq0UIjUwtfQ=",
442
+ "url": "_framework/MindExecution.Plugins.YouTube.a992061fhz.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-HlFaI9iZybI0VDe6KpsgRqLb3GTfUVXoU3KMlx0gQ0o=",
446
- "url": "_framework/MindExecution.Shared.ahf7j9dymw.dll"
445
+ "hash": "sha256-Gvuf30dJMtENks7FSoBydnPkKJp4NS10DjvJmsTLEV0=",
446
+ "url": "_framework/MindExecution.Shared.y27t6njaw0.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-IlDxFyeBhZWgici5UD0RtmqRNwynyD6n9Q0IJ8f4eBE=",
450
- "url": "_framework/MindExecution.Web.6i9vy7xy1k.dll"
449
+ "hash": "sha256-LYhJEP8rDmDCLhf8aQxkiGEmpsffoNzT6S37CxSfNQ8=",
450
+ "url": "_framework/MindExecution.Web.bqamgu5gq1.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-fGH3vBiA9K4LY1+siDEi6C1uyYfJgDAywmyOxn+xGY8=",
773
+ "hash": "sha256-6xyfZZneOcB1qKLApN7yPISkoN/wp8qEwfDnH0h9JhA=",
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-2i4bXinqysl6fMxtRowvbfVpJyKIfPSm3wdbCBcX5i4=",
837
+ "hash": "sha256-0dZRFimqvytlX0CmLM1UGW//sqP9ru7W0usTp/5Z570=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: wUCVSTgn */
1
+ /* Manifest version: dr5Y7e+X */
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