@mindexec/cli 0.2.4 → 0.2.6

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 +8 -0
  2. package/package.json +6 -6
  3. package/remote-hub.js +27 -4
  4. package/scripts/remote-hub-smoke.mjs +35 -1
  5. package/server.js +7 -2
  6. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +505 -81
  7. package/wwwroot/_framework/MindExecution.Core.kf4752v5yl.dll +0 -0
  8. package/wwwroot/_framework/{MindExecution.Kernel.8sz1fl3k6s.dll → MindExecution.Kernel.79mgmkpsy1.dll} +0 -0
  9. package/wwwroot/_framework/{MindExecution.Plugins.Admin.iltai5c3i9.dll → MindExecution.Plugins.Admin.8ey1m70q79.dll} +0 -0
  10. package/wwwroot/_framework/{MindExecution.Plugins.Business.mscgb1gwpf.dll → MindExecution.Plugins.Business.8rvwgqzxaz.dll} +0 -0
  11. package/wwwroot/_framework/{MindExecution.Plugins.Concept.s888y8snr4.dll → MindExecution.Plugins.Concept.ynnk3bqf03.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Directory.281klijdzl.dll → MindExecution.Plugins.Directory.vi24rmnuyq.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.2gy2ozelqp.dll → MindExecution.Plugins.PlanMaster.9r08m5atjr.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.1v8o9nnlzq.dll → MindExecution.Plugins.YouTube.fjkbniwa4o.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Shared.04anisxh35.dll → MindExecution.Shared.e0qnm5vtax.dll} +0 -0
  16. package/wwwroot/_framework/MindExecution.Web.wk9yv02eva.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.rydw4mhsbd.dll +0 -0
  22. package/wwwroot/_framework/MindExecution.Web.0qdidsf6sl.dll +0 -0
package/README.md CHANGED
@@ -95,6 +95,14 @@ curl -X POST -H "X-Bridge-Token: <bridge-token>" -H "Content-Type: application/j
95
95
  http://127.0.0.1:5147/api/remote/tasks
96
96
  ```
97
97
 
98
+ Queue an opt-in text-only AI assist task for agents started with `--ai`:
99
+
100
+ ```bash
101
+ curl -X POST -H "X-Bridge-Token: <bridge-token>" -H "Content-Type: application/json" \
102
+ -d "{\"instruction\":\"Summarize what this computer should do next.\",\"allConnected\":true,\"approvalLevel\":\"ai-assist\"}" \
103
+ http://127.0.0.1:5147/api/remote/tasks
104
+ ```
105
+
98
106
  ?먮뒗 ?꾩뿭 ?ㅼ튂 ???ㅽ뻾?⑸땲??
99
107
 
100
108
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -8,9 +8,9 @@
8
8
  "start-bridge.bat",
9
9
  "start-bridge.sh",
10
10
  "launch-bridge.cjs",
11
- "server.js",
12
- "remote-hub.js",
13
- "codex-runtime.js",
11
+ "server.js",
12
+ "remote-hub.js",
13
+ "codex-runtime.js",
14
14
  "port-guard.cjs",
15
15
  "wwwroot/",
16
16
  "scripts/",
@@ -20,8 +20,8 @@
20
20
  "scripts": {
21
21
  "start": "node launch-bridge.cjs",
22
22
  "dev": "node launch-bridge.cjs --watch",
23
- "test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/remote-hub-smoke.mjs",
24
- "test:remote": "node scripts/remote-hub-smoke.mjs",
23
+ "test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/remote-hub-smoke.mjs",
24
+ "test:remote": "node scripts/remote-hub-smoke.mjs",
25
25
  "pack:dry": "npm pack --dry-run",
26
26
  "setup:grammars": "node scripts/setup-tree-sitter-grammars.mjs",
27
27
  "postinstall": "npm run setup:grammars"
package/remote-hub.js CHANGED
@@ -45,6 +45,22 @@ function safeText(value, maxLength = 1000) {
45
45
  return String(value ?? '').replace(/\0/g, '').trim().slice(0, maxLength);
46
46
  }
47
47
 
48
+ function normalizeApprovalLevel(value) {
49
+ const level = safeString(value, 80).toLowerCase();
50
+ return level === 'ai-assist' ? 'ai-assist' : 'task-only';
51
+ }
52
+
53
+ function readCapabilityFlag(capabilities, key) {
54
+ const value = capabilities?.[key];
55
+ if (typeof value === 'boolean') {
56
+ return value;
57
+ }
58
+ if (typeof value === 'number') {
59
+ return value !== 0;
60
+ }
61
+ return /^(1|true|yes|on)$/i.test(String(value || '').trim());
62
+ }
63
+
48
64
  function normalizeDeviceId(value) {
49
65
  const id = safeString(value, 128).replace(/[^a-zA-Z0-9_.:-]/g, '-');
50
66
  return id || crypto.randomUUID();
@@ -644,6 +660,11 @@ export function createRemoteHub(options = {}) {
644
660
  }
645
661
 
646
662
  const now = new Date().toISOString();
663
+ const approvalLevel = normalizeApprovalLevel(options.approvalLevel);
664
+ if (approvalLevel === 'ai-assist' && !readCapabilityFlag(device.capabilities, 'aiAssist')) {
665
+ return { ok: false, error: 'device-ai-assist-unavailable' };
666
+ }
667
+
647
668
  const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
648
669
  const taskId = safeString(options.taskId, 128) || crypto.randomUUID();
649
670
  const title = safeString(options.title, 120)
@@ -655,7 +676,7 @@ export function createRemoteHub(options = {}) {
655
676
  title,
656
677
  instructionPreview: safeText(instruction, 320),
657
678
  status: 'queued',
658
- approvalLevel: 'task-only',
679
+ approvalLevel,
659
680
  requestedAt: now,
660
681
  sentAt: now,
661
682
  updatedAt: now,
@@ -673,7 +694,8 @@ export function createRemoteHub(options = {}) {
673
694
  taskId,
674
695
  title,
675
696
  instruction,
676
- approvalLevel: 'task-only',
697
+ approvalLevel,
698
+ model: safeString(options.model, 120),
677
699
  requestedAt: now
678
700
  },
679
701
  issuedAt: now
@@ -689,9 +711,10 @@ export function createRemoteHub(options = {}) {
689
711
  emitRemoteEvent('RemoteTaskQueued', device, {
690
712
  commandId,
691
713
  taskId,
692
- title
714
+ title,
715
+ approvalLevel
693
716
  });
694
- return { ok: true, commandId, taskId };
717
+ return { ok: true, commandId, taskId, approvalLevel };
695
718
  }
696
719
 
697
720
  function requestThumbnail(deviceId, options = {}) {
@@ -62,7 +62,9 @@ try {
62
62
  thumbnail: false,
63
63
  control: false,
64
64
  computerAgent: true,
65
- taskDispatch: true
65
+ taskDispatch: true,
66
+ aiAssist: true,
67
+ aiModel: 'fake-ai'
66
68
  }
67
69
  });
68
70
  writeJsonLine(socket, {
@@ -138,6 +140,38 @@ try {
138
140
  assert.equal(taskDevice.counters.tasksQueued, 1);
139
141
  assert.equal(taskDevice.counters.taskResultsReceived, 1);
140
142
 
143
+ const aiTaskCommand = hub.requestAgentTask('smoke-device', {
144
+ instruction: 'Use AI assist to summarize the smoke test state.',
145
+ title: 'Smoke AI task',
146
+ approvalLevel: 'ai-assist'
147
+ });
148
+ assert.equal(aiTaskCommand.ok, true);
149
+ assert.equal(aiTaskCommand.approvalLevel, 'ai-assist');
150
+ writeJsonLine(socket, {
151
+ type: 'command.result',
152
+ commandId: aiTaskCommand.commandId,
153
+ result: {
154
+ kind: 'agent.task',
155
+ mode: 'ai-assist',
156
+ taskId: aiTaskCommand.taskId,
157
+ status: 'completed',
158
+ summary: 'Smoke AI task accepted.',
159
+ completedAt: new Date().toISOString()
160
+ }
161
+ });
162
+
163
+ const aiTaskDevice = await waitFor(() => {
164
+ const current = hub.listDevices();
165
+ return current[0]?.latestTask?.taskId === aiTaskCommand.taskId
166
+ && current[0]?.latestTask?.approvalLevel === 'ai-assist'
167
+ && current[0]?.latestTask?.status === 'completed'
168
+ ? current[0]
169
+ : null;
170
+ });
171
+ assert.equal(aiTaskDevice.latestTask.resultSummary, 'Smoke AI task accepted.');
172
+ assert.equal(aiTaskDevice.counters.tasksQueued, 2);
173
+ assert.equal(aiTaskDevice.counters.taskResultsReceived, 2);
174
+
141
175
  socket.destroy();
142
176
  await waitFor(() => hub.listDevices()[0]?.connected === false);
143
177
  console.log('RemoteHub smoke OK');
package/server.js CHANGED
@@ -6992,7 +6992,9 @@ app.post('/api/remote/devices/:deviceId/tasks', (req, res) => {
6992
6992
  instruction: req.body?.instruction,
6993
6993
  title: req.body?.title,
6994
6994
  taskId: req.body?.taskId,
6995
- commandId: req.body?.commandId
6995
+ commandId: req.body?.commandId,
6996
+ approvalLevel: req.body?.approvalLevel,
6997
+ model: req.body?.model
6996
6998
  }));
6997
6999
  });
6998
7000
 
@@ -7010,7 +7012,9 @@ app.post('/api/remote/tasks', (req, res) => {
7010
7012
  deviceId,
7011
7013
  ...remoteHub.requestAgentTask(deviceId, {
7012
7014
  instruction: req.body?.instruction,
7013
- title: req.body?.title
7015
+ title: req.body?.title,
7016
+ approvalLevel: req.body?.approvalLevel,
7017
+ model: req.body?.model
7014
7018
  })
7015
7019
  }));
7016
7020
  const queued = results.filter(result => result.ok === true).length;
@@ -7018,6 +7022,7 @@ app.post('/api/remote/tasks', (req, res) => {
7018
7022
  ok: queued > 0,
7019
7023
  total: uniqueTargetIds.length,
7020
7024
  queued,
7025
+ approvalLevel: req.body?.approvalLevel === 'ai-assist' ? 'ai-assist' : 'task-only',
7021
7026
  results,
7022
7027
  error: queued > 0 ? undefined : (uniqueTargetIds.length === 0 ? 'no-target-devices' : 'no-task-queued')
7023
7028
  });
@@ -12005,8 +12005,11 @@
12005
12005
  return `${Math.floor(seconds / 86400)}d ago`;
12006
12006
  }
12007
12007
 
12008
- function createRemoteFleetStat(label, value, tone = 'default') {
12008
+ function createRemoteFleetStat(label, value, tone = 'default', key = '') {
12009
12009
  const item = document.createElement('div');
12010
+ if (key) {
12011
+ item.dataset.remoteFleetStat = key;
12012
+ }
12010
12013
  item.style.cssText = `
