@mindexec/cli 0.2.3 → 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 (22) hide show
  1. package/README.md +12 -0
  2. package/package.json +1 -1
  3. package/remote-hub.js +167 -1
  4. package/scripts/remote-hub-smoke.mjs +30 -1
  5. package/server.js +36 -0
  6. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +182 -0
  7. package/wwwroot/_framework/MindExecution.Core.rydw4mhsbd.dll +0 -0
  8. package/wwwroot/_framework/{MindExecution.Kernel.mot9nj6bzm.dll → MindExecution.Kernel.8sz1fl3k6s.dll} +0 -0
  9. package/wwwroot/_framework/{MindExecution.Plugins.Admin.x9v2drg2f7.dll → MindExecution.Plugins.Admin.iltai5c3i9.dll} +0 -0
  10. package/wwwroot/_framework/{MindExecution.Plugins.Business.b0kjoyx31x.dll → MindExecution.Plugins.Business.mscgb1gwpf.dll} +0 -0
  11. package/wwwroot/_framework/{MindExecution.Plugins.Concept.6tojojgh1a.dll → MindExecution.Plugins.Concept.s888y8snr4.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Directory.fqtbuqadsx.dll → MindExecution.Plugins.Directory.281klijdzl.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.j7llfeae6l.dll → MindExecution.Plugins.PlanMaster.2gy2ozelqp.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.yo5fwdhugr.dll → MindExecution.Plugins.YouTube.1v8o9nnlzq.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Shared.0qi7vbn9a4.dll → MindExecution.Shared.04anisxh35.dll} +0 -0
  16. package/wwwroot/_framework/MindExecution.Web.0qdidsf6sl.dll +0 -0
  17. package/wwwroot/_framework/blazor.boot.json +21 -21
  18. package/wwwroot/index.html +1 -1
  19. package/wwwroot/service-worker-assets.js +24 -24
  20. package/wwwroot/service-worker.js +1 -1
  21. package/wwwroot/_framework/MindExecution.Core.5luow1xgjs.dll +0 -0
  22. package/wwwroot/_framework/MindExecution.Web.6cv7ad7rik.dll +0 -0
package/README.md CHANGED
@@ -83,6 +83,18 @@ curl -X POST -H "X-Bridge-Token: <bridge-token>" http://127.0.0.1:5147/api/remot
83
83
  curl -H "X-Bridge-Token: <bridge-token>" http://127.0.0.1:5147/api/remote/devices/<device-id>/thumbnail
