@mindexec/cli 0.2.15 → 0.2.17

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 (22) hide show
  1. package/package.json +1 -1
  2. package/remote-hub.js +350 -0
  3. package/scripts/remote-fleet-render-smoke.mjs +35 -2
  4. package/scripts/remote-hub-scale-smoke.mjs +14 -7
  5. package/scripts/remote-hub-smoke.mjs +59 -1
  6. package/server.js +13 -14
  7. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +93 -2
  8. package/wwwroot/_framework/MindExecution.Core.ckgjbdvvxl.dll +0 -0
  9. package/wwwroot/_framework/{MindExecution.Kernel.lqglgq2jmo.dll → MindExecution.Kernel.lm7kgq8l8h.dll} +0 -0
  10. package/wwwroot/_framework/{MindExecution.Plugins.Admin.2jqptn6ylw.dll → MindExecution.Plugins.Admin.k085kt3dls.dll} +0 -0
  11. package/wwwroot/_framework/{MindExecution.Plugins.Business.8hzwity3fl.dll → MindExecution.Plugins.Business.dsz9uwkxih.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Concept.gc38fmee62.dll → MindExecution.Plugins.Concept.6gcazss69r.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Directory.hr2p0grenr.dll → MindExecution.Plugins.Directory.ufbriq6js4.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.ivqd47y3cx.dll → MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.tpgm4p80hh.dll → MindExecution.Plugins.YouTube.5ojg8vbf9w.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Shared.2r4r7zx1ek.dll → MindExecution.Shared.ahf7j9dymw.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Web.stbecd1tk9.dll → MindExecution.Web.6i9vy7xy1k.dll} +0 -0
  18. package/wwwroot/_framework/blazor.boot.json +21 -21
  19. package/wwwroot/index.html +1 -1
  20. package/wwwroot/service-worker-assets.js +24 -24
  21. package/wwwroot/service-worker.js +1 -1
  22. package/wwwroot/_framework/MindExecution.Core.q469sducjw.dll +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
package/remote-hub.js CHANGED
@@ -5,12 +5,14 @@ import crypto from 'crypto';
5
5
  const DEFAULT_REMOTE_HUB_PORT = 5197;
6
6
  const DEFAULT_REMOTE_HUB_HOST = '127.0.0.1';
7
7
  const DEFAULT_HEARTBEAT_MS = 5000;
8
+ const DEFAULT_AGENT_TASK_TIMEOUT_MS = 120000;
8
9
  const MAX_LINE_CHARS = 4 * 1024 * 1024;
9
10
  const MAX_THUMBNAIL_BASE64_CHARS = 3 * 1024 * 1024;
10
11
  const MAX_STREAM_BASE64_CHARS = 3 * 1024 * 1024;
11
12
  const MAX_AGENT_TASK_CHARS = 4000;
12
13
  const MAX_AGENT_TASK_RESULT_CHARS = 3000;
13
14
  const RECENT_TASK_LIMIT = 12;
15
+ const RECENT_TASK_BATCH_LIMIT = 16;
14
16
  const REMOTE_PROTOCOL_VERSION = 1;
15
17
  const MAX_SYNTHETIC_DEVICES = 1000;
16
18
  const SYNTHETIC_FRAME_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC';
@@ -79,6 +81,7 @@ function createDeviceCounters(overrides = {}) {
79
81
  tasksQueued: 0,
80
82
  taskResultsReceived: 0,
81
83
  taskResultsFailed: 0,
84
+ taskResultsTimedOut: 0,
82
85
  ...overrides
83
86
  };
84
87
  }
@@ -152,6 +155,31 @@ function serializeDevice(device) {
152
155
  };
153
156
  }
154
157
 