12011
12014
  display: flex;
12012
12015
  flex-direction: column;
@@ -12029,6 +12032,7 @@
12029
12032
  `;
12030
12033
 
12031
12034
  const valueEl = document.createElement('strong');
12035
+ valueEl.dataset.remoteFleetStatValue = 'true';
12032
12036
  valueEl.textContent = value;
12033
12037
  valueEl.style.cssText = `
12034
12038
  color: ${tone === 'online' ? '#047857' : '#0f172a'};
@@ -12073,6 +12077,130 @@
12073
12077
  return button;
12074
12078
  }
12075
12079
 
12080
+ function createRemoteFleetSelect(options, value, title) {
12081
+ const select = document.createElement('select');
12082
+ select.title = title || '';
12083
+ select.setAttribute('aria-label', title || 'Remote fleet option');
12084
+ select.style.cssText = `
12085
+ min-width: 0;
12086
+ height: 34px;
12087
+ border-radius: 7px;
12088
+ border: 1px solid rgba(148, 163, 184, 0.34);
12089
+ background: rgba(255, 255, 255, 0.94);
12090
+ color: #0f172a;
12091
+ padding: 0 8px;
12092
+ font-size: 11px;
12093
+ font-weight: 850;
12094
+ letter-spacing: 0;
12095
+ outline: none;
12096
+ pointer-events: auto;
12097
+ `;
12098
+ options.forEach(option => {
12099
+ const optionEl = document.createElement('option');
12100
+ optionEl.value = option.value;
12101
+ optionEl.textContent = option.label;
12102
+ select.appendChild(optionEl);
12103
+ });
12104
+ select.value = value;
12105
+ return select;
12106
+ }
12107
+
12108
+ function getRemoteFleetDeviceField(device, camelKey, pascalKey, fallback = '') {
12109
+ const value = device?.[camelKey] ?? device?.[pascalKey] ?? fallback;
12110
+ return value === undefined || value === null ? fallback : value;
12111
+ }
12112
+
12113
+ function getRemoteFleetDeviceId(device) {
12114
+ return String(getRemoteFleetDeviceField(device, 'deviceId', 'DeviceId', '')).trim();
12115
+ }
12116
+
12117
+ function getRemoteFleetDeviceName(device) {
12118
+ return String(
12119
+ getRemoteFleetDeviceField(device, 'name', 'Name')
12120
+ || getRemoteFleetDeviceField(device, 'hostname', 'Hostname')
12121
+ || getRemoteFleetDeviceId(device)
12122
+ || 'device');
12123
+ }
12124
+
12125
+ function isRemoteFleetDeviceConnected(device) {
12126
+ return getRemoteFleetDeviceField(device, 'connected', 'Connected', false) === true;
12127
+ }
12128
+
12129
+ function isRemoteFleetDeviceTaskCapable(device) {
12130
+ return getRemoteFleetDeviceField(device, 'computerAgentEnabled', 'ComputerAgentEnabled', false) === true;
12131
+ }
12132
+
12133
+ function isRemoteFleetDeviceAiCapable(device) {
12134
+ return getRemoteFleetDeviceField(device, 'aiAssistEnabled', 'AiAssistEnabled', false) === true;
12135
+ }
12136
+
12137
+ function hasRemoteFleetThumbnail(device) {
12138
+ const dataUrl = String(getRemoteFleetDeviceField(device, 'thumbnailDataUrl', 'ThumbnailDataUrl', ''));
12139
+ return /^data:image\/(png|jpe?g|webp|svg\+xml);base64,/i.test(dataUrl);
12140
+ }
12141
+
12142
+ function getRemoteFleetTimestampMs(value) {
12143
+ const timestamp = Date.parse(String(value || ''));
12144
+ return Number.isFinite(timestamp) ? timestamp : 0;
12145
+ }
12146
+
12147
+ function getRemoteFleetDeviceLastSeenMs(device) {
12148
+ return Math.max(
12149
+ getRemoteFleetTimestampMs(getRemoteFleetDeviceField(device, 'lastSeenAt', 'LastSeenAt', '')),
12150
+ getRemoteFleetTimestampMs(getRemoteFleetDeviceField(device, 'lastStatusAt', 'LastStatusAt', '')),
12151
+ getRemoteFleetTimestampMs(getRemoteFleetDeviceField(device, 'thumbnailReceivedAt', 'ThumbnailReceivedAt', '')));
12152
+ }
12153
+
12154
+ function getRemoteFleetDeviceNumber(device, camelKey, pascalKey) {
12155
+ const number = Number(getRemoteFleetDeviceField(device, camelKey, pascalKey, Number.NaN));
12156
+ return Number.isFinite(number) ? number : Number.NaN;
12157
+ }
12158
+
12159
+ function buildRemoteFleetSearchText(device) {
12160
+ return [
12161
+ getRemoteFleetDeviceId(device),
12162
+ getRemoteFleetDeviceName(device),
12163
+ getRemoteFleetDeviceField(device, 'hostname', 'Hostname', ''),
12164
+ getRemoteFleetDeviceField(device, 'platform', 'Platform', ''),
12165
+ getRemoteFleetDeviceField(device, 'release', 'Release', ''),
12166
+ getRemoteFleetDeviceField(device, 'arch', 'Arch', ''),
12167
+ getRemoteFleetDeviceField(device, 'agentVersion', 'AgentVersion', ''),
12168
+ getRemoteFleetDeviceField(device, 'latestTaskTitle', 'LatestTaskTitle', ''),
12169
+ getRemoteFleetDeviceField(device, 'latestTaskStatus', 'LatestTaskStatus', ''),
12170
+ getRemoteFleetDeviceField(device, 'latestTaskResultSummary', 'LatestTaskResultSummary', ''),
12171
+ getRemoteFleetDeviceField(device, 'latestTaskError', 'LatestTaskError', '')
12172
+ ].join(' ').toLowerCase();
12173
+ }
12174
+
12175
+ function compareRemoteFleetDevices(left, right, sortMode) {
12176
+ const connectedDelta = Number(isRemoteFleetDeviceConnected(right)) - Number(isRemoteFleetDeviceConnected(left));
12177
+ const byName = () => getRemoteFleetDeviceName(left).localeCompare(getRemoteFleetDeviceName(right))
12178
+ || getRemoteFleetDeviceId(left).localeCompare(getRemoteFleetDeviceId(right));
12179
+
12180
+ switch (sortMode) {
12181
+ case 'recent':
12182
+ return getRemoteFleetDeviceLastSeenMs(right) - getRemoteFleetDeviceLastSeenMs(left) || byName();
12183
+ case 'memory': {
12184
+ const leftMem = getRemoteFleetDeviceNumber(left, 'usedMemRatio', 'UsedMemRatio');
12185
+ const rightMem = getRemoteFleetDeviceNumber(right, 'usedMemRatio', 'UsedMemRatio');
12186
+ return (Number.isFinite(rightMem) ? rightMem : -1) - (Number.isFinite(leftMem) ? leftMem : -1) || byName();
12187
+ }
12188
+ case 'load': {
12189
+ const leftLoad = getRemoteFleetDeviceNumber(left, 'load1', 'Load1');
12190
+ const rightLoad = getRemoteFleetDeviceNumber(right, 'load1', 'Load1');
12191
+ return (Number.isFinite(rightLoad) ? rightLoad : -1) - (Number.isFinite(leftLoad) ? leftLoad : -1) || byName();
12192
+ }
12193
+ case 'task':
12194
+ return String(getRemoteFleetDeviceField(right, 'latestTaskUpdatedAt', 'LatestTaskUpdatedAt', ''))
12195
+ .localeCompare(String(getRemoteFleetDeviceField(left, 'latestTaskUpdatedAt', 'LatestTaskUpdatedAt', ''))) || byName();
12196
+ case 'status':
12197
+ return connectedDelta || byName();
12198
+ case 'name':
12199
+ default:
12200
+ return byName();
12201
+ }
12202
+ }
12203
+
12076
12204
  async function syncRemoteFleetNodeStateFromResult(result) {
12077
12205
  const nodeState = result?.nodeState || result?.refresh?.nodeState;
12078
12206
  if (nodeState && window.mindMap?.syncNodeStates) {
@@ -12084,12 +12212,20 @@
12084
12212
  if (!bodyView) return;
12085
12213
 
12086
12214
  const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
12087
- const devices = parseRemoteFleetDevices(nodeModel);
12215
+ const searchState = bodyView.dataset.remoteFleetSearch || '';
12216
+ const filterState = bodyView.dataset.remoteFleetFilter || 'all';
12217
+ const sortState = bodyView.dataset.remoteFleetSort || 'status';
12218
+ const densityState = bodyView.dataset.remoteFleetDensity || 'cards';
12219
+ const aiAssistState = bodyView.dataset.remoteFleetAiAssist === 'true';
12220
+ const devices = [...parseRemoteFleetDevices(nodeModel)]
12221
+ .sort((left, right) => compareRemoteFleetDevices(left, right, sortState));
12088
12222
  const total = Number(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetDeviceCount', devices.length));
12089
12223
  const connected = Number(getRemoteFleetMetadataValue(
12090
12224
  nodeModel,
12091
12225
  'RemoteFleetConnectedDeviceCount',
12092
- devices.filter(device => device?.connected === true || device?.Connected === true).length));
12226
+ devices.filter(isRemoteFleetDeviceConnected).length));
12227
+ const taskCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceTaskCapable(device)).length;
12228
+ const aiCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceAiCapable(device)).length;
12093
12229
  const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