84
84
  ```
85
85
 
86
+ Queue a safe task-only instruction for one device or all connected devices:
87
+
88
+ ```bash
89
+ curl -X POST -H "X-Bridge-Token: <bridge-token>" -H "Content-Type: application/json" \
90
+ -d "{\"instruction\":\"Check the desktop and report status.\"}" \
91
+ http://127.0.0.1:5147/api/remote/devices/<device-id>/tasks
92
+
93
+ curl -X POST -H "X-Bridge-Token: <bridge-token>" -H "Content-Type: application/json" \
94
+ -d "{\"instruction\":\"Prepare a short status report.\",\"allConnected\":true}" \
95
+ http://127.0.0.1:5147/api/remote/tasks
96
+ ```
97
+
86
98
  ?먮뒗 ?꾩뿭 ?ㅼ튂 ???ㅽ뻾?⑸땲??
87
99
 
88
100
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
package/remote-hub.js CHANGED
@@ -7,6 +7,9 @@ const DEFAULT_REMOTE_HUB_HOST = '127.0.0.1';
7
7
  const DEFAULT_HEARTBEAT_MS = 5000;
8
8
  const MAX_LINE_CHARS = 4 * 1024 * 1024;
9
9
  const MAX_THUMBNAIL_BASE64_CHARS = 3 * 1024 * 1024;
10
+ const MAX_AGENT_TASK_CHARS = 4000;
11
+ const MAX_AGENT_TASK_RESULT_CHARS = 3000;
12
+ const RECENT_TASK_LIMIT = 12;
10
13
  const REMOTE_PROTOCOL_VERSION = 1;
11
14
 
12
15
  function isEnabledValue(value, fallback = true) {
@@ -38,6 +41,10 @@ function safeString(value, maxLength = 200) {
38
41
  return String(value ?? '').replace(/[\r\n\t]/g, ' ').trim().slice(0, maxLength);
39
42
  }
40
43
 
44
+ function safeText(value, maxLength = 1000) {
45
+ return String(value ?? '').replace(/\0/g, '').trim().slice(0, maxLength);
46
+ }
47
+
41
48
  function normalizeDeviceId(value) {
42
49
  const id = safeString(value, 128).replace(/[^a-zA-Z0-9_.:-]/g, '-');
43
50
  return id || crypto.randomUUID();
@@ -95,6 +102,10 @@ function serializeDevice(device) {
95
102
  remoteAddress: device.remoteAddress,
96
103
  remotePort: device.remotePort,
97
104
  latestThumbnail: device.latestThumbnail ? { ...device.latestThumbnail } : null,
105
+ latestTask: device.latestTask ? { ...device.latestTask } : null,
106
+ recentTasks: Array.isArray(device.recentTasks)
107
+ ? device.recentTasks.map(task => ({ ...task }))
108
+ : [],
98
109
  status: { ...device.status },
99
110
  counters: { ...device.counters }
100
111
  };
@@ -222,13 +233,19 @@ export function createRemoteHub(options = {}) {
222
233
  remotePort: socket.remotePort || 0,
223
234
  status: {},
224
235
  latestThumbnail: null,
236
+ latestTask: null,
237
+ recentTasks: [],
238
+ pendingTaskCommands: new Map(),
225
239
  counters: {
226
240
  messagesReceived: 1,
227
241
  statusReceived: 0,
228
242
  commandsSent: 0,
229
243
  commandResultsReceived: 0,
230
244
  thumbnailFramesReceived: 0,
231
- thumbnailFramesDropped: 0
245
+ thumbnailFramesDropped: 0,
246
+ tasksQueued: 0,
247
+ taskResultsReceived: 0,
248
+ taskResultsFailed: 0
232
249
  }
233
250
  };
234
251
 
@@ -268,6 +285,79 @@ export function createRemoteHub(options = {}) {
268
285
  emitRemoteEvent('RemoteDeviceDisconnected', device, { reason });
269
286
  }
270
287
 
288
+ function rememberDeviceTask(device, task) {
289
+ if (!device || !task) {
290
+ return null;
291
+ }
292
+
293
+ const existingIndex = device.recentTasks.findIndex(item =>
294
+ item.taskId === task.taskId || item.commandId === task.commandId);
295
+ if (existingIndex >= 0) {
296
+ device.recentTasks.splice(existingIndex, 1);
297
+ }
298
+
299
+ device.recentTasks.unshift(task);
300
+ if (device.recentTasks.length > RECENT_TASK_LIMIT) {
301
+ device.recentTasks.length = RECENT_TASK_LIMIT;
302
+ }
303
+
304
+ device.latestTask = task;
305
+ if (task.commandId) {
306
+ device.pendingTaskCommands.set(task.commandId, task);
307
+ }
308
+
309
+ return task;
310
+ }
311
+
312
+ function summarizeTaskResult(result) {
313
+ if (result && typeof result === 'object') {
314
+ return safeText(
315
+ result.summary
316
+ || result.message
317
+ || result.output
318
+ || result.result
319
+ || JSON.stringify(result),
320
+ MAX_AGENT_TASK_RESULT_CHARS);
321
+ }
322
+
323
+ return safeText(result ?? '', MAX_AGENT_TASK_RESULT_CHARS);
324
+ }
325
+
326
+ function applyTaskResult(device, commandId, result, error) {
327
+ if (!device || !commandId) {
328
+ return null;
329
+ }
330
+
331
+ const resultTaskId = result && typeof result === 'object'
332
+ ? safeString(result.taskId, 128)
333
+ : '';
334
+ const task = resultTaskId
335
+ ? device.recentTasks.find(item => item.taskId === resultTaskId)
336
+ : device.pendingTaskCommands.get(commandId);
337
+ if (!task) {
338
+ return null;
339
+ }
340
+
341
+ const now = device.lastSeenAt || new Date().toISOString();
342
+ const status = error
343
+ ? 'failed'
344
+ : safeString(result?.status, 40) || 'completed';
345
+ task.status = status;
346
+ task.updatedAt = now;
347
+ task.completedAt = safeString(result?.completedAt, 80) || now;
348
+ task.error = error;
349
+ task.resultSummary = error || summarizeTaskResult(result);
350
+ task.resultKind = safeString(result?.kind || result?.mode || 'agent-task', 80);
351
+ device.latestTask = task;
352
+ device.pendingTaskCommands.delete(commandId);
353
+ device.counters.taskResultsReceived += 1;
354
+ if (error) {
355
+ device.counters.taskResultsFailed += 1;
356
+ }
357
+
358
+ return task;
359
+ }
360
+
271
361
  function handleAgentMessage(socket, state, message) {
272
362
  if (!message || typeof message !== 'object') {
273
363
  return;
@@ -312,6 +402,19 @@ export function createRemoteHub(options = {}) {
312
402
  break;
313
403
  case 'command.result':
314
404
  device.counters.commandResultsReceived += 1;
405
+ {
406
+ const commandId = safeString(message.commandId, 128);
407
+ const error = safeString(message.error, 500);
408
+ const task = applyTaskResult(device, commandId, message.result ?? null, error);
409
+ if (task) {
410
+ emitRemoteEvent('RemoteTaskResult', device, {
411
+ commandId,
412
+ taskId: task.taskId,
413
+ status: task.status,
414
+ error: task.error
415
+ });
416
+ }
417
+ }
315
418
  emitRemoteEvent('RemoteCommandResult', device, {
316
419
  commandId: safeString(message.commandId, 128),
317
420
  result: message.result ?? null,
@@ -529,6 +632,68 @@ export function createRemoteHub(options = {}) {
529
632
  return { ok: true, commandId };
530
633
  }
531
634
 
635
+ function requestAgentTask(deviceId, options = {}) {
636
+ const device = devices.get(String(deviceId || ''));
637
+ if (!device?.socket || device.socket.destroyed || !device.connected) {
638
+ return { ok: false, error: 'device-not-connected' };
639
+ }
640
+
641
+ const instruction = safeText(options.instruction, MAX_AGENT_TASK_CHARS);
642
+ if (!instruction) {
643
+ return { ok: false, error: 'missing-instruction' };
644
+ }
645
+
646
+ const now = new Date().toISOString();
647
+ const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
648
+ const taskId = safeString(options.taskId, 128) || crypto.randomUUID();
649
+ const title = safeString(options.title, 120)
650
+ || safeString(instruction.split(/\r?\n/)[0], 120)
651
+ || 'Remote task';
652
+ const task = {
653
+ taskId,
654
+ commandId,
655
+ title,
656
+ instructionPreview: safeText(instruction, 320),
657
+ status: 'queued',
658
+ approvalLevel: 'task-only',
659
+ requestedAt: now,
660
+ sentAt: now,
661
+ updatedAt: now,
662
+ completedAt: '',
663
+ error: '',
664
+ resultKind: '',
665
+ resultSummary: ''
666
+ };
667
+
668
+ const sent = writeJsonLine(device.socket, {
669
+ type: 'command',
670
+ commandId,
671
+ command: 'agent.task',
672
+ payload: {
673
+ taskId,
674
+ title,
675
+ instruction,
676
+ approvalLevel: 'task-only',
677
+ requestedAt: now
678
+ },
679
+ issuedAt: now
680
+ });
681
+
682
+ if (!sent) {
683
+ return { ok: false, error: 'device-not-connected' };
684
+ }
685
+
686
+ device.counters.commandsSent += 1;
687
+ device.counters.tasksQueued += 1;
688
+ rememberDeviceTask(device, task);
689
+ emitRemoteEvent('RemoteTaskQueued', device, {
690
+ commandId,
691
+ taskId,
692
+ title
693
+ });
694
+ return { ok: true, commandId, taskId };
695
+ }
696
+
532
697
  function requestThumbnail(deviceId, options = {}) {
533
698
  return sendCommand(deviceId, {
534
699
  command: 'thumbnail.capture',
@@ -555,6 +720,7 @@ export function createRemoteHub(options = {}) {
555
720
  listDevices,
556
721
  disconnectDevice,
557
722
  sendCommand,
723
+ requestAgentTask,
558
724
  requestThumbnail,
559
725
  getDeviceThumbnail,
560
726
  getPairToken: () => pairToken
@@ -61,7 +61,8 @@ try {
61
61
  status: true,
62
62
  thumbnail: false,
63
63
  control: false,
64
- computerAgent: false
64
+ computerAgent: true,
65
+ taskDispatch: true
65
66
  }
66
67
  });
67
68
  writeJsonLine(socket, {
@@ -109,6 +110,34 @@ try {
109
110
  assert.equal(thumbnailDevice.latestThumbnail.streamId, 'smoke-thumb');
110
111
  assert.equal(thumbnailDevice.counters.thumbnailFramesReceived, 1);
111
112
 
113
+ const taskCommand = hub.requestAgentTask('smoke-device', {
114
+ instruction: 'Summarize current desktop status for the manager.',
115
+ title: 'Smoke task'
116
+ });
117
+ assert.equal(taskCommand.ok, true);
118
+ writeJsonLine(socket, {
119
+ type: 'command.result',
120
+ commandId: taskCommand.commandId,
121
+ result: {
122
+ kind: 'agent.task',
123
+ taskId: taskCommand.taskId,
124
+ status: 'completed',
125
+ summary: 'Smoke task accepted.',
126
+ completedAt: new Date().toISOString()
127
+ }
128
+ });
129
+
130
+ const taskDevice = await waitFor(() => {
131
+ const current = hub.listDevices();
132
+ return current[0]?.latestTask?.taskId === taskCommand.taskId
133
+ && current[0]?.latestTask?.status === 'completed'
134
+ ? current[0]
135
+ : null;
136
+ });
137
+ assert.equal(taskDevice.latestTask.resultSummary, 'Smoke task accepted.');
138
+ assert.equal(taskDevice.counters.tasksQueued, 1);
139
+ assert.equal(taskDevice.counters.taskResultsReceived, 1);
140
+
112
141
  socket.destroy();
113
142
  await waitFor(() => hub.listDevices()[0]?.connected === false);
114
143
  console.log('RemoteHub smoke OK');
package/server.js CHANGED
@@ -6987,6 +6987,42 @@ app.post('/api/remote/devices/:deviceId/ping', (req, res) => {
6987
6987
  }));
6988
6988
  });
6989
6989
 
6990
+ app.post('/api/remote/devices/:deviceId/tasks', (req, res) => {
6991
+ res.json(remoteHub.requestAgentTask(req.params.deviceId, {
6992
+ instruction: req.body?.instruction,
6993
+ title: req.body?.title,
6994
+ taskId: req.body?.taskId,
6995
+ commandId: req.body?.commandId
6996
+ }));
6997
+ });
6998
+
6999
+ app.post('/api/remote/tasks', (req, res) => {
7000
+ const requestedDeviceIds = Array.isArray(req.body?.deviceIds)
7001
+ ? req.body.deviceIds.map(value => String(value || '').trim()).filter(Boolean)
7002
+ : [];
7003
+ const allConnected = req.body?.allConnected !== false;
7004
+ const devices = remoteHub.listDevices();
7005
+ const targetIds = requestedDeviceIds.length > 0
7006
+ ? requestedDeviceIds
7007
+ : (allConnected ? devices.filter(device => device.connected).map(device => device.deviceId) : []);
7008
+ const uniqueTargetIds = [...new Set(targetIds)].slice(0, 500);
7009
+ const results = uniqueTargetIds.map(deviceId => ({
7010
+ deviceId,
7011
+ ...remoteHub.requestAgentTask(deviceId, {
7012
+ instruction: req.body?.instruction,
7013
+ title: req.body?.title
7014
+ })
7015
+ }));
7016
+ const queued = results.filter(result => result.ok === true).length;
7017
+ res.json({
7018
+ ok: queued > 0,
7019
+ total: uniqueTargetIds.length,
7020
+ queued,
7021
+ results,
7022
+ error: queued > 0 ? undefined : (uniqueTargetIds.length === 0 ? 'no-target-devices' : 'no-task-queued')
7023
+ });
7024
+ });
7025
+
6990
7026
  app.get('/api/remote/devices/:deviceId/thumbnail', (req, res) => {
6991
7027
  res.setHeader('Cache-Control', 'no-store');
6992
7028
  const thumbnail = remoteHub.getDeviceThumbnail(req.params.deviceId);
@@ -12160,6 +12160,60 @@
12160
12160
  commandRow.appendChild(refreshButton);
12161
12161
  bodyView.appendChild(commandRow);
12162
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
+
12163
12217
  if (lastError.trim()) {
12164
12218
  const errorEl = document.createElement('div');
12165
12219
  errorEl.textContent = lastError;
@@ -12220,6 +12274,12 @@
12220
12274
  const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
12221
12275
  const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
12222
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 || '');
12223
12283
  const card = document.createElement('article');
12224
12284
  card.dataset.deviceId = deviceId;
12225
12285
  card.style.cssText = `