158
+ function serializeTaskBatch(batch) {
159
+ if (!batch) {
160
+ return null;
161
+ }
162
+
163
+ return {
164
+ batchId: batch.batchId,
165
+ title: batch.title,
166
+ instructionPreview: batch.instructionPreview,
167
+ approvalLevel: batch.approvalLevel,
168
+ status: batch.status,
169
+ requestedAt: batch.requestedAt,
170
+ updatedAt: batch.updatedAt,
171
+ total: batch.total,
172
+ queued: batch.queued,
173
+ pending: batch.pending,
174
+ completed: batch.completed,
175
+ failed: batch.failed,
176
+ timedOut: batch.timedOut,
177
+ results: Array.isArray(batch.results)
178
+ ? batch.results.map(item => ({ ...item }))
179
+ : []
180
+ };
181
+ }
182
+
155
183
  function writeJsonLine(socket, payload) {
156
184
  if (!socket || socket.destroyed) {
157
185
  return false;
@@ -172,11 +200,17 @@ export function createRemoteHub(options = {}) {
172
200
  const host = safeString(env.REMOTE_HUB_HOST || DEFAULT_REMOTE_HUB_HOST, 128);
173
201
  const requestedPort = normalizePort(env.REMOTE_HUB_PORT || DEFAULT_REMOTE_HUB_PORT);
174
202
  const heartbeatMs = clampNumber(env.REMOTE_HUB_HEARTBEAT_MS, 1000, 60000, DEFAULT_HEARTBEAT_MS);
203
+ const taskTimeoutMs = clampNumber(
204
+ options.taskTimeoutMs ?? env.REMOTE_HUB_TASK_TIMEOUT_MS,
205
+ 50,
206
+ 30 * 60 * 1000,
207
+ DEFAULT_AGENT_TASK_TIMEOUT_MS);
175
208
  const pairToken = safeString(
176
209
  env.REMOTE_HUB_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(6).toString('hex'),
177
210
  256);
178
211
 
179
212
  const devices = new Map();
213
+ const taskBatches = new Map();
180
214
  const sockets = new Map();
181
215
  const allSockets = new Set();
182
216
  let server = null;
@@ -194,6 +228,7 @@ export function createRemoteHub(options = {}) {
194
228
  protocol: 'tcp-jsonl',
195
229
  protocolVersion: REMOTE_PROTOCOL_VERSION,
196
230
  heartbeatMs,
231
+ taskTimeoutMs,
197
232
  agentPackage: '@mindexec/remote',
198
233
  agentEndpoint: `${host}:${boundPort || requestedPort}`,
199
234
  pairToken: includeSecrets ? pairToken : undefined,
@@ -221,6 +256,17 @@ export function createRemoteHub(options = {}) {
221
256
  });
222
257
  }
223
258
 
259
+ function listTaskBatches() {
260
+ return [...taskBatches.values()]
261
+ .map(serializeTaskBatch)
262
+ .filter(Boolean)
263
+ .sort((left, right) => String(right.requestedAt || '').localeCompare(String(left.requestedAt || '')));
264
+ }
265
+
266
+ function getLatestTaskBatch() {
267
+ return listTaskBatches()[0] || null;
268
+ }
269
+
224
270
  function emitRemoteEvent(type, device = null, extra = {}) {
225
271
  emitEvent(type, {
226
272
  ...extra,
@@ -307,6 +353,7 @@ export function createRemoteHub(options = {}) {
307
353
  }
308
354
 
309
355
  existing.lastDisconnectReason = 'replaced-by-new-session';
356
+ failAllPendingTasks(existing, 'replaced-by-new-session');
310
357
  writeJsonLine(existing.socket, {
311
358
  type: 'disconnect',
312
359
  reason: 'replaced-by-new-session',
@@ -427,6 +474,7 @@ export function createRemoteHub(options = {}) {
427
474
  latestTask,
428
475
  recentTasks: latestTask ? [latestTask] : [],
429
476
  pendingTaskCommands: new Map(),
477
+ pendingTaskTimers: new Map(),
430
478
  counters: createDeviceCounters({
431
479
  messagesReceived: 1,
432
480
  statusReceived: 1,
@@ -532,6 +580,7 @@ export function createRemoteHub(options = {}) {
532
580
  latestTask: null,
533
581
  recentTasks: [],
534
582
  pendingTaskCommands: new Map(),
583
+ pendingTaskTimers: new Map(),
535
584
  synthetic: false,
536
585
  counters: createDeviceCounters({ messagesReceived: 1 })
537
586
  };
@@ -573,6 +622,7 @@ export function createRemoteHub(options = {}) {
573
622
  device.activeLiveStream.stoppedAt = device.disconnectedAt;
574
623
  device.activeLiveStream.stopReason = reason;
575
624
  }
625
+ failAllPendingTasks(device, 'device-disconnected');
576
626
  logWarn('remote', `device disconnected ${device.deviceName} (${deviceId}): ${reason}`);
577
627
  emitRemoteEvent('RemoteDeviceDisconnected', device, { reason });
578
628
  }
@@ -601,6 +651,241 @@ export function createRemoteHub(options = {}) {
601
651
  return task;
602
652
  }
603
653
 
654
+ function trimTaskBatches() {
655
+ const ordered = [...taskBatches.values()]
656
+ .sort((left, right) => String(right.requestedAt || '').localeCompare(String(left.requestedAt || '')));
657
+ for (const batch of ordered.slice(RECENT_TASK_BATCH_LIMIT)) {
658
+ taskBatches.delete(batch.batchId);
659
+ }
660
+ }
661
+
662
+ function beginTaskBatch(deviceIds, options = {}) {
663
+ const targets = [...new Set((deviceIds || [])
664
+ .map(deviceId => safeString(deviceId, 128))
665
+ .filter(Boolean))]
666
+ .slice(0, 500);
667
+ const now = new Date().toISOString();
668
+ const instruction = safeText(options.instruction, MAX_AGENT_TASK_CHARS);
669
+ const batchId = safeString(options.batchId, 128) || crypto.randomUUID();
670
+ const title = safeString(options.title, 120)
671
+ || safeString(instruction.split(/\r?\n/)[0], 120)
672
+ || 'Remote task batch';
673
+ const batch = {
674
+ batchId,
675
+ title,
676
+ instructionPreview: safeText(instruction, 320),
677
+ approvalLevel: normalizeApprovalLevel(options.approvalLevel),
678
+ status: targets.length > 0 ? 'running' : 'failed',
679
+ requestedAt: now,
680
+ updatedAt: now,
681
+ total: targets.length,
682
+ queued: 0,
683
+ pending: 0,
684
+ completed: 0,
685
+ failed: targets.length > 0 ? 0 : 1,
686
+ timedOut: 0,
687
+ results: targets.map(deviceId => ({
688
+ deviceId,
689
+ ok: false,
690
+ status: 'targeted',
691
+ error: '',
692
+ commandId: '',
693
+ taskId: '',
694
+ approvalLevel: normalizeApprovalLevel(options.approvalLevel),
695
+ updatedAt: now
696
+ }))
697
+ };
698
+
699
+ taskBatches.set(batchId, batch);
700
+ trimTaskBatches();
701
+ return batch;
702
+ }
703
+
704
+ function getTaskBatchItem(batch, deviceId) {
705
+ if (!batch) {
706
+ return null;
707
+ }
708
+
709
+ const id = safeString(deviceId, 128);
710
+ if (!id) {
711
+ return null;
712
+ }
713
+
714
+ let item = batch.results.find(result => result.deviceId === id);
715
+ if (!item) {
716
+ item = {
717
+ deviceId: id,
718
+ ok: false,
719
+ status: 'targeted',
720
+ error: '',
721
+ commandId: '',
722
+ taskId: '',
723
+ approvalLevel: batch.approvalLevel,
724
+ updatedAt: new Date().toISOString()
725
+ };
726
+ batch.results.push(item);
727
+ batch.total = batch.results.length;
728
+ }
729
+
730
+ return item;
731
+ }
732
+
733
+ function recalculateTaskBatch(batch) {
734
+ if (!batch) {
735
+ return null;
736
+ }
737
+
738
+ const results = Array.isArray(batch.results) ? batch.results : [];
739
+ batch.total = results.length;
740
+ batch.queued = results.filter(item => item.ok === true).length;
741
+ batch.completed = results.filter(item => item.status === 'completed').length;
742
+ batch.failed = results.filter(item => item.status === 'failed' || (item.ok === false && !!item.error)).length;
743
+ batch.timedOut = results.filter(item => item.error === 'task-timeout').length;
744
+ batch.pending = results.filter(item =>
745
+ item.ok === true
746
+ && item.status !== 'completed'
747
+ && item.status !== 'failed').length;
748
+ batch.updatedAt = new Date().toISOString();
749
+
750
+ if (batch.total === 0) {
751
+ batch.status = 'failed';
752
+ } else if (batch.completed + batch.failed >= batch.total) {
753
+ batch.status = batch.failed > 0 ? 'completed-with-failures' : 'completed';
754
+ } else if (batch.queued > 0) {
755
+ batch.status = 'running';
756
+ } else {
757
+ batch.status = 'failed';
758
+ }
759
+
760
+ return batch;
761
+ }
762
+
763
+ function syncTaskBatchFromTask(device, task) {
764
+ const batchId = safeString(task?.batchId, 128);
765
+ if (!batchId) {
766
+ return null;
767
+ }
768
+
769
+ const batch = taskBatches.get(batchId);
770
+ if (!batch) {
771
+ return null;
772
+ }
773
+
774
+ const item = getTaskBatchItem(batch, device?.deviceId);
775
+ if (!item) {
776
+ return null;
777
+ }
778
+
779
+ item.ok = true;
780
+ item.status = safeString(task.status, 40) || 'queued';
781
+ item.error = safeString(task.error, 500);
782
+ item.commandId = safeString(task.commandId, 128);
783
+ item.taskId = safeString(task.taskId, 128);
784
+ item.approvalLevel = normalizeApprovalLevel(task.approvalLevel || batch.approvalLevel);
785
+ item.updatedAt = task.updatedAt || task.completedAt || task.sentAt || new Date().toISOString();
786
+ return recalculateTaskBatch(batch);
787
+ }
788
+
789
+ function syncTaskBatchDispatchFailure(batchId, deviceId, error, extra = {}) {
790
+ const batch = taskBatches.get(safeString(batchId, 128));
791
+ if (!batch) {
792
+ return null;
793
+ }
794
+
795
+ const item = getTaskBatchItem(batch, deviceId);
796
+ if (!item) {
797
+ return null;
798
+ }
799
+
800
+ item.ok = false;
801
+ item.status = 'failed';
802
+ item.error = safeString(error, 500) || 'task-dispatch-failed';
803
+ item.commandId = safeString(extra.commandId, 128);
804
+ item.taskId = safeString(extra.taskId, 128);
805
+ item.approvalLevel = normalizeApprovalLevel(extra.approvalLevel || batch.approvalLevel);
806
+ item.updatedAt = new Date().toISOString();
807
+ return recalculateTaskBatch(batch);
808
+ }
809
+
810
+ function clearTaskTimeout(device, commandId) {
811
+ const key = safeString(commandId, 128);
812
+ if (!device?.pendingTaskTimers || !key) {
813
+ return;
814
+ }
815
+
816
+ const timer = device.pendingTaskTimers.get(key);
817
+ if (timer) {
818
+ clearTimeout(timer);
819
+ }
820
+ device.pendingTaskTimers.delete(key);
821
+ }
822
+
823
+ function failPendingTask(device, commandId, error = 'task-timeout') {
824
+ const key = safeString(commandId, 128);
825
+ if (!device?.pendingTaskCommands || !key) {
826
+ return null;
827
+ }
828
+
829
+ const task = device.pendingTaskCommands.get(key);
830
+ if (!task) {
831
+ clearTaskTimeout(device, key);
832
+ return null;
833
+ }
834
+
835
+ const now = new Date().toISOString();
836
+ clearTaskTimeout(device, key);
837
+ task.status = 'failed';
838
+ task.updatedAt = now;
839
+ task.completedAt = now;
840
+ task.error = safeString(error, 500) || 'task-timeout';
841
+ task.resultSummary = task.error;
842
+ task.resultKind = task.resultKind || 'agent-task';
843
+ device.latestTask = task;
844
+ device.pendingTaskCommands.delete(key);
845
+ device.counters.taskResultsReceived += 1;
846
+ device.counters.taskResultsFailed += 1;
847
+ if (task.error === 'task-timeout') {
848
+ device.counters.taskResultsTimedOut += 1;
849
+ }
850
+ syncTaskBatchFromTask(device, task);
851
+ emitRemoteEvent('RemoteTaskResult', device, {
852
+ commandId: key,
853
+ taskId: task.taskId,
854
+ status: task.status,
855
+ error: task.error
856
+ });
857
+ return task;
858
+ }
859
+
860
+ function failAllPendingTasks(device, error = 'device-disconnected') {
861
+ if (!device?.pendingTaskCommands) {
862
+ return 0;
863
+ }
864
+
865
+ const commandIds = [...device.pendingTaskCommands.keys()];
866
+ let failed = 0;
867
+ for (const commandId of commandIds) {
868
+ if (failPendingTask(device, commandId, error)) {
869
+ failed += 1;
870
+ }
871
+ }
872
+ return failed;
873
+ }
874
+
875
+ function scheduleTaskTimeout(device, task) {
876
+ const commandId = safeString(task?.commandId, 128);
877
+ if (!device?.pendingTaskTimers || !commandId) {
878
+ return;
879
+ }
880
+
881
+ clearTaskTimeout(device, commandId);
882
+ const timer = setTimeout(() => {
883
+ failPendingTask(device, commandId, 'task-timeout');
884
+ }, taskTimeoutMs);
885
+ timer.unref?.();
886
+ device.pendingTaskTimers.set(commandId, timer);
887
+ }
888
+
604
889
  function summarizeTaskResult(result) {
605
890
  if (result && typeof result === 'object') {
606
891
  return safeText(
@@ -634,6 +919,7 @@ export function createRemoteHub(options = {}) {
634
919
  const status = error
635
920
  ? 'failed'
636
921
  : safeString(result?.status, 40) || 'completed';
922
+ clearTaskTimeout(device, commandId);
637
923
  task.status = status;
638
924
  task.updatedAt = now;
639
925
  task.completedAt = safeString(result?.completedAt, 80) || now;
@@ -648,6 +934,7 @@ export function createRemoteHub(options = {}) {
648
934
  if (error) {
649
935
  device.counters.taskResultsFailed += 1;
650
936
  }
937
+ syncTaskBatchFromTask(device, task);
651
938
 
652
939
  return task;
653
940
  }
@@ -903,6 +1190,7 @@ export function createRemoteHub(options = {}) {
903
1190
 
904
1191
  async function close() {
905
1192
  for (const device of devices.values()) {
1193
+ failAllPendingTasks(device, 'hub-shutdown');
906
1194
  if (device.socket && !device.socket.destroyed) {
907
1195
  writeJsonLine(device.socket, { type: 'disconnect', reason: 'hub-shutdown' });
908
1196
  device.socket.destroy();
@@ -958,6 +1246,7 @@ export function createRemoteHub(options = {}) {
958
1246
  device.activeLiveStream.stoppedAt = device.disconnectedAt;
959
1247
  device.activeLiveStream.stopReason = reason;
960
1248
  }
1249
+ failAllPendingTasks(device, 'device-disconnected');
961
1250
  emitRemoteEvent('RemoteDeviceDisconnected', device, { reason, synthetic: true });
962
1251
  return true;
963
1252
  }
@@ -1020,6 +1309,10 @@ export function createRemoteHub(options = {}) {
1020
1309
 
1021
1310
  function requestAgentTask(deviceId, options = {}) {
1022
1311
  const device = devices.get(String(deviceId || ''));
1312
+ if (!device) {
1313
+ return { ok: false, error: 'device-not-found' };
1314
+ }
1315
+
1023
1316
  if (device?.synthetic === true && device.connected) {
1024
1317
  const instruction = safeText(options.instruction, MAX_AGENT_TASK_CHARS);
1025
1318
  if (!instruction) {
@@ -1038,6 +1331,7 @@ export function createRemoteHub(options = {}) {
1038
1331
  || safeString(instruction.split(/\r?\n/)[0], 120)
1039
1332
  || 'Remote task';
1040
1333
  const task = {
1334
+ batchId: safeString(options.batchId, 128),
1041
1335
  taskId,
1042
1336
  commandId,
1043
1337
  title,
@@ -1068,6 +1362,7 @@ export function createRemoteHub(options = {}) {
1068
1362
  device.lastSeenAt = now;
1069
1363
  rememberDeviceTask(device, task);
1070
1364
  device.pendingTaskCommands.delete(commandId);
1365
+ syncTaskBatchFromTask(device, task);
1071
1366
  emitRemoteEvent('RemoteTaskQueued', device, {
1072
1367
  commandId,
1073
1368
  taskId,
@@ -1106,6 +1401,7 @@ export function createRemoteHub(options = {}) {
1106
1401
  || safeString(instruction.split(/\r?\n/)[0], 120)
1107
1402
  || 'Remote task';
1108
1403
  const task = {
1404
+ batchId: safeString(options.batchId, 128),
1109
1405
  taskId,
1110
1406
  commandId,
1111
1407
  title,
@@ -1145,6 +1441,8 @@ export function createRemoteHub(options = {}) {
1145
1441
  device.counters.commandsSent += 1;
1146
1442
  device.counters.tasksQueued += 1;
1147
1443
  rememberDeviceTask(device, task);
1444
+ syncTaskBatchFromTask(device, task);
1445
+ scheduleTaskTimeout(device, task);
1148
1446
  emitRemoteEvent('RemoteTaskQueued', device, {
1149
1447
  commandId,
1150
1448
  taskId,
@@ -1154,6 +1452,55 @@ export function createRemoteHub(options = {}) {
1154
1452
  return { ok: true, commandId, taskId, approvalLevel };
1155
1453
  }
1156
1454
 
1455
+ function requestAgentTaskBatch(deviceIds, options = {}) {
1456
+ const targets = [...new Set((deviceIds || [])
1457
+ .map(deviceId => safeString(deviceId, 128))
1458
+ .filter(Boolean))]
1459
+ .slice(0, 500);
1460
+
1461
+ if (targets.length === 0) {
1462
+ return {
1463
+ ok: false,
1464
+ error: 'no-target-devices',
1465
+ total: 0,
1466
+ queued: 0,
1467
+ approvalLevel: normalizeApprovalLevel(options.approvalLevel),
1468
+ results: []
1469
+ };
1470
+ }
1471
+
1472
+ const batch = beginTaskBatch(targets, options);
1473
+ const results = targets.map(deviceId => {
1474
+ const result = requestAgentTask(deviceId, {
1475
+ ...options,
1476
+ batchId: batch.batchId
1477
+ });
1478
+
1479
+ if (result.ok !== true) {
1480
+ syncTaskBatchDispatchFailure(batch.batchId, deviceId, result.error, result);
1481
+ }
1482
+
1483
+ return {
1484
+ deviceId,
1485
+ ...result
1486
+ };
1487
+ });
1488
+
1489
+ const queued = results.filter(result => result.ok === true).length;
1490
+ const updatedBatch = taskBatches.get(batch.batchId) || batch;
1491
+ recalculateTaskBatch(updatedBatch);
1492
+ return {
1493
+ ok: queued > 0,
1494
+ batchId: batch.batchId,
1495
+ total: targets.length,
1496
+ queued,
1497
+ approvalLevel: batch.approvalLevel,
1498
+ results,
1499
+ batch: serializeTaskBatch(updatedBatch),
1500
+ error: queued > 0 ? undefined : 'no-task-queued'
1501
+ };
1502
+ }
1503
+
1157
1504
  function requestThumbnail(deviceId, options = {}) {
1158
1505
  return sendCommand(deviceId, {
1159
1506
  command: 'thumbnail.capture',
@@ -1329,9 +1676,12 @@ export function createRemoteHub(options = {}) {
1329
1676
  close,
1330
1677
  getStatus,
1331
1678
  listDevices,
1679
+ listTaskBatches,
1680
+ getLatestTaskBatch,
1332
1681
  disconnectDevice,
1333
1682
  sendCommand,
1334
1683
  requestAgentTask,
1684
+ requestAgentTaskBatch,
1335
1685
  requestThumbnail,
1336
1686
  startLiveStream,
1337
1687
  stopLiveStream,
@@ -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) {
415
+ function buildMonitorNode(devices, hubStatus, latestTaskBatch = null) {
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';
@@ -433,6 +433,7 @@ function buildMonitorNode(devices, hubStatus) {
433
433
  RemoteFleetConnectedDeviceCount: String(connected),
434
434
  RemoteFleetCanvasPagination: 'none',
435
435
  RemoteFleetDevicesJson: JSON.stringify(devices),
436
+ RemoteFleetLatestTaskBatchJson: latestTaskBatch ? JSON.stringify(latestTaskBatch) : '',
436
437
  RemoteFleetLastError: ''
437
438
  }
438
439
  };
@@ -531,6 +532,31 @@ try {
531
532
  const aiCount = devices.filter(device => device.Connected && device.AiAssistEnabled).length;
532
533
  const focusedDevice = devices.find(device => device.Connected);
533
534
  assert.ok(focusedDevice);
535
+ focusedDevice.LatestTaskId = 'render-smoke-timeout-task';
536
+ focusedDevice.LatestTaskCommandId = 'render-smoke-timeout-command';
537
+ focusedDevice.LatestTaskTitle = 'Timed out smoke task';
538
+ focusedDevice.LatestTaskInstructionPreview = 'This synthetic task timed out before the agent replied.';
539
+ focusedDevice.LatestTaskStatus = 'failed';
540
+ focusedDevice.LatestTaskApprovalLevel = 'task-only';
541
+ focusedDevice.LatestTaskUpdatedAt = new Date().toISOString();
542
+ focusedDevice.LatestTaskError = 'task-timeout';
543
+ focusedDevice.LatestTaskResultSummary = '';
544
+ const latestTaskBatch = {
545
+ batchId: 'render-smoke-batch',
546
+ title: 'Render smoke batch',
547
+ instructionPreview: 'Render smoke batch instruction preview.',
548
+ approvalLevel: 'ai-assist',
549
+ status: 'running',
550
+ requestedAt: new Date(Date.now() - 2500).toISOString(),
551
+ updatedAt: new Date().toISOString(),
552
+ total: 210,
553
+ queued: 210,
554
+ pending: 1,
555
+ completed: 208,
556
+ failed: 1,
557
+ timedOut: 1,
558
+ results: []
559
+ };
534
560
 
535
561
  const { manager, document } = await loadCss3DManager();
536
562
  assert.equal(typeof manager?.renderRemoteFleetMonitorForTest, 'function');
@@ -564,7 +590,7 @@ try {
564
590
  bodyView.dataset.remoteFleetFocusDeviceId = focusedDevice.DeviceId;
565
591
  document.body.appendChild(bodyView);
566
592
 
567
- manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true })));
593
+ manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch));
568
594
 
569
595
  let cards = bodyView.querySelectorAll('article[data-device-id]');
570
596
  assert.equal(cards.length, SYNTHETIC_COUNT);
@@ -577,6 +603,12 @@ try {
577
603
  assert.ok(bodyView.textContent.includes('all devices, no paging'));
578
604
  assert.ok(bodyView.querySelector('code')?.textContent.includes('npx @mindexec/remote connect'));
579
605
  assert.ok(bodyView.textContent.includes('synthetic-render-ai'));
606
+ assert.ok(bodyView.textContent.includes('Task timed out waiting for the agent response.'));
607
+ const batchSummary = bodyView.querySelector('[data-remote-fleet-task-batch="true"]');
608
+ assert.ok(batchSummary);
609
+ assert.ok(batchSummary.textContent.includes('Render smoke batch'));
610
+ assert.ok(batchSummary.textContent.includes('208/210 done'));
611
+ assert.ok(batchSummary.textContent.includes('1 timed out'));
580
612
  assert.ok(devices.some(device => /^synthetic-response-/.test(device.LatestTaskResultResponseId)));
581
613
 
582
614
  const searchInput = bodyView.querySelector('[data-remote-fleet-search="true"]');
@@ -630,6 +662,7 @@ try {
630
662
  assert.ok(deviceBody.textContent.includes(focusedDevice.Name));
631
663
  assert.ok(deviceBody.querySelector('[data-remote-fleet-action="refresh-device"]'));
632
664
  assert.ok(deviceBody.querySelector('[data-remote-fleet-action="task-device"]'));
665
+ assert.ok(deviceBody.textContent.includes('Task timed out waiting for the agent response.'));
633
666
  assert.ok(deviceBody.textContent.includes(focusedDevice.DeviceId));
634
667
 
635
668
  console.log(`RemoteFleet render smoke OK (${SYNTHETIC_COUNT} synthetic devices, ${connectedCount} connected, ${aiCount} AI-ready)`);
@@ -77,13 +77,20 @@ try {
77
77
  .filter(device => device.connected && device.capabilities?.taskDispatch)
78
78
  .slice(0, 200);
79
79
  assert.ok(connectedTargets.length >= 100);
80
- const taskResults = connectedTargets.map(device =>
81
- hub.requestAgentTask(device.deviceId, {
82
- instruction: 'Synthetic scale task: report current status to the manager.',
83
- title: 'Scale task'
84
- }));
85
- assert.equal(taskResults.every(result => result.ok === true), true);
86
- assert.equal(taskResults.length, connectedTargets.length);
80
+ const scaleBatch = hub.requestAgentTaskBatch(connectedTargets.map(device => device.deviceId), {
81
+ instruction: 'Synthetic scale task: report current status to the manager.',
82
+ title: 'Scale task batch'
83
+ });
84
+ assert.equal(scaleBatch.ok, true);
85
+ assert.equal(scaleBatch.total, connectedTargets.length);
86
+ assert.equal(scaleBatch.queued, connectedTargets.length);
87
+ assert.equal(scaleBatch.results.every(result => result.ok === true), true);
88
+ assert.equal(scaleBatch.batch?.completed, connectedTargets.length);
89
+ assert.equal(scaleBatch.batch?.failed, 0);
90
+ assert.equal(scaleBatch.batch?.status, 'completed');
91
+ const latestBatch = hub.getLatestTaskBatch();
92
+ assert.equal(latestBatch?.batchId, scaleBatch.batchId);
93
+ assert.equal(latestBatch?.completed, connectedTargets.length);
87
94
 
88
95
  const aiTarget = hub.listDevices().find(device =>
89
96
  device.connected && device.capabilities?.taskDispatch && device.capabilities?.aiAssist);
@@ -30,7 +30,8 @@ const hub = createRemoteHub({
30
30
  MINDEXEC_REMOTE_HUB: '1',
31
31
  REMOTE_HUB_HOST: '127.0.0.1',
32
32
  REMOTE_HUB_PORT: '0',
33
- REMOTE_HUB_PAIR_TOKEN: 'smoke-token'
33
+ REMOTE_HUB_PAIR_TOKEN: 'smoke-token',
34
+ REMOTE_HUB_TASK_TIMEOUT_MS: '120'
34
35
  }
35
36
  });
36
37
 
@@ -230,6 +231,63 @@ try {
230
231
  assert.equal(aiTaskDevice.counters.tasksQueued, 2);
231
232
  assert.equal(aiTaskDevice.counters.taskResultsReceived, 2);
232
233
 
234
+ const batchTask = hub.requestAgentTaskBatch(['smoke-device', 'missing-smoke-device'], {
235
+ instruction: 'Dispatch a smoke batch to one connected and one missing device.',
236
+ title: 'Smoke batch'
237
+ });
238
+ assert.equal(batchTask.ok, true);
239
+ assert.equal(batchTask.total, 2);
240
+ assert.equal(batchTask.queued, 1);
241
+ assert.ok(batchTask.batchId);
242
+ assert.equal(batchTask.batch?.pending, 1);
243
+ assert.equal(batchTask.batch?.failed, 1);
244
+ assert.equal(batchTask.batch?.status, 'running');
245
+ const acceptedBatchItem = batchTask.results.find(result => result.deviceId === 'smoke-device');
246
+ assert.ok(acceptedBatchItem?.commandId);
247
+ writeJsonLine(socket, {
248
+ type: 'command.result',
249
+ commandId: acceptedBatchItem.commandId,
250
+ result: {
251
+ kind: 'agent.task',
252
+ taskId: acceptedBatchItem.taskId,
253
+ status: 'completed',
254
+ summary: 'Smoke batch item accepted.',
255
+ completedAt: new Date().toISOString()
256
+ }
257
+ });
258
+
259
+ const completedBatch = await waitFor(() => {
260
+ const latest = hub.getLatestTaskBatch();
261
+ return latest?.batchId === batchTask.batchId
262
+ && latest.completed === 1
263
+ && latest.failed === 1
264
+ ? latest
265
+ : null;
266
+ });
267
+ assert.equal(completedBatch.pending, 0);
268
+ assert.equal(completedBatch.status, 'completed-with-failures');
269
+ assert.equal(completedBatch.results.length, 2);
270
+ assert.equal(completedBatch.results.find(result => result.deviceId === 'missing-smoke-device')?.error, 'device-not-found');
271
+
272
+ const timeoutTaskCommand = hub.requestAgentTask('smoke-device', {
273
+ instruction: 'This task intentionally receives no agent response.',
274
+ title: 'Timeout task'
275
+ });
276
+ assert.equal(timeoutTaskCommand.ok, true);
277
+ const timeoutTaskDevice = await waitFor(() => {
278
+ const current = hub.listDevices();
279
+ return current[0]?.latestTask?.taskId === timeoutTaskCommand.taskId
280
+ && current[0]?.latestTask?.status === 'failed'
281
+ ? current[0]
282
+ : null;
283
+ }, 1000);
284
+ assert.equal(timeoutTaskDevice.latestTask.error, 'task-timeout');
285
+ assert.equal(timeoutTaskDevice.latestTask.resultSummary, 'task-timeout');
286
+ assert.equal(timeoutTaskDevice.counters.tasksQueued, 4);
287
+ assert.equal(timeoutTaskDevice.counters.taskResultsReceived, 4);
288
+ assert.equal(timeoutTaskDevice.counters.taskResultsFailed, 1);
289
+ assert.equal(timeoutTaskDevice.counters.taskResultsTimedOut, 1);
290
+
233
291
  socket.destroy();
234
292
  await waitFor(() => hub.listDevices()[0]?.connected === false);
235
293
  console.log('RemoteHub smoke OK');
package/server.js CHANGED
@@ -6969,6 +6969,8 @@ app.get('/api/remote/devices', (req, res) => {
6969
6969
  total: devices.length,
6970
6970
  pagination: 'none',
6971
6971
  canvasDeviceListMode: 'all-devices',
6972
+ latestTaskBatch: remoteHub.getLatestTaskBatch(),
6973
+ recentTaskBatches: remoteHub.listTaskBatches(),
6972
6974
  devices
6973
6975
  });
6974
6976
  });
@@ -7040,23 +7042,20 @@ app.post('/api/remote/tasks', (req, res) => {
7040
7042
  ? requestedDeviceIds
7041
7043
  : (allConnected ? devices.filter(device => device.connected).map(device => device.deviceId) : []);
7042
7044
  const uniqueTargetIds = [...new Set(targetIds)].slice(0, 500);
7043
- const results = uniqueTargetIds.map(deviceId => ({
7044
- deviceId,
7045
- ...remoteHub.requestAgentTask(deviceId, {
7046
- instruction: req.body?.instruction,
7047
- title: req.body?.title,
7048
- approvalLevel: req.body?.approvalLevel,
7049
- model: req.body?.model
7050
- })
7051
- }));
7052
- const queued = results.filter(result => result.ok === true).length;
7045
+ const result = remoteHub.requestAgentTaskBatch(uniqueTargetIds, {
7046
+ instruction: req.body?.instruction,
7047
+ title: req.body?.title,
7048
+ approvalLevel: req.body?.approvalLevel,
7049
+ model: req.body?.model,
7050
+ batchId: req.body?.batchId
7051
+ });
7053
7052
  res.json({
7054
- ok: queued > 0,
7053
+ ...result,
7054
+ ok: result.ok === true,
7055
7055
  total: uniqueTargetIds.length,
7056
- queued,
7056
+ queued: result.queued || 0,
7057
7057
  approvalLevel: req.body?.approvalLevel === 'ai-assist' ? 'ai-assist' : 'task-only',
7058
- results,
7059
- error: queued > 0 ? undefined : (uniqueTargetIds.length === 0 ? 'no-target-devices' : 'no-task-queued')
7058
+ error: result.ok === true ? undefined : (uniqueTargetIds.length === 0 ? 'no-target-devices' : (result.error || 'no-task-queued'))
7060
7059
  });
7061
7060
  });
7062
7061
 
@@ -11975,6 +11975,22 @@
11975
11975
  }
11976
11976
  }
11977
11977
 
11978
+ function parseRemoteFleetLatestTaskBatch(nodeModel) {
11979
+ const raw = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLatestTaskBatchJson', '');
11980
+ if (!raw.trim()) {
11981
+ return null;
11982
+ }
11983
+
11984
+ try {
11985
+ const parsed = JSON.parse(raw);
11986
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
11987
+ ? parsed
11988
+ : null;
11989
+ } catch {
11990
+ return null;
11991
+ }
11992
+ }
11993
+
11978
11994
  function parseRemoteFleetPinnedDevice(nodeModel) {
11979
11995
  const raw = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetPinnedDeviceJson', '{}');
11980
11996
  if (!raw.trim()) {
@@ -12204,6 +12220,22 @@
12204
12220
  return getRemoteFleetDeviceField(device, 'thumbnailEnabled', 'ThumbnailEnabled', false) === true;
12205
12221
  }
12206
12222
 
12223
+ function formatRemoteFleetTaskError(error) {
12224
+ const value = String(error || '').trim();
12225
+ switch (value) {
12226
+ case 'task-timeout':
12227
+ return 'Task timed out waiting for the agent response.';
12228
+ case 'device-disconnected':
12229
+ return 'Agent disconnected before returning a task result.';
12230
+ case 'replaced-by-new-session':
12231
+ return 'Agent reconnected before returning a task result.';
12232
+ case 'hub-shutdown':
12233
+ return 'RemoteHub shut down before the task completed.';
12234
+ default:
12235
+ return value;
12236
+ }
12237
+ }
12238
+
12207
12239
  function getRemoteFleetGroupInfo(device, groupMode) {
12208
12240
  const mode = String(groupMode || 'none');
12209
12241
  const connected = isRemoteFleetDeviceConnected(device);
@@ -12601,7 +12633,7 @@
12601
12633
  : taskLine.textContent;
12602
12634
  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;`;
12603
12635
  const taskSummary = document.createElement('div');
12604
- taskSummary.textContent = latestTaskError || latestTaskResult || latestTaskTitle || 'Task queued';
12636
+ taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
12605
12637
  taskSummary.title = taskSummary.textContent;
12606
12638
  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;';
12607
12639
  taskBox.appendChild(taskLine);
@@ -12852,6 +12884,7 @@
12852
12884
  const aiAssistState = bodyView.dataset.remoteFleetAiAssist === 'true';
12853
12885
  const autoMonitorState = bodyView.dataset.remoteFleetAutoMonitor !== 'false';
12854
12886
  const focusState = bodyView.dataset.remoteFleetFocusDeviceId || '';
12887
+ const latestTaskBatch = parseRemoteFleetLatestTaskBatch(nodeModel);
12855
12888
  const sortedDevices = [...parseRemoteFleetDevices(nodeModel)]
12856
12889
  .sort((left, right) => compareRemoteFleetDevices(left, right, sortState));
12857
12890
  const devices = groupState === 'none'
@@ -13159,6 +13192,64 @@
13159
13192
  `;
13160
13193
  bodyView.appendChild(taskFeedback);
13161
13194
 
13195
+ if (latestTaskBatch) {
13196
+ const batchTotal = Number(latestTaskBatch.total ?? latestTaskBatch.Total ?? 0);
13197
+ const batchQueued = Number(latestTaskBatch.queued ?? latestTaskBatch.Queued ?? 0);
13198
+ const batchPending = Number(latestTaskBatch.pending ?? latestTaskBatch.Pending ?? 0);
13199
+ const batchCompleted = Number(latestTaskBatch.completed ?? latestTaskBatch.Completed ?? 0);
13200
+ const batchFailed = Number(latestTaskBatch.failed ?? latestTaskBatch.Failed ?? 0);
13201
+ const batchTimedOut = Number(latestTaskBatch.timedOut ?? latestTaskBatch.TimedOut ?? 0);
13202
+ const batchStatus = String(latestTaskBatch.status ?? latestTaskBatch.Status ?? '').trim();
13203
+ const batchTitle = String(latestTaskBatch.title ?? latestTaskBatch.Title ?? 'Remote task batch').trim();
13204
+ const batchUpdatedAt = String(latestTaskBatch.updatedAt ?? latestTaskBatch.UpdatedAt ?? latestTaskBatch.requestedAt ?? latestTaskBatch.RequestedAt ?? '').trim();
13205
+ const doneCount = Math.max(0, batchCompleted + batchFailed);
13206
+ const progress = batchTotal > 0 ? Math.max(0, Math.min(100, Math.round(doneCount * 100 / batchTotal))) : 0;
13207
+ const batchTone = batchFailed > 0
13208
+ ? 'error'
13209
+ : batchPending > 0
13210
+ ? 'pending'
13211
+ : 'done';
13212
+ const batchBox = document.createElement('div');
13213
+ batchBox.dataset.remoteFleetTaskBatch = 'true';
13214
+ batchBox.style.cssText = `
13215
+ flex: 0 0 auto;
13216
+ display: grid;
13217
+ grid-template-columns: minmax(0, 1fr) auto;
13218
+ gap: 8px 12px;
13219
+ align-items: center;
13220
+ padding: 8px 10px;
13221
+ border-radius: 8px;
13222
+ background: ${batchTone === 'error' ? 'rgba(248, 113, 113, 0.11)' : batchTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
13223
+ border: 1px solid ${batchTone === 'error' ? 'rgba(248, 113, 113, 0.24)' : batchTone === 'done' ? 'rgba(16, 185, 129, 0.20)' : 'rgba(37, 99, 235, 0.16)'};
13224
+ min-width: 0;
13225
+ `;
13226
+ const batchMain = document.createElement('div');
13227
+ batchMain.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:5px;';
13228
+ const batchLabel = document.createElement('div');
13229
+ batchLabel.textContent = `${batchTitle} - ${batchStatus || 'running'}${batchUpdatedAt ? ` - ${formatRemoteFleetAge(batchUpdatedAt)}` : ''}`;
13230
+ batchLabel.title = batchLabel.textContent;
13231
+ batchLabel.style.cssText = `color:${batchTone === 'error' ? '#991b1b' : batchTone === 'done' ? '#047857' : '#1d4ed8'};font-size:11px;font-weight:950;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
13232
+ const progressTrack = document.createElement('div');
13233
+ progressTrack.style.cssText = 'position:relative;height:7px;border-radius:999px;background:rgba(148,163,184,0.22);overflow:hidden;';
13234
+ const progressBar = document.createElement('div');
13235
+ progressBar.style.cssText = `
13236
+ width: ${progress}%;
13237
+ height: 100%;
13238
+ border-radius: inherit;
13239
+ background: ${batchTone === 'error' ? '#ef4444' : batchTone === 'done' ? '#10b981' : '#2563eb'};
13240
+ `;
13241
+ progressTrack.appendChild(progressBar);
13242
+ batchMain.appendChild(batchLabel);
13243
+ batchMain.appendChild(progressTrack);
13244
+ const batchStats = document.createElement('div');
13245
+ batchStats.textContent = `${batchCompleted}/${batchTotal || batchQueued || 0} done - ${batchPending} pending - ${batchFailed} failed${batchTimedOut > 0 ? ` - ${batchTimedOut} timed out` : ''}`;
13246
+ batchStats.title = batchStats.textContent;
13247
+ batchStats.style.cssText = 'color:#334155;font-size:10px;font-weight:900;line-height:1.25;white-space:nowrap;letter-spacing:0;';
13248
+ batchBox.appendChild(batchMain);
13249
+ batchBox.appendChild(batchStats);
13250
+ bodyView.appendChild(batchBox);
13251
+ }
13252
+
13162
13253
  if (focusedDevice) {
13163
13254
  const focusedId = getRemoteFleetDeviceId(focusedDevice);
13164
13255
  const focusedName = getRemoteFleetDeviceName(focusedDevice);
@@ -13641,7 +13732,7 @@
13641
13732
  letter-spacing: 0;
13642
13733
  `;
13643
13734
  const taskSummary = document.createElement('div');
13644
- taskSummary.textContent = latestTaskError || latestTaskResult || latestTaskTitle || 'Task queued';
13735
+ taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
13645
13736
  taskSummary.title = taskSummary.textContent;
13646
13737
  taskSummary.style.cssText = `
13647
13738
  color: #334155;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-SDDoFz4/uzWopUhjgpkiIgiLuIxFbjw0fze0ljEluiQ=",
4
+ "hash": "sha256-x+zuHEgludyG90VHm7vAvh24gxrcXgpLsHEMSmn3EZA=",
5
5
  "fingerprinting": {
6
6
  "Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
7
7
  "Markdig.d1j7v41cl1.dll": "Markdig.dll",
@@ -123,16 +123,16 @@
123
123
  "System.m05i39uvk9.dll": "System.dll",
124
124
  "netstandard.0xet7jg7ky.dll": "netstandard.dll",
125
125
  "System.Private.CoreLib.rkafq04oma.dll": "System.Private.CoreLib.dll",
126
- "MindExecution.Core.q469sducjw.dll": "MindExecution.Core.dll",
127
- "MindExecution.Kernel.lqglgq2jmo.dll": "MindExecution.Kernel.dll",
128
- "MindExecution.Plugins.Admin.2jqptn6ylw.dll": "MindExecution.Plugins.Admin.dll",
129
- "MindExecution.Plugins.Business.8hzwity3fl.dll": "MindExecution.Plugins.Business.dll",
130
- "MindExecution.Plugins.Concept.gc38fmee62.dll": "MindExecution.Plugins.Concept.dll",
131
- "MindExecution.Plugins.Directory.hr2p0grenr.dll": "MindExecution.Plugins.Directory.dll",
132
- "MindExecution.Plugins.PlanMaster.ivqd47y3cx.dll": "MindExecution.Plugins.PlanMaster.dll",
133
- "MindExecution.Plugins.YouTube.tpgm4p80hh.dll": "MindExecution.Plugins.YouTube.dll",
134
- "MindExecution.Shared.2r4r7zx1ek.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.stbecd1tk9.dll": "MindExecution.Web.dll",
126
+ "MindExecution.Core.ckgjbdvvxl.dll": "MindExecution.Core.dll",
127
+ "MindExecution.Kernel.lm7kgq8l8h.dll": "MindExecution.Kernel.dll",
128
+ "MindExecution.Plugins.Admin.k085kt3dls.dll": "MindExecution.Plugins.Admin.dll",
129
+ "MindExecution.Plugins.Business.dsz9uwkxih.dll": "MindExecution.Plugins.Business.dll",
130
+ "MindExecution.Plugins.Concept.6gcazss69r.dll": "MindExecution.Plugins.Concept.dll",
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",
136
136
  "dotnet.js": "dotnet.js",
137
137
  "dotnet.native.xsn1d6x2kd.js": "dotnet.native.js",
138
138
  "dotnet.native.vz0adxojrz.wasm": "dotnet.native.wasm",
@@ -278,18 +278,18 @@
278
278
  "System.Xml.XDocument.c539ki6cuq.dll": "sha256-MPTRJkptrL9nGa2tl4kF46+wErNUYRPCGblX3ANoKoY=",
279
279
  "System.m05i39uvk9.dll": "sha256-5jDfIdbYAigw7/Q/lMzt5W/+cayGbW9ko9FvuaN1GsQ=",
280
280
  "netstandard.0xet7jg7ky.dll": "sha256-xENDv620uJ8fHwLJ2bdhrTHz4QPjvqXOztnk2a4wr0c=",
281
- "MindExecution.Core.q469sducjw.dll": "sha256-tqgvXepOZPfdkyxH1BK3KlhL8azJjoV+iV/BismieCc=",
282
- "MindExecution.Kernel.lqglgq2jmo.dll": "sha256-AyAPsZpAwsH/HeC6cWVzRH50hBjGAoamzGup6clWJTg=",
283
- "MindExecution.Plugins.Concept.gc38fmee62.dll": "sha256-EFZnHpuHNJzdkWPTkGXV7EYsZIkpMMOAHf6R5FBGzQ0=",
284
- "MindExecution.Plugins.PlanMaster.ivqd47y3cx.dll": "sha256-slYqPeIeQ3yugnSA4ABLJ1eGGd7opYVqd11waq+hJBo=",
285
- "MindExecution.Shared.2r4r7zx1ek.dll": "sha256-znMYyuG18GNFULfWrkwczymEkg1ZtSPHpHXKkw13h2A=",
286
- "MindExecution.Web.stbecd1tk9.dll": "sha256-oJwTIFv6bY3dnlzZQafAf01epgSK3gOAbo2XK7nLaQA="
281
+ "MindExecution.Core.ckgjbdvvxl.dll": "sha256-ZDas50sgMQGCNt0UC1ZErP+uv7PQ7z/skK7zCwnmU2I=",
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="
287
287
  },
288
288
  "lazyAssembly": {
289
- "MindExecution.Plugins.Admin.2jqptn6ylw.dll": "sha256-ctRNgHtUfPq0pCvAeG0DCzZPBRTgLtQC2c4pjGFM+Go=",
290
- "MindExecution.Plugins.Business.8hzwity3fl.dll": "sha256-ZL74Jut/WgaNdDBWZzhC6ECsjBdm+0cB1P5fx4ACugM=",
291
- "MindExecution.Plugins.Directory.hr2p0grenr.dll": "sha256-PUrdpFqGptJ5kxYaarWNwB2NmZ1TphHnFHlnyQL48DI=",
292
- "MindExecution.Plugins.YouTube.tpgm4p80hh.dll": "sha256-3/nB2+iWQdfNdzktROztp/oVkSJbxu64mmWRShW2QMo="
289
+ "MindExecution.Plugins.Admin.k085kt3dls.dll": "sha256-LLDLGzE3yaP+rL+8CMZGWBmGQZBfrRWgUMijX/fxTxw=",
290
+ "MindExecution.Plugins.Business.dsz9uwkxih.dll": "sha256-bfvFu7Pq58deN7TnwfhsUZdvWhS4RpA0LdO77UsBnY8=",
291
+ "MindExecution.Plugins.Directory.ufbriq6js4.dll": "sha256-qoxaegrVr9oaXJgQqU7t7CrCf0EYgsd22BcWXg/+91I=",
292
+ "MindExecution.Plugins.YouTube.5ojg8vbf9w.dll": "sha256-a+S4Xha/L7um0foEwb8E0p8VqEw21M6ETzjrEUpIYTk="
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-dispatch-feedback-v474';
561
+ const scriptVersion = '20260612-remote-task-batch-v476';
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": "0KR50DAc",
2
+ "version": "wUCVSTgn",
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-E87osNoVqhikMqj3XaJ+fZpNqyY0377QlQIjHfzZPhc=",
89
+ "hash": "sha256-MaP05yuNb/XsfihViUh/bL9svf11oMZGMZiAWnpm/Gk=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -410,44 +410,44 @@
410
410
  "url": "_framework/MimeMapping.og9ys58ylm.dll"
411
411
  },
412
412
  {
413
- "hash": "sha256-tqgvXepOZPfdkyxH1BK3KlhL8azJjoV+iV/BismieCc=",
414
- "url": "_framework/MindExecution.Core.q469sducjw.dll"
413
+ "hash": "sha256-ZDas50sgMQGCNt0UC1ZErP+uv7PQ7z/skK7zCwnmU2I=",
414
+ "url": "_framework/MindExecution.Core.ckgjbdvvxl.dll"
415
415
  },
416
416
  {
417
- "hash": "sha256-AyAPsZpAwsH/HeC6cWVzRH50hBjGAoamzGup6clWJTg=",
418
- "url": "_framework/MindExecution.Kernel.lqglgq2jmo.dll"
417
+ "hash": "sha256-ndscepu8d8g/nrJmBP8VrkIOTIkSA57t5shOq/rp/ys=",
418
+ "url": "_framework/MindExecution.Kernel.lm7kgq8l8h.dll"
419
419
  },
420
420
  {
421
- "hash": "sha256-ctRNgHtUfPq0pCvAeG0DCzZPBRTgLtQC2c4pjGFM+Go=",
422
- "url": "_framework/MindExecution.Plugins.Admin.2jqptn6ylw.dll"
421
+ "hash": "sha256-LLDLGzE3yaP+rL+8CMZGWBmGQZBfrRWgUMijX/fxTxw=",
422
+ "url": "_framework/MindExecution.Plugins.Admin.k085kt3dls.dll"
423
423
  },
424
424
  {
425
- "hash": "sha256-ZL74Jut/WgaNdDBWZzhC6ECsjBdm+0cB1P5fx4ACugM=",
426
- "url": "_framework/MindExecution.Plugins.Business.8hzwity3fl.dll"
425
+ "hash": "sha256-bfvFu7Pq58deN7TnwfhsUZdvWhS4RpA0LdO77UsBnY8=",
426
+ "url": "_framework/MindExecution.Plugins.Business.dsz9uwkxih.dll"
427
427
  },
428
428
  {
429
- "hash": "sha256-EFZnHpuHNJzdkWPTkGXV7EYsZIkpMMOAHf6R5FBGzQ0=",
430
- "url": "_framework/MindExecution.Plugins.Concept.gc38fmee62.dll"
429
+ "hash": "sha256-hpCyKxiQxk50zHE8iOWtoIiBapBG83wfaos3jZzeaYg=",
430
+ "url": "_framework/MindExecution.Plugins.Concept.6gcazss69r.dll"
431
431
  },
432
432
  {
433
- "hash": "sha256-PUrdpFqGptJ5kxYaarWNwB2NmZ1TphHnFHlnyQL48DI=",
434
- "url": "_framework/MindExecution.Plugins.Directory.hr2p0grenr.dll"
433
+ "hash": "sha256-qoxaegrVr9oaXJgQqU7t7CrCf0EYgsd22BcWXg/+91I=",
434
+ "url": "_framework/MindExecution.Plugins.Directory.ufbriq6js4.dll"
435
435
  },
436
436
  {
437
- "hash": "sha256-slYqPeIeQ3yugnSA4ABLJ1eGGd7opYVqd11waq+hJBo=",
438
- "url": "_framework/MindExecution.Plugins.PlanMaster.ivqd47y3cx.dll"
437
+ "hash": "sha256-iwATrRDt3Wwr/DKLxUl7GzqwKdhMLZNBKYVJPAkrhdM=",
438
+ "url": "_framework/MindExecution.Plugins.PlanMaster.zqc0kv9n5d.dll"
439
439
  },
440
440
  {
441
- "hash": "sha256-3/nB2+iWQdfNdzktROztp/oVkSJbxu64mmWRShW2QMo=",
442
- "url": "_framework/MindExecution.Plugins.YouTube.tpgm4p80hh.dll"
441
+ "hash": "sha256-a+S4Xha/L7um0foEwb8E0p8VqEw21M6ETzjrEUpIYTk=",
442
+ "url": "_framework/MindExecution.Plugins.YouTube.5ojg8vbf9w.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-znMYyuG18GNFULfWrkwczymEkg1ZtSPHpHXKkw13h2A=",
446
- "url": "_framework/MindExecution.Shared.2r4r7zx1ek.dll"
445
+ "hash": "sha256-HlFaI9iZybI0VDe6KpsgRqLb3GTfUVXoU3KMlx0gQ0o=",
446
+ "url": "_framework/MindExecution.Shared.ahf7j9dymw.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-oJwTIFv6bY3dnlzZQafAf01epgSK3gOAbo2XK7nLaQA=",
450
- "url": "_framework/MindExecution.Web.stbecd1tk9.dll"
449
+ "hash": "sha256-IlDxFyeBhZWgici5UD0RtmqRNwynyD6n9Q0IJ8f4eBE=",
450
+ "url": "_framework/MindExecution.Web.6i9vy7xy1k.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-y6Kp7jPu3NDwDsVpfK/DXAaN7ZSNGvq3LDKrIhURBio=",
773
+ "hash": "sha256-fGH3vBiA9K4LY1+siDEi6C1uyYfJgDAywmyOxn+xGY8=",
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-kK0fdbwqbRSdpNq5r4LyomSjE6FQ2lhjrXkQVBWacL8=",
837
+ "hash": "sha256-2i4bXinqysl6fMxtRowvbfVpJyKIfPSm3wdbCBcX5i4=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: 0KR50DAc */
1
+ /* Manifest version: wUCVSTgn */
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