12094
12230
  const command = getRemoteFleetMetadataValue(
12095
12231
  nodeModel,
@@ -12123,8 +12259,8 @@
12123
12259
  `;
12124
12260
  top.appendChild(createRemoteFleetStat('Hub', hubStatus === 'online' ? 'Online' : 'Offline', hubStatus === 'online' ? 'online' : 'default'));
12125
12261
  top.appendChild(createRemoteFleetStat('Connected', `${Number.isFinite(connected) ? connected : 0}/${Number.isFinite(total) ? total : devices.length}`, connected > 0 ? 'online' : 'default'));
12126
- top.appendChild(createRemoteFleetStat('Mode', 'All', 'default'));
12127
- top.appendChild(createRemoteFleetStat('Paging', 'None', 'default'));
12262
+ top.appendChild(createRemoteFleetStat('Task', String(taskCapableCount), taskCapableCount > 0 ? 'online' : 'default'));
12263
+ top.appendChild(createRemoteFleetStat('AI', String(aiCapableCount), aiCapableCount > 0 ? 'online' : 'default'));
12128
12264
  bodyView.appendChild(top);
12129
12265
 
12130
12266
  const commandRow = document.createElement('div');
@@ -12160,10 +12296,86 @@
12160
12296
  commandRow.appendChild(refreshButton);
12161
12297
  bodyView.appendChild(commandRow);
12162
12298
 
12299
+ const filterRow = document.createElement('div');
12300
+ filterRow.style.cssText = `
12301
+ display: grid;
12302
+ grid-template-columns: minmax(150px, 1.2fr) minmax(96px, 0.7fr) minmax(96px, 0.7fr) minmax(84px, 0.55fr) auto;
12303
+ gap: 8px;
12304
+ align-items: center;
12305
+ flex: 0 0 auto;
12306
+ `;
12307
+
12308
+ const searchInput = document.createElement('input');
12309
+ searchInput.type = 'search';
12310
+ searchInput.value = searchState;
12311
+ searchInput.placeholder = 'Search devices';
12312
+ searchInput.dataset.remoteFleetSearch = 'true';
12313
+ searchInput.autocomplete = 'off';
12314
+ searchInput.spellcheck = false;
12315
+ searchInput.style.cssText = `
12316
+ min-width: 0;
12317
+ height: 34px;
12318
+ border-radius: 7px;
12319
+ border: 1px solid rgba(148, 163, 184, 0.34);
12320
+ background: rgba(255, 255, 255, 0.94);
12321
+ color: #0f172a;
12322
+ padding: 0 10px;
12323
+ font-size: 12px;
12324
+ font-weight: 750;
12325
+ letter-spacing: 0;
12326
+ outline: none;
12327
+ pointer-events: auto;
12328
+ `;
12329
+
12330
+ const filterSelect = createRemoteFleetSelect([
12331
+ { value: 'all', label: 'All' },
12332
+ { value: 'connected', label: 'Online' },
12333
+ { value: 'offline', label: 'Offline' },
12334
+ { value: 'task', label: 'Task' },
12335
+ { value: 'ai', label: 'AI' },
12336
+ { value: 'frames', label: 'Frames' },
12337
+ { value: 'issues', label: 'Issues' }
12338
+ ], filterState, 'Device filter');
12339
+
12340
+ const sortSelect = createRemoteFleetSelect([
12341
+ { value: 'status', label: 'Status' },
12342
+ { value: 'name', label: 'Name' },
12343
+ { value: 'recent', label: 'Recent' },
12344
+ { value: 'memory', label: 'Memory' },
12345
+ { value: 'load', label: 'Load' },
12346
+ { value: 'task', label: 'Task' }
12347
+ ], sortState, 'Device sort');
12348
+
12349
+ const densitySelect = createRemoteFleetSelect([
12350
+ { value: 'cards', label: 'Cards' },
12351
+ { value: 'dense', label: 'Dense' }
12352
+ ], densityState, 'Device density');
12353
+
12354
+ const matchCount = document.createElement('div');
12355
+ matchCount.dataset.remoteFleetMatchCount = 'true';
12356
+ matchCount.style.cssText = `
12357
+ min-width: 72px;
12358
+ color: #334155;
12359
+ font-size: 11px;
12360
+ font-weight: 900;
12361
+ letter-spacing: 0;
12362
+ text-align: right;
12363
+ white-space: nowrap;
12364
+ overflow: hidden;
12365
+ text-overflow: ellipsis;
12366
+ `;
12367
+
12368
+ filterRow.appendChild(searchInput);
12369
+ filterRow.appendChild(filterSelect);
12370
+ filterRow.appendChild(sortSelect);
12371
+ filterRow.appendChild(densitySelect);
12372
+ filterRow.appendChild(matchCount);
12373
+ bodyView.appendChild(filterRow);
12374
+
12163
12375
  const taskRow = document.createElement('div');
12164
12376
  taskRow.style.cssText = `
12165
12377
  display: grid;
12166
- grid-template-columns: minmax(0, 1fr) auto;
12378
+ grid-template-columns: minmax(0, 1fr) auto auto auto;
12167
12379
  gap: 8px;
12168
12380
  align-items: stretch;
12169
12381
  flex: 0 0 auto;
@@ -12189,9 +12401,48 @@
12189
12401
  outline: none;
12190
12402
  pointer-events: auto;
12191
12403
  `;
12192
- const sendConnectedButton = createRemoteFleetButton('Send connected', 'Dispatch task to all connected task-capable devices', 'task-connected');
12404
+ const aiToggleLabel = document.createElement('label');
12405
+ aiToggleLabel.title = 'Use AI assist on agents that expose it';
12406
+ aiToggleLabel.style.cssText = `
12407
+ display: inline-flex;
12408
+ align-items: center;
12409
+ justify-content: center;
12410
+ gap: 6px;
12411
+ min-width: 54px;
12412
+ height: 54px;
12413
+ padding: 0 9px;
12414
+ border-radius: 7px;
12415
+ border: 1px solid rgba(14, 165, 233, 0.32);
12416
+ background: rgba(240, 249, 255, 0.88);
12417
+ color: #0369a1;
12418
+ font-size: 11px;
12419
+ font-weight: 900;
12420
+ letter-spacing: 0;
12421
+ cursor: pointer;
12422
+ pointer-events: auto;
12423
+ user-select: none;
12424
+ `;
12425
+ const aiToggle = document.createElement('input');
12426
+ aiToggle.type = 'checkbox';
12427
+ aiToggle.checked = aiAssistState;
12428
+ aiToggle.dataset.remoteFleetAiToggle = 'true';
12429
+ aiToggle.style.cssText = `
12430
+ width: 14px;
12431
+ height: 14px;
12432
+ margin: 0;
12433
+ accent-color: #0284c7;
12434
+ `;
12435
+ const aiToggleText = document.createElement('span');
12436
+ aiToggleText.textContent = 'AI';
12437
+ aiToggleLabel.appendChild(aiToggle);
12438
+ aiToggleLabel.appendChild(aiToggleText);
12439
+ const sendVisibleButton = createRemoteFleetButton('Send visible', 'Dispatch task to visible connected task-capable devices', 'task-visible');
12440
+ sendVisibleButton.style.height = '54px';
12441
+ const sendConnectedButton = createRemoteFleetButton('Send all', 'Dispatch task to all connected task-capable devices', 'task-connected');
12193
12442
  sendConnectedButton.style.height = '54px';
12194
12443
  taskRow.appendChild(taskInput);
12444
+ taskRow.appendChild(aiToggleLabel);
12445
+ taskRow.appendChild(sendVisibleButton);
12195
12446
  taskRow.appendChild(sendConnectedButton);
12196
12447
  bodyView.appendChild(taskRow);
12197
12448
 
@@ -12238,7 +12489,7 @@
12238
12489
  overflow-y: auto;
12239
12490
  overflow-x: hidden;
12240
12491
  display: grid;
12241
- grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
12492
+ grid-template-columns: ${densityState === 'dense' ? 'repeat(auto-fill, minmax(220px, 1fr))' : 'repeat(auto-fill, minmax(168px, 1fr))'};
12242
12493
  align-content: start;
12243
12494
  gap: 8px;
12244
12495
  padding-right: 4px;
@@ -12263,99 +12514,110 @@
12263
12514
  grid.appendChild(empty);
12264
12515
  } else {
12265
12516
  devices.forEach(device => {
12266
- const connectedDevice = device?.connected === true || device?.Connected === true;
12267
- const name = String(device?.name || device?.Name || device?.hostname || device?.Hostname || device?.deviceId || device?.DeviceId || 'device');
12517
+ const connectedDevice = isRemoteFleetDeviceConnected(device);
12518
+ const name = getRemoteFleetDeviceName(device);
12268
12519
  const platform = [device?.platform || device?.Platform, device?.arch || device?.Arch]
12269
12520
  .filter(Boolean)
12270
12521
  .join(' / ') || 'unknown';
12271
12522
  const release = String(device?.release || device?.Release || '');
12272
- const deviceId = String(device?.deviceId || device?.DeviceId || '');
12523
+ const deviceId = getRemoteFleetDeviceId(device);
12273
12524
  const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
12274
12525
  const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
12275
12526
  const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
12276
- const hasThumbnail = /^data:image\/(png|jpe?g|webp|svg\+xml);base64,/i.test(thumbnailDataUrl);
12277
- const taskEnabled = device?.computerAgentEnabled === true || device?.ComputerAgentEnabled === true;
12527
+ const hasThumbnail = hasRemoteFleetThumbnail(device);
12528
+ const taskEnabled = isRemoteFleetDeviceTaskCapable(device);
12529
+ const aiAssistEnabled = isRemoteFleetDeviceAiCapable(device);
12530
+ const aiModel = String(device?.aiModel || device?.AiModel || '');
12278
12531
  const latestTaskStatus = String(device?.latestTaskStatus || device?.LatestTaskStatus || '');
12279
12532
  const latestTaskTitle = String(device?.latestTaskTitle || device?.LatestTaskTitle || '');
12533
+ const latestTaskApproval = String(device?.latestTaskApprovalLevel || device?.LatestTaskApprovalLevel || '');
12280
12534
  const latestTaskUpdatedAt = String(device?.latestTaskUpdatedAt || device?.LatestTaskUpdatedAt || '');
12281
12535
  const latestTaskError = String(device?.latestTaskError || device?.LatestTaskError || '');
12282
12536
  const latestTaskResult = String(device?.latestTaskResultSummary || device?.LatestTaskResultSummary || '');
12283
12537
  const card = document.createElement('article');
12284
12538
  card.dataset.deviceId = deviceId;
12539
+ card.dataset.remoteFleetSearchText = buildRemoteFleetSearchText(device);
12540
+ card.dataset.remoteFleetConnected = connectedDevice ? 'true' : 'false';
12541
+ card.dataset.remoteFleetTaskCapable = taskEnabled ? 'true' : 'false';
12542
+ card.dataset.remoteFleetAiCapable = aiAssistEnabled ? 'true' : 'false';
12543
+ card.dataset.remoteFleetHasFrame = hasThumbnail ? 'true' : 'false';
12544
+ card.dataset.remoteFleetIssue = (!connectedDevice || latestTaskStatus === 'failed' || !!latestTaskError) ? 'true' : 'false';
12285
12545
  card.style.cssText = `
12286
12546
  display: flex;
12287
12547
  flex-direction: column;
12288
- gap: 8px;
12548
+ gap: ${densityState === 'dense' ? '6px' : '8px'};
12289
12549
  min-width: 0;
12290
- min-height: 134px;
12291
- padding: 10px;
12550
+ min-height: ${densityState === 'dense' ? '92px' : '134px'};
12551
+ padding: ${densityState === 'dense' ? '8px' : '10px'};
12292
12552
  border-radius: 8px;
12293
12553
  background: #ffffff;
12294
12554
  border: 1px solid ${connectedDevice ? 'rgba(16, 185, 129, 0.34)' : 'rgba(148, 163, 184, 0.28)'};
12295
12555
  box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
12296
12556
  `;
12297
12557
 
12298
- const preview = document.createElement('div');
12299
- preview.style.cssText = `
12300
- position: relative;
12301
- width: 100%;
12302
- aspect-ratio: 16 / 9;
12303
- overflow: hidden;
12304
- border-radius: 7px;
12305
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
12306
- border: 1px solid rgba(15, 23, 42, 0.12);
12307
- `;
12308
- if (hasThumbnail) {
12309
- const image = document.createElement('img');
12310
- image.src = thumbnailDataUrl;
12311
- image.alt = `${name} thumbnail`;
12312
- image.loading = 'lazy';
12313
- image.decoding = 'async';
12314
- image.style.cssText = `
12558
+ if (densityState !== 'dense') {
12559
+ const preview = document.createElement('div');
12560
+ preview.style.cssText = `
12561
+ position: relative;
12315
12562
  width: 100%;
12316
- height: 100%;
12317
- object-fit: cover;
12318
- display: block;
12319
- `;
12320
- preview.appendChild(image);
12321
- } else {
12322
- const placeholder = document.createElement('div');
12323
- placeholder.textContent = thumbnailEnabled ? 'No frame yet' : 'Status only';
12324
- placeholder.style.cssText = `
12325
- position: absolute;
12326
- inset: 0;
12327
- display: flex;
12328
- align-items: center;
12329
- justify-content: center;
12330
- color: rgba(226, 232, 240, 0.78);
12331
- font-size: 11px;
12332
- font-weight: 900;
12333
- letter-spacing: 0;
12334
- `;
12335
- preview.appendChild(placeholder);
12336
- }
12337
- if (thumbnailCapturedAt) {
12338
- const badge = document.createElement('span');
12339
- badge.textContent = formatRemoteFleetAge(thumbnailCapturedAt);
12340
- badge.style.cssText = `
12341
- position: absolute;
12342
- right: 6px;
12343
- bottom: 6px;
12344
- max-width: calc(100% - 12px);
12345
- padding: 3px 6px;
12346
- border-radius: 999px;
12347
- background: rgba(15, 23, 42, 0.74);
12348
- color: #e2e8f0;
12349
- font-size: 9px;
12350
- font-weight: 900;
12351
- line-height: 1;
12563
+ aspect-ratio: 16 / 9;
12352
12564
  overflow: hidden;
12353
- text-overflow: ellipsis;
12354
- white-space: nowrap;
12565
+ border-radius: 7px;
12566
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
12567
+ border: 1px solid rgba(15, 23, 42, 0.12);
12355
12568
  `;
12356
- preview.appendChild(badge);
12569
+ if (hasThumbnail) {
12570
+ const image = document.createElement('img');
12571
+ image.src = thumbnailDataUrl;
12572
+ image.alt = `${name} thumbnail`;
12573
+ image.loading = 'lazy';
12574
+ image.decoding = 'async';
12575
+ image.style.cssText = `
12576
+ width: 100%;
12577
+ height: 100%;
12578
+ object-fit: cover;
12579
+ display: block;
12580
+ `;
12581
+ preview.appendChild(image);
12582
+ } else {
12583
+ const placeholder = document.createElement('div');
12584
+ placeholder.textContent = thumbnailEnabled ? 'No frame yet' : 'Status only';
12585
+ placeholder.style.cssText = `
12586
+ position: absolute;
12587
+ inset: 0;
12588
+ display: flex;
12589
+ align-items: center;
12590
+ justify-content: center;
12591
+ color: rgba(226, 232, 240, 0.78);
12592
+ font-size: 11px;
12593
+ font-weight: 900;
12594
+ letter-spacing: 0;
12595
+ `;
12596
+ preview.appendChild(placeholder);
12597
+ }
12598
+ if (thumbnailCapturedAt) {
12599
+ const badge = document.createElement('span');
12600
+ badge.textContent = formatRemoteFleetAge(thumbnailCapturedAt);
12601
+ badge.style.cssText = `
12602
+ position: absolute;
12603
+ right: 6px;
12604
+ bottom: 6px;
12605
+ max-width: calc(100% - 12px);
12606
+ padding: 3px 6px;
12607
+ border-radius: 999px;
12608
+ background: rgba(15, 23, 42, 0.74);
12609
+ color: #e2e8f0;
12610
+ font-size: 9px;
12611
+ font-weight: 900;
12612
+ line-height: 1;
12613
+ overflow: hidden;
12614
+ text-overflow: ellipsis;
12615
+ white-space: nowrap;
12616
+ `;
12617
+ preview.appendChild(badge);
12618
+ }
12619
+ card.appendChild(preview);
12357
12620
  }
12358
- card.appendChild(preview);
12359
12621
 
12360
12622
  const cardHeader = document.createElement('div');
12361
12623
  cardHeader.style.cssText = 'display:flex;align-items:flex-start;gap:8px;min-width:0;';
@@ -12432,7 +12694,23 @@
12432
12694
  addMetric('Uptime', formatRemoteFleetDuration(device?.uptimeSec ?? device?.UptimeSec));
12433
12695
  addMetric('Mem', formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio));