@@ -12374,6 +12434,52 @@
12374
12434
  addMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
12375
12435
  card.appendChild(metrics);
12376
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
+
12377
12483
  const actions = document.createElement('div');
12378
12484
  actions.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:auto;';
12379
12485
  const status = document.createElement('span');
@@ -12389,6 +12495,13 @@
12389
12495
  `;
12390
12496
  actions.appendChild(status);
12391
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
+ }
12392
12505
  if (thumbnailEnabled) {
12393
12506
  const thumbnailButton = createRemoteFleetButton('Shot', 'Request thumbnail', 'thumbnail-device');
12394
12507
  thumbnailButton.dataset.deviceId = deviceId;
@@ -12429,6 +12542,48 @@
12429
12542
  navigator.clipboard?.writeText?.(command).catch(() => { });
12430
12543
  });
12431
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
+
12432
12587
  refreshButton.addEventListener('click', async event => {
12433
12588
  event.preventDefault();
12434
12589
  event.stopPropagation();
@@ -12449,6 +12604,33 @@
12449
12604
  });
12450
12605
  });
12451
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
+
12452
12634
  grid.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').forEach(button => {
12453
12635
  button.addEventListener('click', async event => {
12454
12636
  event.preventDefault();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-O+rTW41RGhoEFv77enci/rzrHD3OdR8jVue4Ekv/BXc=",
4
+ "hash": "sha256-ei2Zh3i4bzfRC0LQiNOOGqlx8qC3i1LaPPF8R1ZZfiI=",
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.5luow1xgjs.dll": "MindExecution.Core.dll",
127
- "MindExecution.Kernel.mot9nj6bzm.dll": "MindExecution.Kernel.dll",
128
- "MindExecution.Plugins.Admin.x9v2drg2f7.dll": "MindExecution.Plugins.Admin.dll",
129
- "MindExecution.Plugins.Business.b0kjoyx31x.dll": "MindExecution.Plugins.Business.dll",
130
- "MindExecution.Plugins.Concept.6tojojgh1a.dll": "MindExecution.Plugins.Concept.dll",
131
- "MindExecution.Plugins.Directory.fqtbuqadsx.dll": "MindExecution.Plugins.Directory.dll",
132
- "MindExecution.Plugins.PlanMaster.j7llfeae6l.dll": "MindExecution.Plugins.PlanMaster.dll",
133
- "MindExecution.Plugins.YouTube.yo5fwdhugr.dll": "MindExecution.Plugins.YouTube.dll",
134
- "MindExecution.Shared.0qi7vbn9a4.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.6cv7ad7rik.dll": "MindExecution.Web.dll",
126
+ "MindExecution.Core.rydw4mhsbd.dll": "MindExecution.Core.dll",
127
+ "MindExecution.Kernel.8sz1fl3k6s.dll": "MindExecution.Kernel.dll",
128
+ "MindExecution.Plugins.Admin.iltai5c3i9.dll": "MindExecution.Plugins.Admin.dll",
129
+ "MindExecution.Plugins.Business.mscgb1gwpf.dll": "MindExecution.Plugins.Business.dll",
130
+ "MindExecution.Plugins.Concept.s888y8snr4.dll": "MindExecution.Plugins.Concept.dll",
131
+ "MindExecution.Plugins.Directory.281klijdzl.dll": "MindExecution.Plugins.Directory.dll",
132
+ "MindExecution.Plugins.PlanMaster.2gy2ozelqp.dll": "MindExecution.Plugins.PlanMaster.dll",
133
+ "MindExecution.Plugins.YouTube.1v8o9nnlzq.dll": "MindExecution.Plugins.YouTube.dll",
134
+ "MindExecution.Shared.04anisxh35.dll": "MindExecution.Shared.dll",
135
+ "MindExecution.Web.0qdidsf6sl.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.5luow1xgjs.dll": "sha256-2YRYhyo44+pE+OT0e2NSDCcxNji6d55ITkrE/pj7xL0=",
282
- "MindExecution.Kernel.mot9nj6bzm.dll": "sha256-ej5PzXIwPCOmBctW5KddMd2Lotqv5PPwPrVPMHRrfVc=",
283
- "MindExecution.Plugins.Concept.6tojojgh1a.dll": "sha256-pnckzioW3S/Gd8Zgwvm6KqHzCMEUAmO2pXcs1ilG2XQ=",
284
- "MindExecution.Plugins.PlanMaster.j7llfeae6l.dll": "sha256-YRqzOjhkhZKW1kpttYBKFTB9kUpO2khBvAyayRzy4YE=",
285
- "MindExecution.Shared.0qi7vbn9a4.dll": "sha256-iJXZ6p8LtuUPrlFwKFxkMntcxXsKARiL/YO/UWnFOBU=",
286
- "MindExecution.Web.6cv7ad7rik.dll": "sha256-Zmi8Ley2/DFOiUNHrQ1NrsYzza6LRGdMdPmpZ6SlKWk="
281
+ "MindExecution.Core.rydw4mhsbd.dll": "sha256-s7r1GaMpvZ1RlLjiZO0RM4QK7CrQNbP6hcxpGO4H2vs=",
282
+ "MindExecution.Kernel.8sz1fl3k6s.dll": "sha256-aH4xUmfv18Zuwc1QVpIg1lZwyeNq2j/gnwEYRCiSgGs=",
283
+ "MindExecution.Plugins.Concept.s888y8snr4.dll": "sha256-RCJ1pfe7Pu+W0O7jNXCoR8X6IPK4+0ShVNdJyFM7DvY=",
284
+ "MindExecution.Plugins.PlanMaster.2gy2ozelqp.dll": "sha256-4r45+0ahnIZ9fvQ60VJb4OymhE8XTFMZ7jIF71DPrfE=",
285
+ "MindExecution.Shared.04anisxh35.dll": "sha256-8CQC4804ef5KlfH9/MULdbWZzL7YvpS7elf263ZYRlw=",
286
+ "MindExecution.Web.0qdidsf6sl.dll": "sha256-CF3kk5RKIgyaPytT7v1WVS9qj73pzXVbQ+vMxfyQQ2Y="
287
287
  },
288
288
  "lazyAssembly": {
289
- "MindExecution.Plugins.Admin.x9v2drg2f7.dll": "sha256-K/zYBndcqJXnUSVnQd6BwD/XxIQ3kTa9QKYfLV/grpE=",
290
- "MindExecution.Plugins.Business.b0kjoyx31x.dll": "sha256-9WMgmkFQqcrhPdkhbn8emFOJN1JHjbXRsOrW/XslYuM=",
291
- "MindExecution.Plugins.Directory.fqtbuqadsx.dll": "sha256-R9GNp8lYTA83muajc5V/BTbTSVuuO0ateoMiNc8O9g0=",
292
- "MindExecution.Plugins.YouTube.yo5fwdhugr.dll": "sha256-ks7QMZUaNrwB2Il1bP77dJ2jpbRv+CRuAj8v4cFvk7s="
289
+ "MindExecution.Plugins.Admin.iltai5c3i9.dll": "sha256-VrqPUOhOEQtnZuf97TfpU1ueaYKncgML/ylgcY3/3H4=",
290
+ "MindExecution.Plugins.Business.mscgb1gwpf.dll": "sha256-OsxBerI3pzCELMXu5vIOAQI5wQyKcWoOwc2d+N/nvCM=",
291
+ "MindExecution.Plugins.Directory.281klijdzl.dll": "sha256-MnBG5SBYBloYJ15miLmaRwTM/7/CE9KM/vcMgVPDGK8=",
292
+ "MindExecution.Plugins.YouTube.1v8o9nnlzq.dll": "sha256-I6jStz9j3BGIhprlmF+TfL5WpVuBKSVm3XYovlD5f+w="
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-thumb-v463';
561
+ const scriptVersion = '20260612-remote-task-v464';
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": "kF7vg+wU",
2
+ "version": "xZ2srqoC",
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-D+RFf7H+V5VEKkPJfNGy7IiWQdR3oYnJ1ooa7i344ww=",
89
+ "hash": "sha256-oCghpRmu/8JqF3GAUtmueGLrHLWHpaF3XIcK+VUwY64=",
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-2YRYhyo44+pE+OT0e2NSDCcxNji6d55ITkrE/pj7xL0=",
414
- "url": "_framework/MindExecution.Core.5luow1xgjs.dll"
413
+ "hash": "sha256-s7r1GaMpvZ1RlLjiZO0RM4QK7CrQNbP6hcxpGO4H2vs=",
414
+ "url": "_framework/MindExecution.Core.rydw4mhsbd.dll"
415
415
  },
416
416
  {
417
- "hash": "sha256-ej5PzXIwPCOmBctW5KddMd2Lotqv5PPwPrVPMHRrfVc=",
418
- "url": "_framework/MindExecution.Kernel.mot9nj6bzm.dll"
417
+ "hash": "sha256-aH4xUmfv18Zuwc1QVpIg1lZwyeNq2j/gnwEYRCiSgGs=",
418
+ "url": "_framework/MindExecution.Kernel.8sz1fl3k6s.dll"
419
419
  },
420
420
  {
421
- "hash": "sha256-K/zYBndcqJXnUSVnQd6BwD/XxIQ3kTa9QKYfLV/grpE=",
422
- "url": "_framework/MindExecution.Plugins.Admin.x9v2drg2f7.dll"
421
+ "hash": "sha256-VrqPUOhOEQtnZuf97TfpU1ueaYKncgML/ylgcY3/3H4=",
422
+ "url": "_framework/MindExecution.Plugins.Admin.iltai5c3i9.dll"
423
423
  },
424
424
  {
425
- "hash": "sha256-9WMgmkFQqcrhPdkhbn8emFOJN1JHjbXRsOrW/XslYuM=",
426
- "url": "_framework/MindExecution.Plugins.Business.b0kjoyx31x.dll"
425
+ "hash": "sha256-OsxBerI3pzCELMXu5vIOAQI5wQyKcWoOwc2d+N/nvCM=",
426
+ "url": "_framework/MindExecution.Plugins.Business.mscgb1gwpf.dll"
427
427
  },
428
428
  {
429
- "hash": "sha256-pnckzioW3S/Gd8Zgwvm6KqHzCMEUAmO2pXcs1ilG2XQ=",
430
- "url": "_framework/MindExecution.Plugins.Concept.6tojojgh1a.dll"
429
+ "hash": "sha256-RCJ1pfe7Pu+W0O7jNXCoR8X6IPK4+0ShVNdJyFM7DvY=",
430
+ "url": "_framework/MindExecution.Plugins.Concept.s888y8snr4.dll"
431
431
  },
432
432
  {
433
- "hash": "sha256-R9GNp8lYTA83muajc5V/BTbTSVuuO0ateoMiNc8O9g0=",
434
- "url": "_framework/MindExecution.Plugins.Directory.fqtbuqadsx.dll"
433
+ "hash": "sha256-MnBG5SBYBloYJ15miLmaRwTM/7/CE9KM/vcMgVPDGK8=",
434
+ "url": "_framework/MindExecution.Plugins.Directory.281klijdzl.dll"
435
435
  },
436
436
  {
437
- "hash": "sha256-YRqzOjhkhZKW1kpttYBKFTB9kUpO2khBvAyayRzy4YE=",
438
- "url": "_framework/MindExecution.Plugins.PlanMaster.j7llfeae6l.dll"
437
+ "hash": "sha256-4r45+0ahnIZ9fvQ60VJb4OymhE8XTFMZ7jIF71DPrfE=",
438
+ "url": "_framework/MindExecution.Plugins.PlanMaster.2gy2ozelqp.dll"
439
439
  },
440
440
  {
441
- "hash": "sha256-ks7QMZUaNrwB2Il1bP77dJ2jpbRv+CRuAj8v4cFvk7s=",
442
- "url": "_framework/MindExecution.Plugins.YouTube.yo5fwdhugr.dll"
441
+ "hash": "sha256-I6jStz9j3BGIhprlmF+TfL5WpVuBKSVm3XYovlD5f+w=",
442
+ "url": "_framework/MindExecution.Plugins.YouTube.1v8o9nnlzq.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-iJXZ6p8LtuUPrlFwKFxkMntcxXsKARiL/YO/UWnFOBU=",
446
- "url": "_framework/MindExecution.Shared.0qi7vbn9a4.dll"
445
+ "hash": "sha256-8CQC4804ef5KlfH9/MULdbWZzL7YvpS7elf263ZYRlw=",
446
+ "url": "_framework/MindExecution.Shared.04anisxh35.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-Zmi8Ley2/DFOiUNHrQ1NrsYzza6LRGdMdPmpZ6SlKWk=",
450
- "url": "_framework/MindExecution.Web.6cv7ad7rik.dll"
449
+ "hash": "sha256-CF3kk5RKIgyaPytT7v1WVS9qj73pzXVbQ+vMxfyQQ2Y=",
450
+ "url": "_framework/MindExecution.Web.0qdidsf6sl.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-8zIv4wl1NzISQFFiuDooCDM8G/D4O+dZZVbdToxKOKU=",
773
+ "hash": "sha256-pgZK03P5rDjSp61sZae0rpKy7fjg88JxqpQFSe7f/6Q=",
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-/aDhdXrjdOK/zJR6oXMIZoF8WvryE+s7wxPwDa5w9xE=",
837
+ "hash": "sha256-O+OZkgaXnLILhly2Of1gV09vQgw+YgNGMz375BoNSTk=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: kF7vg+wU */
1
+ /* Manifest version: xZ2srqoC */
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