12434
12696
  addMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
12435
- card.appendChild(metrics);
12697
+ if (densityState === 'dense') {
12698
+ const denseMeta = document.createElement('div');
12699
+ denseMeta.textContent = `Seen ${formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt)} - Mem ${formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio)} - Load ${formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2)}`;
12700
+ denseMeta.style.cssText = `
12701
+ color: #475569;
12702
+ font-size: 10px;
12703
+ font-weight: 750;
12704
+ line-height: 1.2;
12705
+ overflow: hidden;
12706
+ text-overflow: ellipsis;
12707
+ white-space: nowrap;
12708
+ letter-spacing: 0;
12709
+ `;
12710
+ card.appendChild(denseMeta);
12711
+ } else {
12712
+ card.appendChild(metrics);
12713
+ }
12436
12714
 
12437
12715
  if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
12438
12716
  const taskBox = document.createElement('div');
@@ -12450,7 +12728,8 @@
12450
12728
  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
12729
  `;
12452
12730
  const taskLine = document.createElement('div');
12453
- taskLine.textContent = `${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}`;
12731
+ const taskModeLabel = latestTaskApproval === 'ai-assist' ? 'AI' : 'Task';
12732
+ taskLine.textContent = `${taskModeLabel} ${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}`;
12454
12733
  taskLine.style.cssText = `
12455
12734
  color: ${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};
12456
12735
  font-size: 10px;
@@ -12483,7 +12762,9 @@
12483
12762
  const actions = document.createElement('div');
12484
12763
  actions.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:auto;';
12485
12764
  const status = document.createElement('span');
12486
- status.textContent = connectedDevice ? 'Connected' : 'Offline';
12765
+ status.textContent = connectedDevice
12766
+ ? (aiAssistEnabled ? `AI ${aiModel || 'ready'}` : 'Connected')
12767
+ : 'Offline';
12487
12768
  status.style.cssText = `
12488
12769
  min-width: 0;
12489
12770
  color: ${connectedDevice ? '#047857' : '#64748b'};
@@ -12518,12 +12799,31 @@
12518
12799
  card.appendChild(actions);
12519
12800
  grid.appendChild(card);
12520
12801
  });
12802
+
12803
+ const noMatch = document.createElement('div');
12804
+ noMatch.dataset.remoteFleetNoMatch = 'true';
12805
+ noMatch.textContent = 'No matching devices.';
12806
+ noMatch.style.cssText = `
12807
+ grid-column: 1 / -1;
12808
+ display: none;
12809
+ align-items: center;
12810
+ min-height: 74px;
12811
+ padding: 14px;
12812
+ border-radius: 8px;
12813
+ border: 1px dashed rgba(100, 116, 139, 0.36);
12814
+ color: #475569;
12815
+ font-size: 13px;
12816
+ font-weight: 850;
12817
+ background: rgba(255, 255, 255, 0.74);
12818
+ `;
12819
+ grid.appendChild(noMatch);
12521
12820
  }
12522
12821
 
12523
12822
  bodyView.appendChild(grid);
12524
12823
 
12525
12824
  const footer = document.createElement('div');
12526
12825
  footer.textContent = `Endpoint ${endpoint} · refreshed ${formatRemoteFleetAge(refreshedAt)}`;
12826
+ footer.textContent = `Endpoint ${endpoint} - all devices, no paging - refreshed ${formatRemoteFleetAge(refreshedAt)}`;
12527
12827
  footer.style.cssText = `
12528
12828
  flex: 0 0 auto;
12529
12829
  color: #64748b;
@@ -12558,6 +12858,128 @@
12558
12858
  };
12559
12859
 
12560
12860
  const readTaskInstruction = () => String(taskInput.value || '').trim();
12861
+ const useAiAssist = () => aiToggle.checked === true;
12862
+ const getDeviceCards = () => Array.from(grid.querySelectorAll('article[data-device-id]'));
12863
+ const getVisibleEligibleDeviceIds = () => {
12864
+ const wantsAi = useAiAssist();
12865
+ return getDeviceCards()
12866
+ .filter(card => card.style.display !== 'none'
12867
+ && card.dataset.remoteFleetConnected === 'true'
12868
+ && card.dataset.remoteFleetTaskCapable === 'true'
12869
+ && (!wantsAi || card.dataset.remoteFleetAiCapable === 'true'))
12870
+ .map(card => String(card.dataset.deviceId || '').trim())
12871
+ .filter(Boolean);
12872
+ };
12873
+
12874
+ const applyRemoteFleetFilters = () => {
12875
+ const searchText = String(searchInput.value || '').trim().toLowerCase();
12876
+ const terms = searchText.split(/\s+/).filter(Boolean);
12877
+ const filterMode = String(filterSelect.value || 'all');
12878
+ let visible = 0;
12879
+ let eligible = 0;
12880
+ const wantsAi = useAiAssist();
12881
+
12882
+ getDeviceCards().forEach(card => {
12883
+ const matchesSearch = terms.length === 0
12884
+ || terms.every(term => String(card.dataset.remoteFleetSearchText || '').includes(term));
12885
+ const matchesMode = filterMode === 'all'
12886
+ || (filterMode === 'connected' && card.dataset.remoteFleetConnected === 'true')
12887
+ || (filterMode === 'offline' && card.dataset.remoteFleetConnected !== 'true')
12888
+ || (filterMode === 'task' && card.dataset.remoteFleetConnected === 'true' && card.dataset.remoteFleetTaskCapable === 'true')
12889
+ || (filterMode === 'ai' && card.dataset.remoteFleetConnected === 'true' && card.dataset.remoteFleetAiCapable === 'true')
12890
+ || (filterMode === 'frames' && card.dataset.remoteFleetHasFrame === 'true')
12891
+ || (filterMode === 'issues' && card.dataset.remoteFleetIssue === 'true');
12892
+ const show = matchesSearch && matchesMode;
12893
+ card.style.display = show ? 'flex' : 'none';
12894
+ if (show) {
12895
+ visible += 1;
12896
+ if (card.dataset.remoteFleetConnected === 'true'
12897
+ && card.dataset.remoteFleetTaskCapable === 'true'
12898
+ && (!wantsAi || card.dataset.remoteFleetAiCapable === 'true')) {
12899
+ eligible += 1;
12900
+ }
12901
+ }
12902
+ });
12903
+
12904
+ const noMatch = grid.querySelector('[data-remote-fleet-no-match="true"]');
12905
+ if (noMatch) {
12906
+ noMatch.style.display = devices.length > 0 && visible === 0 ? 'flex' : 'none';
12907
+ }
12908
+
12909
+ matchCount.textContent = `${visible}/${devices.length}`;
12910
+ matchCount.title = `${visible} visible, ${eligible} targetable`;
12911
+ sendVisibleButton.disabled = eligible <= 0;
12912
+ sendVisibleButton.title = eligible > 0
12913
+ ? `Dispatch to ${eligible} visible target(s)`
12914
+ : 'No visible targetable devices';
12915
+ const allTargetCount = wantsAi ? aiCapableCount : taskCapableCount;
12916
+ sendConnectedButton.disabled = allTargetCount <= 0;
12917
+ sendConnectedButton.title = allTargetCount > 0
12918
+ ? `Dispatch to ${allTargetCount} connected target(s)`
12919
+ : 'No connected targetable devices';
12920
+ grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
12921
+ const card = button.closest('article[data-device-id]');
12922
+ button.disabled = wantsAi && card?.dataset.remoteFleetAiCapable !== 'true';
12923
+ button.title = button.disabled ? 'AI assist is not enabled on this device' : 'Dispatch task to this device';
12924
+ });
12925
+
12926
+ bodyView.dataset.remoteFleetSearch = searchText;
12927
+ bodyView.dataset.remoteFleetFilter = filterMode;
12928
+ bodyView.dataset.remoteFleetSort = String(sortSelect.value || 'status');
12929
+ bodyView.dataset.remoteFleetDensity = String(densitySelect.value || 'cards');
12930
+ bodyView.dataset.remoteFleetAiAssist = wantsAi ? 'true' : 'false';
12931
+ };
12932
+
12933
+ [searchInput, filterSelect, sortSelect, densitySelect, aiToggle].forEach(control => {
12934
+ ['mousedown', 'mouseup', 'click', 'dblclick', 'keydown'].forEach(eventName => {
12935
+ control.addEventListener(eventName, event => event.stopPropagation());
12936
+ });
12937
+ });
12938
+
12939
+ searchInput.addEventListener('input', applyRemoteFleetFilters);
12940
+ filterSelect.addEventListener('change', applyRemoteFleetFilters);
12941
+ aiToggle.addEventListener('change', applyRemoteFleetFilters);
12942
+ sortSelect.addEventListener('change', () => {
12943
+ bodyView.dataset.remoteFleetSort = String(sortSelect.value || 'status');
12944
+ renderRemoteFleetMonitor(bodyView, nodeModel);
12945
+ });
12946
+ densitySelect.addEventListener('change', () => {
12947
+ bodyView.dataset.remoteFleetDensity = String(densitySelect.value || 'cards');
12948
+ renderRemoteFleetMonitor(bodyView, nodeModel);
12949
+ });
12950
+ applyRemoteFleetFilters();
12951
+
12952
+ sendVisibleButton.addEventListener('click', async event => {
12953
+ event.preventDefault();
12954
+ event.stopPropagation();
12955
+ const instruction = readTaskInstruction();
12956
+ if (!instruction) {
12957
+ setTaskFeedback('Write a task first.', 'error');
12958
+ taskInput.focus();
12959
+ return;
12960
+ }
12961
+
12962
+ const targetIds = getVisibleEligibleDeviceIds();
12963
+ if (targetIds.length === 0) {
12964
+ setTaskFeedback('No visible targetable devices.', 'error');
12965
+ return;
12966
+ }
12967
+
12968
+ sendVisibleButton.disabled = true;
12969
+ setTaskFeedback('Dispatching task to visible devices...');
12970
+ try {
12971
+ const result = await invokeDotNetAsync('DispatchRemoteFleetTaskBatchFromJs', nodeId, targetIds, instruction, useAiAssist());
12972
+ await syncRemoteFleetNodeStateFromResult(result);
12973
+ if (result?.success) {
12974
+ const mode = useAiAssist() ? 'AI task' : 'remote task';
12975
+ setTaskFeedback(`Queued ${result.queued || targetIds.length} visible ${mode}(s).`, 'success');
12976
+ } else {
12977
+ setTaskFeedback(result?.error || 'Task dispatch failed.', 'error');
12978
+ }
12979
+ } finally {
12980
+ applyRemoteFleetFilters();
12981
+ }
12982
+ });
12561
12983
 
12562
12984
  sendConnectedButton.addEventListener('click', async event => {
12563
12985
  event.preventDefault();
@@ -12572,15 +12994,16 @@
12572
12994
  sendConnectedButton.disabled = true;
12573
12995
  setTaskFeedback('Dispatching task to connected devices...');
12574
12996
  try {
12575
- const result = await invokeDotNetAsync('DispatchRemoteFleetTaskFromJs', nodeId, '', instruction);
12997
+ const result = await invokeDotNetAsync('DispatchRemoteFleetTaskFromJs', nodeId, '', instruction, useAiAssist());
12576
12998
  await syncRemoteFleetNodeStateFromResult(result);
12577
12999
  if (result?.success) {
12578
- setTaskFeedback(`Queued ${result.queued || result.total || 1} remote task(s).`, 'success');
13000
+ const mode = useAiAssist() ? 'AI task' : 'remote task';
13001
+ setTaskFeedback(`Queued ${result.queued || result.total || 1} ${mode}(s).`, 'success');
12579
13002
  } else {
12580
13003
  setTaskFeedback(result?.error || 'Task dispatch failed.', 'error');
12581
13004
  }
12582
13005
  } finally {
12583
- sendConnectedButton.disabled = false;
13006
+ applyRemoteFleetFilters();
12584
13007
  }
12585
13008
  });
12586
13009
 
@@ -12618,15 +13041,16 @@
12618
13041
  button.disabled = true;
12619
13042
  setTaskFeedback('Dispatching task to device...');
12620
13043
  try {
12621
- const result = await invokeDotNetAsync('DispatchRemoteFleetTaskFromJs', nodeId, button.dataset.deviceId || '', instruction);
13044
+ const result = await invokeDotNetAsync('DispatchRemoteFleetTaskFromJs', nodeId, button.dataset.deviceId || '', instruction, useAiAssist());
12622
13045
  await syncRemoteFleetNodeStateFromResult(result);
12623
13046
  if (result?.success) {
12624
- setTaskFeedback('Queued remote task.', 'success');
13047
+ setTaskFeedback(useAiAssist() ? 'Queued AI task.' : 'Queued remote task.', 'success');
12625
13048
  } else {
12626
13049
  setTaskFeedback(result?.error || 'Task dispatch failed.', 'error');
12627
13050
  }
12628
13051
  } finally {
12629
13052
  button.disabled = false;
13053
+ applyRemoteFleetFilters();
12630
13054
  }
12631
13055
  });
12632
13056
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-ei2Zh3i4bzfRC0LQiNOOGqlx8qC3i1LaPPF8R1ZZfiI=",
4
+ "hash": "sha256-/odScRNovgEc8hBO1R9VAaBYER2ZEkgcGNL8jCOVsoU=",
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.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",
126
+ "MindExecution.Core.kf4752v5yl.dll": "MindExecution.Core.dll",
127
+ "MindExecution.Kernel.79mgmkpsy1.dll": "MindExecution.Kernel.dll",
128
+ "MindExecution.Plugins.Admin.8ey1m70q79.dll": "MindExecution.Plugins.Admin.dll",
129
+ "MindExecution.Plugins.Business.8rvwgqzxaz.dll": "MindExecution.Plugins.Business.dll",
130
+ "MindExecution.Plugins.Concept.ynnk3bqf03.dll": "MindExecution.Plugins.Concept.dll",
131
+ "MindExecution.Plugins.Directory.vi24rmnuyq.dll": "MindExecution.Plugins.Directory.dll",
132
+ "MindExecution.Plugins.PlanMaster.9r08m5atjr.dll": "MindExecution.Plugins.PlanMaster.dll",
133
+ "MindExecution.Plugins.YouTube.fjkbniwa4o.dll": "MindExecution.Plugins.YouTube.dll",
134
+ "MindExecution.Shared.e0qnm5vtax.dll": "MindExecution.Shared.dll",
135
+ "MindExecution.Web.wk9yv02eva.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.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="
281
+ "MindExecution.Core.kf4752v5yl.dll": "sha256-MK+6sYNYxso5bmurLtzuQOy6PiIr6jHmV0DMzHSg+sc=",
282
+ "MindExecution.Kernel.79mgmkpsy1.dll": "sha256-qwoCzn5eUut0Qz0kvhUJmtlPAtoMtYtMTv9c4ekX84o=",
283
+ "MindExecution.Plugins.Concept.ynnk3bqf03.dll": "sha256-Mvw1k0NexULKDOElumjmqjK4jl2MS5NNvFrTnBDZ2qc=",
284
+ "MindExecution.Plugins.PlanMaster.9r08m5atjr.dll": "sha256-K2De6QooRNGCQL7zfYyZ4aeMZZE2etfT4Aj52u7y+RQ=",
285
+ "MindExecution.Shared.e0qnm5vtax.dll": "sha256-bh3YoyPl9H4U9q8TO3u6hoJLMoWW0sYxJyJwj8heek0=",
286
+ "MindExecution.Web.wk9yv02eva.dll": "sha256-AHwwn1rOC7uDlpCJJLndmyf97G7nM018K4WpzRuPwTg="
287
287
  },
288
288
  "lazyAssembly": {
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="
289
+ "MindExecution.Plugins.Admin.8ey1m70q79.dll": "sha256-oMlSl7sNr3GfLsqVskx6UNQMyoGJANENnZIL62rfG+c=",
290
+ "MindExecution.Plugins.Business.8rvwgqzxaz.dll": "sha256-xJBBAfZAEw5jQcNThJNI3vKn2rLWuCRV7Oo8onCFwOo=",
291
+ "MindExecution.Plugins.Directory.vi24rmnuyq.dll": "sha256-x+iwabNhSVcOgBw2RlEp7HSQ7Eep/nvCn+lZVNGxCK8=",
292
+ "MindExecution.Plugins.YouTube.fjkbniwa4o.dll": "sha256-RUR2I22IaFbLmHY/Gcw7wfwiybsIs8m+kgMfo1i78+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-task-v464';
561
+ const scriptVersion = '20260612-remote-fleet-scale-v466';
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": "xZ2srqoC",
2
+ "version": "tyUOiEjY",
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-oCghpRmu/8JqF3GAUtmueGLrHLWHpaF3XIcK+VUwY64=",
89
+ "hash": "sha256-UIJj28RUmbRzdxZB7yxJHs88G8t0QTV1mCQOJeHywK8=",
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-s7r1GaMpvZ1RlLjiZO0RM4QK7CrQNbP6hcxpGO4H2vs=",
414
- "url": "_framework/MindExecution.Core.rydw4mhsbd.dll"
413
+ "hash": "sha256-MK+6sYNYxso5bmurLtzuQOy6PiIr6jHmV0DMzHSg+sc=",
414
+ "url": "_framework/MindExecution.Core.kf4752v5yl.dll"
415
415
  },
416
416
  {
417
- "hash": "sha256-aH4xUmfv18Zuwc1QVpIg1lZwyeNq2j/gnwEYRCiSgGs=",
418
- "url": "_framework/MindExecution.Kernel.8sz1fl3k6s.dll"
417
+ "hash": "sha256-qwoCzn5eUut0Qz0kvhUJmtlPAtoMtYtMTv9c4ekX84o=",
418
+ "url": "_framework/MindExecution.Kernel.79mgmkpsy1.dll"
419
419
  },
420
420
  {
421
- "hash": "sha256-VrqPUOhOEQtnZuf97TfpU1ueaYKncgML/ylgcY3/3H4=",
422
- "url": "_framework/MindExecution.Plugins.Admin.iltai5c3i9.dll"
421
+ "hash": "sha256-oMlSl7sNr3GfLsqVskx6UNQMyoGJANENnZIL62rfG+c=",
422
+ "url": "_framework/MindExecution.Plugins.Admin.8ey1m70q79.dll"
423
423
  },
424
424
  {
425
- "hash": "sha256-OsxBerI3pzCELMXu5vIOAQI5wQyKcWoOwc2d+N/nvCM=",
426
- "url": "_framework/MindExecution.Plugins.Business.mscgb1gwpf.dll"
425
+ "hash": "sha256-xJBBAfZAEw5jQcNThJNI3vKn2rLWuCRV7Oo8onCFwOo=",
426
+ "url": "_framework/MindExecution.Plugins.Business.8rvwgqzxaz.dll"
427
427
  },
428
428
  {
429
- "hash": "sha256-RCJ1pfe7Pu+W0O7jNXCoR8X6IPK4+0ShVNdJyFM7DvY=",
430
- "url": "_framework/MindExecution.Plugins.Concept.s888y8snr4.dll"
429
+ "hash": "sha256-Mvw1k0NexULKDOElumjmqjK4jl2MS5NNvFrTnBDZ2qc=",
430
+ "url": "_framework/MindExecution.Plugins.Concept.ynnk3bqf03.dll"
431
431
  },
432
432
  {
433
- "hash": "sha256-MnBG5SBYBloYJ15miLmaRwTM/7/CE9KM/vcMgVPDGK8=",
434
- "url": "_framework/MindExecution.Plugins.Directory.281klijdzl.dll"
433
+ "hash": "sha256-x+iwabNhSVcOgBw2RlEp7HSQ7Eep/nvCn+lZVNGxCK8=",
434
+ "url": "_framework/MindExecution.Plugins.Directory.vi24rmnuyq.dll"
435
435
  },
436
436
  {
437
- "hash": "sha256-4r45+0ahnIZ9fvQ60VJb4OymhE8XTFMZ7jIF71DPrfE=",
438
- "url": "_framework/MindExecution.Plugins.PlanMaster.2gy2ozelqp.dll"
437
+ "hash": "sha256-K2De6QooRNGCQL7zfYyZ4aeMZZE2etfT4Aj52u7y+RQ=",
438
+ "url": "_framework/MindExecution.Plugins.PlanMaster.9r08m5atjr.dll"
439
439
  },
440
440
  {
441
- "hash": "sha256-I6jStz9j3BGIhprlmF+TfL5WpVuBKSVm3XYovlD5f+w=",
442
- "url": "_framework/MindExecution.Plugins.YouTube.1v8o9nnlzq.dll"
441
+ "hash": "sha256-RUR2I22IaFbLmHY/Gcw7wfwiybsIs8m+kgMfo1i78+w=",
442
+ "url": "_framework/MindExecution.Plugins.YouTube.fjkbniwa4o.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-8CQC4804ef5KlfH9/MULdbWZzL7YvpS7elf263ZYRlw=",
446
- "url": "_framework/MindExecution.Shared.04anisxh35.dll"
445
+ "hash": "sha256-bh3YoyPl9H4U9q8TO3u6hoJLMoWW0sYxJyJwj8heek0=",
446
+ "url": "_framework/MindExecution.Shared.e0qnm5vtax.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-CF3kk5RKIgyaPytT7v1WVS9qj73pzXVbQ+vMxfyQQ2Y=",
450
- "url": "_framework/MindExecution.Web.0qdidsf6sl.dll"
449
+ "hash": "sha256-AHwwn1rOC7uDlpCJJLndmyf97G7nM018K4WpzRuPwTg=",
450
+ "url": "_framework/MindExecution.Web.wk9yv02eva.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-pgZK03P5rDjSp61sZae0rpKy7fjg88JxqpQFSe7f/6Q=",
773
+ "hash": "sha256-v2ZTmYhjrtyEfbHSvkcdnYwoI+IoWSf/opZgluPefHA=",
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-O+OZkgaXnLILhly2Of1gV09vQgw+YgNGMz375BoNSTk=",
837
+ "hash": "sha256-TU40FZ+Rzj9ZDbwOAysHnWs1F2588obb2+wwcp7Mcy4=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: xZ2srqoC */
1
+ /* Manifest version: tyUOiEjY */
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