@mindexec/cli 0.2.24 → 0.2.26

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 (95) hide show
  1. package/package.json +6 -6
  2. package/remote-hub.js +130 -1
  3. package/scripts/remote-fleet-render-smoke.mjs +122 -93
  4. package/scripts/remote-http-smoke.mjs +218 -40
  5. package/scripts/remote-hub-smoke.mjs +139 -0
  6. package/server.js +17 -0
  7. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +18 -1
  8. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +676 -456
  9. package/wwwroot/_framework/{Microsoft.CSharp.qrvp77qmhs.dll → Microsoft.CSharp.8bsm8vx6su.dll} +0 -0
  10. package/wwwroot/_framework/MindExecution.Core.6rfnfdndxq.dll +0 -0
  11. package/wwwroot/_framework/{MindExecution.Kernel.qt0p5apeu2.dll → MindExecution.Kernel.z56elxihok.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Admin.i9eswmhltm.dll → MindExecution.Plugins.Admin.p5cs4ap87v.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Business.1eayoj2kvc.dll → MindExecution.Plugins.Business.s35og5uz44.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Concept.a3850nmm3d.dll → MindExecution.Plugins.Concept.zczca3fsxz.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.Directory.l3rmdlu7o7.dll → MindExecution.Plugins.Directory.y74f55e8x3.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.2x5gsi74yi.dll → MindExecution.Plugins.PlanMaster.jpdwbefrh1.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.vbl462eegw.dll → MindExecution.Plugins.YouTube.8nz4wv2nsj.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Shared.l2w05i7sd6.dll → MindExecution.Shared.ihh8mkcn5x.dll} +0 -0
  19. package/wwwroot/_framework/MindExecution.Web.0cs29v57jl.dll +0 -0
  20. package/wwwroot/_framework/{System.Collections.x53e19vfsj.dll → System.Collections.23yxgetbju.dll} +0 -0
  21. package/wwwroot/_framework/{System.Collections.Concurrent.y1zmvuyipi.dll → System.Collections.Concurrent.vow3rm1dku.dll} +0 -0
  22. package/wwwroot/_framework/{System.Collections.Immutable.ug3j698qms.dll → System.Collections.Immutable.ap9596utv5.dll} +0 -0
  23. package/wwwroot/_framework/{System.Collections.NonGeneric.h66hj3863h.dll → System.Collections.NonGeneric.npjkaz40oc.dll} +0 -0
  24. package/wwwroot/_framework/{System.Collections.Specialized.umr3y27ntj.dll → System.Collections.Specialized.ugkjbs6p02.dll} +0 -0
  25. package/wwwroot/_framework/{System.ComponentModel.Annotations.tz6gnt4ebt.dll → System.ComponentModel.Annotations.clwo0z2oyu.dll} +0 -0
  26. package/wwwroot/_framework/{System.ComponentModel.Primitives.j7tiphu4rg.dll → System.ComponentModel.Primitives.end4v0xe2c.dll} +0 -0
  27. package/wwwroot/_framework/{System.ComponentModel.TypeConverter.ujlztox1gx.dll → System.ComponentModel.TypeConverter.tgtp5sm4iv.dll} +0 -0
  28. package/wwwroot/_framework/{System.ComponentModel.x9xz0ojfb6.dll → System.ComponentModel.wupoltkk1t.dll} +0 -0
  29. package/wwwroot/_framework/{System.Console.ijzpqmj7ne.dll → System.Console.9dik0wogo2.dll} +0 -0
  30. package/wwwroot/_framework/{System.Data.Common.1r0sqffq1p.dll → System.Data.Common.4v8jejsiu0.dll} +0 -0
  31. package/wwwroot/_framework/{System.Diagnostics.DiagnosticSource.9upoqwq09o.dll → System.Diagnostics.DiagnosticSource.e2q75ondtq.dll} +0 -0
  32. package/wwwroot/_framework/{System.Diagnostics.Process.m99azzntjm.dll → System.Diagnostics.Process.9736yjnxs8.dll} +0 -0
  33. package/wwwroot/_framework/{System.Diagnostics.TraceSource.pl7wv26myr.dll → System.Diagnostics.TraceSource.h9al53gbbw.dll} +0 -0
  34. package/wwwroot/_framework/{System.Diagnostics.Tracing.crlhfx6tut.dll → System.Diagnostics.Tracing.h4bcp2fo98.dll} +0 -0
  35. package/wwwroot/_framework/{System.Drawing.mi7d8hwowb.dll → System.Drawing.64oovy8qts.dll} +0 -0
  36. package/wwwroot/_framework/{System.Drawing.Primitives.22e4y9ikq9.dll → System.Drawing.Primitives.o6jiqpgbgl.dll} +0 -0
  37. package/wwwroot/_framework/{System.Formats.Asn1.jx23sjiqnn.dll → System.Formats.Asn1.rylx5ipd40.dll} +0 -0
  38. package/wwwroot/_framework/{System.IO.Compression.6fyoii3uej.dll → System.IO.Compression.iceabaupns.dll} +0 -0
  39. package/wwwroot/_framework/{System.IO.Pipelines.vg77t4cd4d.dll → System.IO.Pipelines.uw8csd3mlz.dll} +0 -0
  40. package/wwwroot/_framework/{System.Linq.Expressions.24xqiypwdt.dll → System.Linq.Expressions.ty95ava37f.dll} +0 -0
  41. package/wwwroot/_framework/{System.Linq.Queryable.hvd01d6rsa.dll → System.Linq.Queryable.hs2195jrwy.dll} +0 -0
  42. package/wwwroot/_framework/{System.Linq.1bkoxlqgmq.dll → System.Linq.hssodjwmlf.dll} +0 -0
  43. package/wwwroot/_framework/{System.Memory.8dx3lwgym4.dll → System.Memory.1k78n7wdxb.dll} +0 -0
  44. package/wwwroot/_framework/{System.Net.Http.eitrz660my.dll → System.Net.Http.3br8rfql4c.dll} +0 -0
  45. package/wwwroot/_framework/{System.Net.Http.Json.3mhdm9l1rf.dll → System.Net.Http.Json.860wbh17d8.dll} +0 -0
  46. package/wwwroot/_framework/{System.Net.NetworkInformation.3pkuofcv9r.dll → System.Net.NetworkInformation.e3wr00853o.dll} +0 -0
  47. package/wwwroot/_framework/{System.Net.Ping.8clj5pklrp.dll → System.Net.Ping.r5cw4mf1a4.dll} +0 -0
  48. package/wwwroot/_framework/{System.Net.Primitives.qrp4wcjz1p.dll → System.Net.Primitives.ksxwiwlvhu.dll} +0 -0
  49. package/wwwroot/_framework/{System.Net.WebSockets.qp6u31zvm5.dll → System.Net.WebSockets.6rt3n3gl2q.dll} +0 -0
  50. package/wwwroot/_framework/{System.Net.WebSockets.Client.2u6pv01g69.dll → System.Net.WebSockets.Client.z3usrzo7rz.dll} +0 -0
  51. package/wwwroot/_framework/{System.Numerics.Vectors.kc7ufp2j4l.dll → System.Numerics.Vectors.lbdzx8reja.dll} +0 -0
  52. package/wwwroot/_framework/{System.ObjectModel.qv82fot1ib.dll → System.ObjectModel.yct1gdirzf.dll} +0 -0
  53. package/wwwroot/_framework/{System.Private.CoreLib.rkafq04oma.dll → System.Private.CoreLib.ns29bor93l.dll} +0 -0
  54. package/wwwroot/_framework/{System.Private.Uri.t9542hmr6j.dll → System.Private.Uri.zp9kmg0z93.dll} +0 -0
  55. package/wwwroot/_framework/{System.Private.Xml.Linq.n8n3ptrbwu.dll → System.Private.Xml.Linq.lgv2n0akl4.dll} +0 -0
  56. package/wwwroot/_framework/{System.Private.Xml.rxd3tytisn.dll → System.Private.Xml.dpsk8g304y.dll} +0 -0
  57. package/wwwroot/_framework/{System.Reflection.Emit.ILGeneration.stxyk8zoo1.dll → System.Reflection.Emit.ILGeneration.1tcuz2cmbk.dll} +0 -0
  58. package/wwwroot/_framework/{System.Reflection.Emit.Lightweight.6xrd5v8vg0.dll → System.Reflection.Emit.Lightweight.ddt2wylovg.dll} +0 -0
  59. package/wwwroot/_framework/{System.Reflection.Emit.9tjhp6y0j3.dll → System.Reflection.Emit.d8vkadiwhg.dll} +0 -0
  60. package/wwwroot/_framework/{System.Reflection.Primitives.wgn8fpwwvv.dll → System.Reflection.Primitives.cpsl71xd1z.dll} +0 -0
  61. package/wwwroot/_framework/{System.Runtime.InteropServices.te07xr2we9.dll → System.Runtime.InteropServices.31vjgsfk1o.dll} +0 -0
  62. package/wwwroot/_framework/{System.Runtime.InteropServices.JavaScript.sliym526xh.dll → System.Runtime.InteropServices.JavaScript.pn2wvizzet.dll} +0 -0
  63. package/wwwroot/_framework/{System.Runtime.InteropServices.RuntimeInformation.oji7zut14z.dll → System.Runtime.InteropServices.RuntimeInformation.l5rk496q70.dll} +0 -0
  64. package/wwwroot/_framework/{System.Runtime.Intrinsics.507y4h8nzq.dll → System.Runtime.Intrinsics.0zee9qcqfy.dll} +0 -0
  65. package/wwwroot/_framework/{System.Runtime.Loader.v7gk4bse0k.dll → System.Runtime.Loader.xw2jr9wl92.dll} +0 -0
  66. package/wwwroot/_framework/{System.Runtime.Numerics.eqy5xjv3nd.dll → System.Runtime.Numerics.ezj1dfyvbj.dll} +0 -0
  67. package/wwwroot/_framework/{System.Runtime.Serialization.Formatters.zpkrub8lab.dll → System.Runtime.Serialization.Formatters.0y019e9zkr.dll} +0 -0
  68. package/wwwroot/_framework/{System.Runtime.Serialization.Primitives.vhkpnbxjip.dll → System.Runtime.Serialization.Primitives.bia5wb62c8.dll} +0 -0
  69. package/wwwroot/_framework/{System.Runtime.jn319d5nyg.dll → System.Runtime.h9cduidfkh.dll} +0 -0
  70. package/wwwroot/_framework/{System.Security.Claims.0ztig1q9vo.dll → System.Security.Claims.2r18wim2rl.dll} +0 -0
  71. package/wwwroot/_framework/{System.Security.Cryptography.vttizqc9ho.dll → System.Security.Cryptography.qqgybpoucx.dll} +0 -0
  72. package/wwwroot/_framework/{System.Text.Encoding.Extensions.utdd47ny8f.dll → System.Text.Encoding.Extensions.9t3hs6kyll.dll} +0 -0
  73. package/wwwroot/_framework/{System.Text.Encodings.Web.wah8r1zoe0.dll → System.Text.Encodings.Web.wftjfh2crk.dll} +0 -0
  74. package/wwwroot/_framework/{System.Text.Json.kxlfxj0wrs.dll → System.Text.Json.4s25e3op6i.dll} +0 -0
  75. package/wwwroot/_framework/{System.Text.RegularExpressions.dbqn58klox.dll → System.Text.RegularExpressions.cvkox11l6l.dll} +0 -0
  76. package/wwwroot/_framework/{System.Threading.Channels.hfa7j0uv2w.dll → System.Threading.Channels.419493szqu.dll} +0 -0
  77. package/wwwroot/_framework/{System.Threading.Thread.caul0pdqul.dll → System.Threading.Thread.xhmys87xh5.dll} +0 -0
  78. package/wwwroot/_framework/{System.Threading.42ao9vi047.dll → System.Threading.b66vzsz9g1.dll} +0 -0
  79. package/wwwroot/_framework/{System.Transactions.Local.fimi2hamzo.dll → System.Transactions.Local.7zfffdvnwf.dll} +0 -0
  80. package/wwwroot/_framework/{System.Web.HttpUtility.gq8yz50p2e.dll → System.Web.HttpUtility.9up6xfbtuq.dll} +0 -0
  81. package/wwwroot/_framework/{System.Xml.Linq.kitin4zjoj.dll → System.Xml.Linq.n5rzv9nbf7.dll} +0 -0
  82. package/wwwroot/_framework/{System.Xml.ReaderWriter.kzvw3qgxb0.dll → System.Xml.ReaderWriter.ag8pilllob.dll} +0 -0
  83. package/wwwroot/_framework/{System.Xml.XDocument.c539ki6cuq.dll → System.Xml.XDocument.sn51jas17n.dll} +0 -0
  84. package/wwwroot/_framework/{System.m05i39uvk9.dll → System.brmz7yk5qh.dll} +0 -0
  85. package/wwwroot/_framework/blazor.boot.json +161 -161
  86. package/wwwroot/_framework/dotnet.js +1 -1
  87. package/wwwroot/_framework/{dotnet.native.vz0adxojrz.wasm → dotnet.native.boem75ye5i.wasm} +0 -0
  88. package/wwwroot/_framework/{dotnet.native.xsn1d6x2kd.js → dotnet.native.qc8g39g30v.js} +1 -1
  89. package/wwwroot/_framework/{dotnet.runtime.dstopyvqzi.js → dotnet.runtime.opaiwunc3t.js} +1 -1
  90. package/wwwroot/_framework/{netstandard.0xet7jg7ky.dll → netstandard.yvr3prsx0x.dll} +0 -0
  91. package/wwwroot/index.html +1 -1
  92. package/wwwroot/service-worker-assets.js +166 -166
  93. package/wwwroot/service-worker.js +1 -1
  94. package/wwwroot/_framework/MindExecution.Core.ri6sjbi2qk.dll +0 -0
  95. package/wwwroot/_framework/MindExecution.Web.i4ojmz00kp.dll +0 -0
@@ -3551,6 +3551,7 @@
3551
3551
  { key: 'decision', iconClass: 'fa-solid fa-code-branch' },
3552
3552
  { key: 'user-check', iconClass: 'fa-solid fa-user-check' },
3553
3553
  { key: 'output', iconClass: 'fa-solid fa-file-export' },
3554
+ { key: 'network-wired', iconClass: 'fa-solid fa-network-wired' },
3554
3555
  { key: 'robot', iconClass: 'fa-solid fa-robot' }
3555
3556
  ];
3556
3557
 
@@ -12139,6 +12140,25 @@
12139
12140
  return `${Math.floor(seconds / 86400)}d ago`;
12140
12141
  }
12141
12142
 
12143
+ function formatRemoteFleetTimeUntil(value) {
12144
+ const timestamp = Date.parse(String(value || ''));
12145
+ if (!Number.isFinite(timestamp)) {
12146
+ return '-';
12147
+ }
12148
+
12149
+ const seconds = Math.round((timestamp - Date.now()) / 1000);
12150
+ if (seconds <= 0) {
12151
+ return 'expired';
12152
+ }
12153
+ if (seconds < 60) {
12154
+ return `${seconds}s left`;
12155
+ }
12156
+ if (seconds < 3600) {
12157
+ return `${Math.floor(seconds / 60)}m left`;
12158
+ }
12159
+ return `${Math.floor(seconds / 3600)}h left`;
12160
+ }
12161
+
12142
12162
  function createRemoteFleetStat(label, value, tone = 'default', key = '') {
12143
12163
  const item = document.createElement('div');
12144
12164
  if (key) {
@@ -12211,6 +12231,86 @@
12211
12231
  return button;
12212
12232
  }
12213
12233
 
12234
+ function attachRemoteFleetTitleActions(bodyView, hostButton, stopHostButton, hostState) {
12235
+ const container = bodyView?.closest?.('.map-node-remote-fleet');
12236
+ const header = container?.querySelector?.('.map-node-memo__header') || null;
12237
+ const existingActions = header?.querySelector?.('[data-remote-fleet-title-actions="true"]') || null;
12238
+ existingActions?.remove?.();
12239
+
12240
+ const titleActions = document.createElement('div');
12241
+ titleActions.dataset.remoteFleetTitleActions = 'true';
12242
+ titleActions.style.cssText = `
12243
+ display: inline-flex;
12244
+ align-items: center;
12245
+ justify-content: flex-end;
12246
+ gap: 7px;
12247
+ min-width: 0;
12248
+ pointer-events: auto;
12249
+ `;
12250
+
12251
+ const normalizedState = String(hostState || 'inactive').trim().toLowerCase();
12252
+ const active = normalizedState === 'hosting';
12253
+ const indicator = document.createElement('span');
12254
+ indicator.dataset.remoteFleetHostIndicator = 'true';
12255
+ indicator.dataset.remoteFleetHostActive = active ? 'true' : 'false';
12256
+ indicator.title = active ? 'Host active' : 'Host inactive';
12257
+ indicator.style.cssText = `
12258
+ flex: 0 0 auto;
12259
+ width: 9px;
12260
+ height: 9px;
12261
+ border-radius: 999px;
12262
+ background: ${active ? '#2563eb' : '#94a3b8'};
12263
+ box-shadow: ${active ? '0 0 0 4px rgba(37, 99, 235, 0.14)' : '0 0 0 4px rgba(148, 163, 184, 0.14)'};
12264
+ `;
12265
+
12266
+ titleActions.appendChild(indicator);
12267
+ titleActions.appendChild(hostButton);
12268
+ if (stopHostButton) {
12269
+ titleActions.appendChild(stopHostButton);
12270
+ }
12271
+
12272
+ if (header) {
12273
+ header.style.gridTemplateColumns = '46px minmax(0, 1fr) auto';
12274
+ header.appendChild(titleActions);
12275
+ } else {
12276
+ titleActions.style.alignSelf = 'flex-end';
12277
+ bodyView?.prepend?.(titleActions);
12278
+ }
12279
+
12280
+ return titleActions;
12281
+ }
12282
+
12283
+ function createRemoteFleetEmptyScreens() {
12284
+ const shell = document.createElement('section');
12285
+ shell.dataset.remoteFleetEmptyScreens = 'true';
12286
+ shell.style.cssText = `
12287
+ flex: 1 1 auto;
12288
+ min-height: 0;
12289
+ overflow: hidden;
12290
+ display: grid;
12291
+ grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
12292
+ align-content: start;
12293
+ gap: 10px;
12294
+ padding: 2px 4px 6px 0;
12295
+ `;
12296
+
12297
+ for (let index = 0; index < 6; index += 1) {
12298
+ const screen = document.createElement('div');
12299
+ screen.dataset.remoteFleetEmptyScreen = 'true';
12300
+ screen.style.cssText = `
12301
+ aspect-ratio: 16 / 9;
12302
+ min-height: 96px;
12303
+ border-radius: 8px;
12304
+ border: 1px solid rgba(203, 213, 225, 0.72);
12305
+ background: #ffffff;
12306
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.96), 0 8px 18px rgba(15, 23, 42, 0.04);
12307
+ `;
12308
+ shell.appendChild(screen);
12309
+ }
12310
+
12311
+ return shell;
12312
+ }
12313
+
12214
12314
  function createRemoteFleetSelect(options, value, title) {
12215
12315
  const select = document.createElement('select');
12216
12316
  select.title = title || '';
@@ -12244,6 +12344,142 @@
12244
12344
  const REMOTE_FLEET_TASK_FOLLOW_INITIAL_MS = 250;
12245
12345
  const REMOTE_FLEET_TASK_FOLLOW_REFRESH_MS = 2000;
12246
12346
  const REMOTE_FLEET_TASK_FOLLOW_MAX_TICKS = 60;
12347
+ const REMOTE_FLEET_HOST_LEASE_REFRESH_MS = 10000;
12348
+ const remoteFleetHostLeaseTimers = new Map();
12349
+ const remoteFleetLocalHostTargets = new Map();
12350
+
12351
+ function findRemoteFleetBodyByNodeId(nodeId) {
12352
+ const id = String(nodeId || '').trim();
12353
+ if (!id) return null;
12354
+ return Array.from(document.querySelectorAll('.map-node-remote-fleet__body[data-node-id]'))
12355
+ .find(body => String(body.dataset.nodeId || '').trim() === id) || null;
12356
+ }
12357
+
12358
+ function stopRemoteFleetHostLeaseTimer(nodeId) {
12359
+ const id = String(nodeId || '').trim();
12360
+ if (!id) return;
12361
+ const entry = remoteFleetHostLeaseTimers.get(id);
12362
+ if (entry?.timer) {
12363
+ clearInterval(entry.timer);
12364
+ }
12365
+ remoteFleetHostLeaseTimers.delete(id);
12366
+ }
12367
+
12368
+ function startRemoteFleetHostLeaseTimer(nodeId, renewCallback) {
12369
+ const id = String(nodeId || '').trim();
12370
+ if (!id || typeof renewCallback !== 'function') return;
12371
+ const existing = remoteFleetHostLeaseTimers.get(id);
12372
+ if (existing) {
12373
+ existing.renewCallback = renewCallback;
12374
+ return;
12375
+ }
12376
+
12377
+ const entry = { renewCallback, timer: null };
12378
+ const timer = setInterval(async () => {
12379
+ const currentBody = findRemoteFleetBodyByNodeId(id);
12380
+ if (!currentBody || currentBody.dataset.remoteFleetHostState !== 'hosting') {
12381
+ stopRemoteFleetHostLeaseTimer(id);
12382
+ return;
12383
+ }
12384
+
12385
+ try {
12386
+ await entry.renewCallback();
12387
+ } catch {
12388
+ stopRemoteFleetHostLeaseTimer(id);
12389
+ }
12390
+ }, REMOTE_FLEET_HOST_LEASE_REFRESH_MS);
12391
+
12392
+ timer?.unref?.();
12393
+ entry.timer = timer;
12394
+ remoteFleetHostLeaseTimers.set(id, entry);
12395
+ }
12396
+
12397
+ function getRemoteFleetNodeStateId(nodeState) {
12398
+ return String(nodeState?.nodeId ?? nodeState?.NodeId ?? nodeState?.id ?? nodeState?.Id ?? '').trim();
12399
+ }
12400
+
12401
+ function ensureRemoteFleetNodeStateMetadata(nodeState) {
12402
+ if (!nodeState || typeof nodeState !== 'object') return null;
12403
+ const metadata = nodeState.metadata && typeof nodeState.metadata === 'object'
12404
+ ? nodeState.metadata
12405
+ : nodeState.Metadata && typeof nodeState.Metadata === 'object'
12406
+ ? nodeState.Metadata
12407
+ : {};
12408
+ nodeState.metadata = metadata;
12409
+ nodeState.Metadata = metadata;
12410
+ return metadata;
12411
+ }
12412
+
12413
+ function readRemoteFleetObjectField(source, camelKey, pascalKey, fallback = '') {
12414
+ const value = source?.[camelKey] ?? source?.[pascalKey] ?? fallback;
12415
+ return value === undefined || value === null ? fallback : value;
12416
+ }
12417
+
12418
+ function isRemoteFleetResultSuccess(result) {
12419
+ return result?.success === true || result?.Success === true || result?.ok === true || result?.Ok === true;
12420
+ }
12421
+
12422
+ function isRemoteFleetResultActive(result) {
12423
+ return result?.active === true || result?.Active === true;
12424
+ }
12425
+
12426
+ function isRemoteFleetResultExplicitlyInactive(result) {
12427
+ return result?.active === false || result?.Active === false;
12428
+ }
12429
+
12430
+ function rememberRemoteFleetLocalHostTarget(nodeId, enabled, result) {
12431
+ const id = String(nodeId || '').trim();
12432
+ if (!id) return;
12433
+ if (enabled !== true) {
12434
+ if (isRemoteFleetResultSuccess(result)) {
12435
+ remoteFleetLocalHostTargets.delete(id);
12436
+ stopRemoteFleetHostLeaseTimer(id);
12437
+ }
12438
+ return;
12439
+ }
12440
+
12441
+ if (!isRemoteFleetResultSuccess(result) || isRemoteFleetResultExplicitlyInactive(result)) {
12442
+ return;
12443
+ }
12444
+
12445
+ const hostTarget = result?.hostTarget || result?.HostTarget || {};
12446
+ const now = new Date();
12447
+ const expiresAt = String(readRemoteFleetObjectField(hostTarget, 'expiresAt', 'ExpiresAt', '') || '').trim()
12448
+ || new Date(now.getTime() + 60000).toISOString();
12449
+ remoteFleetLocalHostTargets.set(id, {
12450
+ nodeId: id,
12451
+ leaseId: String(readRemoteFleetObjectField(hostTarget, 'leaseId', 'LeaseId', '') || '').trim(),
12452
+ endpoint: String(readRemoteFleetObjectField(hostTarget, 'endpoint', 'Endpoint', '') || '').trim(),
12453
+ activatedAt: String(readRemoteFleetObjectField(hostTarget, 'activatedAt', 'ActivatedAt', '') || '').trim() || now.toISOString(),
12454
+ updatedAt: String(readRemoteFleetObjectField(hostTarget, 'updatedAt', 'UpdatedAt', '') || '').trim() || now.toISOString(),
12455
+ expiresAt
12456
+ });
12457
+ }
12458
+
12459
+ function applyRemoteFleetLocalHostTarget(nodeState) {
12460
+ const nodeId = getRemoteFleetNodeStateId(nodeState);
12461
+ if (!nodeId) return nodeState;
12462
+ const localHost = remoteFleetLocalHostTargets.get(nodeId);
12463
+ if (!localHost) return nodeState;
12464
+
12465
+ const expiresTime = Date.parse(localHost.expiresAt);
12466
+ if (!Number.isFinite(expiresTime) || expiresTime <= Date.now()) {
12467
+ remoteFleetLocalHostTargets.delete(nodeId);
12468
+ stopRemoteFleetHostLeaseTimer(nodeId);
12469
+ return nodeState;
12470
+ }
12471
+
12472
+ const metadata = ensureRemoteFleetNodeStateMetadata(nodeState);
12473
+ if (!metadata) return nodeState;
12474
+ metadata.RemoteFleetHostTargetState = 'hosting';
12475
+ metadata.RemoteFleetHostTargetNodeId = nodeId;
12476
+ metadata.RemoteFleetHostTargetLeaseId = localHost.leaseId;
12477
+ metadata.RemoteFleetHostTargetEndpoint = localHost.endpoint || metadata.RemoteFleetHubEndpoint || metadata.remoteFleetHubEndpoint || '';
12478
+ metadata.RemoteFleetHostTargetActivatedAtUtc = localHost.activatedAt;
12479
+ metadata.RemoteFleetHostTargetUpdatedAtUtc = localHost.updatedAt;
12480
+ metadata.RemoteFleetHostTargetExpiresAtUtc = localHost.expiresAt;
12481
+ return nodeState;
12482
+ }
12247
12483
 
12248
12484
  function clearRemoteFleetTimers(bodyView) {
12249
12485
  if (!bodyView) return;
@@ -12259,6 +12495,10 @@
12259
12495
  clearTimeout(bodyView._remoteFleetTaskFollowTimer);
12260
12496
  bodyView._remoteFleetTaskFollowTimer = null;
12261
12497
  }
12498
+ if (bodyView._remoteFleetHostLeaseTimer) {
12499
+ clearInterval(bodyView._remoteFleetHostLeaseTimer);
12500
+ bodyView._remoteFleetHostLeaseTimer = null;
12501
+ }
12262
12502
  }
12263
12503
 
12264
12504
  function getRemoteFleetDeviceField(device, camelKey, pascalKey, fallback = '') {
@@ -12455,7 +12695,7 @@
12455
12695
  async function syncRemoteFleetNodeStateFromResult(result) {
12456
12696
  const nodeState = result?.nodeState || result?.refresh?.nodeState;
12457
12697
  if (nodeState && window.mindMap?.syncNodeStates) {
12458
- await window.mindMap.syncNodeStates([nodeState]);
12698
+ await window.mindMap.syncNodeStates([applyRemoteFleetLocalHostTarget(nodeState)]);
12459
12699
  }
12460
12700
  }
12461
12701
 
@@ -12465,11 +12705,11 @@
12465
12705
 
12466
12706
  const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
12467
12707
  const device = parseRemoteFleetPinnedDevice(nodeModel);
12468
- const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
12469
12708
  const hubStatus = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubStatus', 'offline');
12470
12709
  const refreshedAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastRefreshAtUtc', '');
12471
12710
  const lastError = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastError', '');
12472
12711
  const deviceId = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetPinnedDeviceId', device ? getRemoteFleetDeviceId(device) : '');
12712
+ const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
12473
12713
 
12474
12714
  bodyView.dataset.src = `remote-device:${deviceId}:${refreshedAt}`;
12475
12715
  bodyView.classList.add('map-node-remote-fleet__body');
@@ -12997,6 +13237,16 @@
12997
13237
  if (focusedDevice) {
12998
13238
  bodyView.dataset.remoteFleetFocusDeviceId = getRemoteFleetDeviceId(focusedDevice);
12999
13239
  }
13240
+ const selectedState = bodyView.dataset.remoteFleetSelectedDeviceId || focusState || '';
13241
+ const selectedDevice = devices.find(device => getRemoteFleetDeviceId(device) === selectedState)
13242
+ || focusedDevice
13243
+ || devices[0]
13244
+ || null;
13245
+ if (selectedDevice) {
13246
+ bodyView.dataset.remoteFleetSelectedDeviceId = getRemoteFleetDeviceId(selectedDevice);
13247
+ } else {
13248
+ delete bodyView.dataset.remoteFleetSelectedDeviceId;
13249
+ }
13000
13250
  const total = Number(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetDeviceCount', devices.length));
13001
13251
  const connected = Number(getRemoteFleetMetadataValue(
13002
13252
  nodeModel,
@@ -13004,18 +13254,18 @@
13004
13254
  devices.filter(isRemoteFleetDeviceConnected).length));
13005
13255
  const taskCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceTaskCapable(device)).length;
13006
13256
  const aiCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceAiCapable(device)).length;
13007
- const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
13008
- const managerPackage = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetManagerPackage', '@mindexec/cli');
13009
- const managerVersion = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetManagerVersion', '');
13010
- const command = getRemoteFleetMetadataValue(
13011
- nodeModel,
13012
- 'RemoteFleetConnectCommand',
13013
- `npx @mindexec/remote connect --manager ${endpoint} --pair <pair-token>`);
13014
13257
  const hubStatus = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubStatus', 'offline');
13015
13258
  const refreshedAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastRefreshAtUtc', '');
13016
13259
  const lastError = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastError', '');
13260
+ const hostTargetState = String(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetState', 'inactive')).trim().toLowerCase();
13261
+ const isHostingTarget = hostTargetState === 'hosting';
13262
+
13263
+ if (!isHostingTarget) {
13264
+ stopRemoteFleetHostLeaseTimer(nodeId);
13265
+ }
13017
13266
 
13018
13267
  bodyView.dataset.src = `remote-fleet:${refreshedAt}:${devices.length}:${connected}`;
13268
+ bodyView.dataset.remoteFleetHostState = hostTargetState;
13019
13269
  bodyView.classList.add('map-node-remote-fleet__body');
13020
13270
  bodyView.innerHTML = '';
13021
13271
  bodyView.style.cssText = `
@@ -13030,6 +13280,28 @@
13030
13280
  background: linear-gradient(180deg, rgba(248, 250, 252, 0.96), rgba(241, 245, 249, 0.92));
13031
13281
  `;
13032
13282
 
13283
+ const hostButton = createRemoteFleetButton(
13284
+ 'Set Host',
13285
+ isHostingTarget
13286
+ ? 'Renew this monitor as the active host target for remote agents.'
13287
+ : 'Set this monitor as the active host target for remote agents.',
13288
+ 'set-host');
13289
+ hostButton.dataset.remoteFleetHostActive = isHostingTarget ? 'true' : 'false';
13290
+ hostButton.style.height = '30px';
13291
+ hostButton.style.minWidth = '78px';
13292
+ hostButton.style.borderColor = isHostingTarget ? 'rgba(37, 99, 235, 0.44)' : 'rgba(37, 99, 235, 0.32)';
13293
+ hostButton.style.background = isHostingTarget ? 'rgba(239, 246, 255, 0.98)' : '#ffffff';
13294
+ hostButton.style.color = '#1d4ed8';
13295
+ let stopHostButton = null;
13296
+ if (isHostingTarget) {
13297
+ stopHostButton = createRemoteFleetButton('Stop', 'Stop using this monitor as the host target.', 'stop-host');
13298
+ stopHostButton.style.height = '30px';
13299
+ stopHostButton.style.borderColor = 'rgba(239, 68, 68, 0.30)';
13300
+ stopHostButton.style.color = '#b91c1c';
13301
+ stopHostButton.style.background = 'rgba(254, 242, 242, 0.94)';
13302
+ }
13303
+ attachRemoteFleetTitleActions(bodyView, hostButton, stopHostButton, hostTargetState);
13304
+
13033
13305
  const top = document.createElement('div');
13034
13306
  top.style.cssText = `
13035
13307
  display: grid;
@@ -13043,92 +13315,6 @@
13043
13315
  top.appendChild(createRemoteFleetStat('AI', String(aiCapableCount), aiCapableCount > 0 ? 'online' : 'default'));
13044
13316
  bodyView.appendChild(top);
13045
13317
 
13046
- const managerRow = document.createElement('div');
13047
- managerRow.dataset.remoteFleetManagerVersion = 'true';
13048
- managerRow.textContent = `${managerPackage}${managerVersion ? ` ${managerVersion}` : ''} - ${endpoint}`;
13049
- managerRow.title = managerRow.textContent;
13050
- managerRow.style.cssText = `
13051
- flex: 0 0 auto;
13052
- min-width: 0;
13053
- overflow: hidden;
13054
- text-overflow: ellipsis;
13055
- white-space: nowrap;
13056
- color: #475569;
13057
- font-size: 10px;
13058
- font-weight: 850;
13059
- line-height: 1.2;
13060
- letter-spacing: 0;
13061
- `;
13062
- bodyView.appendChild(managerRow);
13063
-
13064
- const commandRow = document.createElement('div');
13065
- commandRow.style.cssText = `
13066
- display: grid;
13067
- grid-template-columns: minmax(0, 1fr) auto auto auto;
13068
- gap: 8px;
13069
- align-items: center;
13070
- flex: 0 0 auto;
13071
- `;
13072
-
13073
- const commandText = document.createElement('code');
13074
- commandText.textContent = command;
13075
- commandText.style.cssText = `
13076
- min-width: 0;
13077
- overflow: hidden;
13078
- text-overflow: ellipsis;
13079
- white-space: nowrap;
13080
- padding: 8px 10px;
13081
- border-radius: 7px;
13082
- background: rgba(15, 23, 42, 0.92);
13083
- color: #e2e8f0;
13084
- font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
13085
- font-size: 11px;
13086
- line-height: 1.2;
13087
- `;
13088
- commandText.title = command;
13089
-
13090
- const copyButton = createRemoteFleetButton('Copy', 'Copy agent command', 'copy-command');
13091
- const refreshButton = createRemoteFleetButton('Refresh', 'Refresh remote devices', 'refresh');
13092
- const autoMonitorLabel = document.createElement('label');
13093
- autoMonitorLabel.title = 'Refresh stale thumbnails on a bounded timer';
13094
- autoMonitorLabel.style.cssText = `
13095
- display: inline-flex;
13096
- align-items: center;
13097
- justify-content: center;
13098
- gap: 6px;
13099
- height: 34px;
13100
- padding: 0 9px;
13101
- border-radius: 7px;
13102
- border: 1px solid rgba(14, 165, 233, 0.28);
13103
- background: ${autoMonitorState ? 'rgba(240, 249, 255, 0.92)' : 'rgba(248, 250, 252, 0.92)'};
13104
- color: ${autoMonitorState ? '#0369a1' : '#475569'};
13105
- font-size: 11px;
13106
- font-weight: 900;
13107
- letter-spacing: 0;
13108
- cursor: pointer;
13109
- pointer-events: auto;
13110
- user-select: none;
13111
- `;
13112
- const autoMonitorToggle = document.createElement('input');
13113
- autoMonitorToggle.type = 'checkbox';
13114
- autoMonitorToggle.checked = autoMonitorState;
13115
- autoMonitorToggle.dataset.remoteFleetAutoToggle = 'true';
13116
- autoMonitorToggle.style.cssText = `
13117
- width: 14px;
13118
- height: 14px;
13119
- margin: 0;
13120
- accent-color: #0284c7;
13121
- `;
13122
- const autoMonitorText = document.createElement('span');
13123
- autoMonitorText.textContent = 'Auto';
13124
- autoMonitorLabel.appendChild(autoMonitorToggle);
13125
- autoMonitorLabel.appendChild(autoMonitorText);
13126
- commandRow.appendChild(commandText);
13127
- commandRow.appendChild(autoMonitorLabel);
13128
- commandRow.appendChild(copyButton);
13129
- commandRow.appendChild(refreshButton);
13130
- bodyView.appendChild(commandRow);
13131
-
13132
13318
  const filterRow = document.createElement('div');
13133
13319
  filterRow.style.cssText = `
13134
13320
  display: grid;
@@ -13141,7 +13327,7 @@
13141
13327
  const searchInput = document.createElement('input');
13142
13328
  searchInput.type = 'search';
13143
13329
  searchInput.value = searchState;
13144
- searchInput.placeholder = 'Search devices';
13330
+ searchInput.placeholder = '';
13145
13331
  searchInput.dataset.remoteFleetSearch = 'true';
13146
13332
  searchInput.autocomplete = 'off';
13147
13333
  searchInput.spellcheck = false;
@@ -13213,11 +13399,11 @@
13213
13399
  filterRow.appendChild(groupSelect);
13214
13400
  filterRow.appendChild(densitySelect);
13215
13401
  filterRow.appendChild(matchCount);
13216
- bodyView.appendChild(filterRow);
13402
+ filterRow.dataset.remoteFleetAdvancedFilters = 'hidden';
13217
13403
 
13218
13404
  const taskRow = document.createElement('div');
13219
13405
  taskRow.style.cssText = `
13220
- display: grid;
13406
+ display: none;
13221
13407
  grid-template-columns: minmax(0, 1fr) auto auto auto;
13222
13408
  gap: 8px;
13223
13409
  align-items: stretch;
@@ -13225,7 +13411,7 @@
13225
13411
  `;
13226
13412
  const taskInput = document.createElement('textarea');
13227
13413
  taskInput.dataset.remoteFleetTaskInput = 'true';
13228
- taskInput.placeholder = 'Task for remote agents';
13414
+ taskInput.placeholder = '';
13229
13415
  taskInput.rows = 2;
13230
13416
  taskInput.style.cssText = `
13231
13417
  width: 100%;
@@ -13287,7 +13473,7 @@
13287
13473
  taskRow.appendChild(aiToggleLabel);
13288
13474
  taskRow.appendChild(sendVisibleButton);
13289
13475
  taskRow.appendChild(sendConnectedButton);
13290
- bodyView.appendChild(taskRow);
13476
+ taskRow.dataset.remoteFleetTaskComposer = 'hidden';
13291
13477
 
13292
13478
  const taskFeedback = document.createElement('div');
13293
13479
  taskFeedback.dataset.remoteFleetTaskFeedback = 'true';
@@ -13626,37 +13812,297 @@
13626
13812
  bodyView.appendChild(errorEl);
13627
13813
  }
13628
13814
 
13815
+ const createDevicePreview = (device, mode = 'tile') => {
13816
+ const name = getRemoteFleetDeviceName(device);
13817
+ const connectedDevice = isRemoteFleetDeviceConnected(device);
13818
+ const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
13819
+ const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
13820
+ const liveFrameDataUrl = String(device?.liveFrameDataUrl || device?.LiveFrameDataUrl || '');
13821
+ const liveFrameReceivedAt = String(device?.liveFrameReceivedAt || device?.LiveFrameReceivedAt || '');
13822
+ const hasThumbnail = hasRemoteFleetThumbnail(device);
13823
+ const hasLiveFrame = hasRemoteFleetLiveFrame(device);
13824
+ const previewDataUrl = hasLiveFrame ? liveFrameDataUrl : thumbnailDataUrl;
13825
+ const previewAt = hasLiveFrame ? liveFrameReceivedAt : thumbnailCapturedAt;
13826
+ const isDetail = mode === 'detail';
13827
+
13828
+ const preview = document.createElement('div');
13829
+ preview.dataset.remoteFleetDevicePreview = mode;
13830
+ preview.style.cssText = `
13831
+ position: relative;
13832
+ width: 100%;
13833
+ aspect-ratio: 16 / 9;
13834
+ overflow: hidden;
13835
+ border-radius: ${isDetail ? '8px' : '6px'};
13836
+ background: ${(hasLiveFrame || hasThumbnail) ? 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)' : '#ffffff'};
13837
+ border: 1px solid rgba(15, 23, 42, ${isDetail ? '0.16' : '0.10'});
13838
+ `;
13839
+
13840
+ if (hasLiveFrame || hasThumbnail) {
13841
+ const image = document.createElement('img');
13842
+ image.src = previewDataUrl;
13843
+ image.alt = hasLiveFrame ? `${name} live frame` : `${name} thumbnail`;
13844
+ image.loading = 'lazy';
13845
+ image.decoding = 'async';
13846
+ image.style.cssText = `
13847
+ width: 100%;
13848
+ height: 100%;
13849
+ object-fit: cover;
13850
+ display: block;
13851
+ `;
13852
+ preview.appendChild(image);
13853
+ } else {
13854
+ const placeholder = document.createElement('div');
13855
+ placeholder.dataset.remoteFleetScreenPlaceholder = 'true';
13856
+ placeholder.style.cssText = `
13857
+ position: absolute;
13858
+ inset: 0;
13859
+ background: #ffffff;
13860
+ `;
13861
+ preview.appendChild(placeholder);
13862
+ }
13863
+
13864
+ const dot = document.createElement('span');
13865
+ dot.style.cssText = `
13866
+ position: absolute;
13867
+ left: ${isDetail ? '9px' : '6px'};
13868
+ top: ${isDetail ? '9px' : '6px'};
13869
+ width: ${isDetail ? '10px' : '8px'};
13870
+ height: ${isDetail ? '10px' : '8px'};
13871
+ border-radius: 999px;
13872
+ background: ${connectedDevice ? '#10b981' : '#94a3b8'};
13873
+ box-shadow: 0 0 0 3px ${connectedDevice ? 'rgba(16,185,129,0.20)' : 'rgba(148,163,184,0.18)'};
13874
+ `;
13875
+ preview.appendChild(dot);
13876
+
13877
+ if (hasLiveFrame || (isDetail && previewAt)) {
13878
+ const badge = document.createElement('span');
13879
+ badge.textContent = hasLiveFrame
13880
+ ? (isDetail && previewAt ? `LIVE ${formatRemoteFleetAge(previewAt)}` : 'LIVE')
13881
+ : formatRemoteFleetAge(previewAt);
13882
+ badge.style.cssText = `
13883
+ position: absolute;
13884
+ right: ${isDetail ? '9px' : '6px'};
13885
+ bottom: ${isDetail ? '9px' : '6px'};
13886
+ max-width: calc(100% - ${isDetail ? '18px' : '12px'});
13887
+ padding: ${isDetail ? '4px 7px' : '3px 6px'};
13888
+ border-radius: 999px;
13889
+ background: ${hasLiveFrame ? 'rgba(220, 38, 38, 0.84)' : 'rgba(15, 23, 42, 0.72)'};
13890
+ color: #e2e8f0;
13891
+ font-size: ${isDetail ? '9px' : '8px'};
13892
+ font-weight: 950;
13893
+ line-height: 1;
13894
+ overflow: hidden;
13895
+ text-overflow: ellipsis;
13896
+ white-space: nowrap;
13897
+ letter-spacing: 0;
13898
+ `;
13899
+ preview.appendChild(badge);
13900
+ }
13901
+
13902
+ return preview;
13903
+ };
13904
+
13905
+ const createSelectedDevicePanel = device => {
13906
+ const panel = document.createElement('aside');
13907
+ panel.dataset.remoteFleetDetailPanel = 'true';
13908
+ panel.style.cssText = `
13909
+ min-width: 0;
13910
+ min-height: 0;
13911
+ overflow-y: auto;
13912
+ display: flex;
13913
+ flex-direction: column;
13914
+ gap: 9px;
13915
+ padding: 10px;
13916
+ border-radius: 8px;
13917
+ border: 1px solid rgba(148, 163, 184, 0.28);
13918
+ background: rgba(255, 255, 255, 0.86);
13919
+ `;
13920
+
13921
+ if (!device) {
13922
+ const empty = document.createElement('div');
13923
+ empty.textContent = 'Select a screen';
13924
+ empty.style.cssText = 'display:flex;align-items:center;justify-content:center;min-height:120px;color:#64748b;font-size:12px;font-weight:900;';
13925
+ panel.appendChild(empty);
13926
+ return panel;
13927
+ }
13928
+
13929
+ const deviceId = getRemoteFleetDeviceId(device);
13930
+ const name = getRemoteFleetDeviceName(device);
13931
+ const connectedDevice = isRemoteFleetDeviceConnected(device);
13932
+ const platform = [device?.platform || device?.Platform, device?.arch || device?.Arch]
13933
+ .filter(Boolean)
13934
+ .join(' / ') || 'unknown';
13935
+ const release = String(device?.release || device?.Release || '');
13936
+ const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
13937
+ const liveStreamEnabled = device?.liveStreamEnabled === true || device?.LiveStreamEnabled === true;
13938
+ const liveStreamActive = isRemoteFleetLiveActive(device);
13939
+ const liveStreamId = String(device?.liveStreamId || device?.LiveStreamId || '');
13940
+ const taskEnabled = isRemoteFleetDeviceTaskCapable(device);
13941
+ const aiAssistEnabled = isRemoteFleetDeviceAiCapable(device);
13942
+ const aiModel = String(device?.aiModel || device?.AiModel || '');
13943
+ const latestTaskStatus = String(device?.latestTaskStatus || device?.LatestTaskStatus || '');
13944
+ const latestTaskTitle = String(device?.latestTaskTitle || device?.LatestTaskTitle || '');
13945
+ const latestTaskApproval = String(device?.latestTaskApprovalLevel || device?.LatestTaskApprovalLevel || '');
13946
+ const latestTaskUpdatedAt = String(device?.latestTaskUpdatedAt || device?.LatestTaskUpdatedAt || '');
13947
+ const latestTaskError = String(device?.latestTaskError || device?.LatestTaskError || '');
13948
+ const latestTaskResultModel = String(device?.latestTaskResultModel || device?.LatestTaskResultModel || '');
13949
+ const latestTaskResultResponseId = String(device?.latestTaskResultResponseId || device?.LatestTaskResultResponseId || '');
13950
+ const latestTaskResult = String(device?.latestTaskResultSummary || device?.LatestTaskResultSummary || '');
13951
+ const statusText = connectedDevice
13952
+ ? (liveStreamActive ? 'Live' : (aiAssistEnabled ? `AI ${aiModel || 'ready'}` : 'Connected'))
13953
+ : 'Offline';
13954
+
13955
+ panel.dataset.deviceId = deviceId;
13956
+ panel.appendChild(createDevicePreview(device, 'detail'));
13957
+
13958
+ const header = document.createElement('div');
13959
+ header.style.cssText = 'display:flex;align-items:flex-start;justify-content:space-between;gap:8px;min-width:0;';
13960
+ const titleBox = document.createElement('div');
13961
+ titleBox.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:3px;';
13962
+ const title = document.createElement('strong');
13963
+ title.textContent = name;
13964
+ title.title = name;
13965
+ title.style.cssText = 'color:#0f172a;font-size:14px;font-weight:950;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
13966
+ const subtitle = document.createElement('span');
13967
+ subtitle.textContent = release ? `${platform} ${release}` : platform;
13968
+ subtitle.title = subtitle.textContent;
13969
+ subtitle.style.cssText = 'color:#64748b;font-size:11px;font-weight:750;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
13970
+ titleBox.appendChild(title);
13971
+ titleBox.appendChild(subtitle);
13972
+ const status = document.createElement('span');
13973
+ status.textContent = statusText;
13974
+ status.style.cssText = `
13975
+ flex: 0 0 auto;
13976
+ max-width: 82px;
13977
+ padding: 4px 7px;
13978
+ border-radius: 999px;
13979
+ background: ${liveStreamActive ? 'rgba(254, 226, 226, 0.92)' : (connectedDevice ? 'rgba(209, 250, 229, 0.92)' : 'rgba(226, 232, 240, 0.92)')};
13980
+ color: ${liveStreamActive ? '#b91c1c' : (connectedDevice ? '#047857' : '#475569')};
13981
+ font-size: 9px;
13982
+ font-weight: 950;
13983
+ line-height: 1;
13984
+ overflow: hidden;
13985
+ text-overflow: ellipsis;
13986
+ white-space: nowrap;
13987
+ letter-spacing: 0;
13988
+ `;
13989
+ header.appendChild(titleBox);
13990
+ header.appendChild(status);
13991
+ panel.appendChild(header);
13992
+
13993
+ const metrics = document.createElement('div');
13994
+ metrics.style.cssText = 'display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;';
13995
+ const addDetailMetric = (label, value) => {
13996
+ const metric = document.createElement('div');
13997
+ metric.style.cssText = 'min-width:0;padding:7px 8px;border-radius:7px;background:rgba(241,245,249,0.86);border:1px solid rgba(148,163,184,0.16);';
13998
+ const labelEl = document.createElement('div');
13999
+ labelEl.textContent = label;
14000
+ labelEl.style.cssText = 'color:#64748b;font-size:9px;font-weight:850;letter-spacing:0;text-transform:uppercase;';
14001
+ const valueEl = document.createElement('div');
14002
+ valueEl.textContent = value;
14003
+ valueEl.title = value;
14004
+ valueEl.style.cssText = 'color:#0f172a;font-size:12px;font-weight:950;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
14005
+ metric.appendChild(labelEl);
14006
+ metric.appendChild(valueEl);
14007
+ metrics.appendChild(metric);
14008
+ };
14009
+ addDetailMetric('Seen', formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt));
14010
+ addDetailMetric('Uptime', formatRemoteFleetDuration(device?.uptimeSec ?? device?.UptimeSec));
14011
+ addDetailMetric('Mem', formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio));
14012
+ addDetailMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
14013
+ panel.appendChild(metrics);
14014
+
14015
+ if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
14016
+ const taskBox = document.createElement('div');
14017
+ const taskTone = latestTaskError || latestTaskStatus === 'failed'
14018
+ ? 'error'
14019
+ : (latestTaskStatus === 'completed' ? 'done' : 'pending');
14020
+ taskBox.style.cssText = `
14021
+ display: flex;
14022
+ flex-direction: column;
14023
+ gap: 4px;
14024
+ min-width: 0;
14025
+ padding: 8px 9px;
14026
+ border-radius: 7px;
14027
+ background: ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.12)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
14028
+ 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)'};
14029
+ `;
14030
+ const taskLine = document.createElement('div');
14031
+ const taskModeLabel = latestTaskApproval === 'ai-assist' ? 'AI' : 'Task';
14032
+ taskLine.textContent = `${taskModeLabel} ${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}${latestTaskResultModel ? ` - ${latestTaskResultModel}` : ''}`;
14033
+ taskLine.title = latestTaskResultResponseId
14034
+ ? `${taskLine.textContent} (${latestTaskResultResponseId})`
14035
+ : taskLine.textContent;
14036
+ taskLine.style.cssText = `color:${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};font-size:10px;font-weight:950;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
14037
+ const taskSummary = document.createElement('div');
14038
+ taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
14039
+ taskSummary.title = taskSummary.textContent;
14040
+ taskSummary.style.cssText = 'color:#334155;font-size:10px;font-weight:750;line-height:1.25;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;letter-spacing:0;';
14041
+ taskBox.appendChild(taskLine);
14042
+ taskBox.appendChild(taskSummary);
14043
+ panel.appendChild(taskBox);
14044
+ }
14045
+
14046
+ const actions = document.createElement('div');
14047
+ actions.style.cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:auto;';
14048
+ if (deviceId) {
14049
+ const pinButton = createRemoteFleetButton('Pin', 'Pin this device as a canvas node', 'pin-device');
14050
+ pinButton.dataset.deviceId = deviceId;
14051
+ actions.appendChild(pinButton);
14052
+ }
14053
+ if (connectedDevice && deviceId) {
14054
+ const focusButton = createRemoteFleetButton('Focus', 'Show this device in focused live panel', 'live-focus');
14055
+ focusButton.dataset.deviceId = deviceId;
14056
+ actions.appendChild(focusButton);
14057
+ if (liveStreamEnabled) {
14058
+ const liveButton = createRemoteFleetButton(liveStreamActive ? 'Stop' : 'Live', liveStreamActive ? 'Stop live stream' : 'Start focused live stream', liveStreamActive ? 'live-stop' : 'live-start');
14059
+ liveButton.dataset.deviceId = deviceId;
14060
+ liveButton.dataset.streamId = liveStreamId;
14061
+ if (liveStreamActive) {
14062
+ liveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
14063
+ liveButton.style.color = '#b91c1c';
14064
+ }
14065
+ actions.appendChild(liveButton);
14066
+ }
14067
+ if (thumbnailEnabled) {
14068
+ const thumbnailButton = createRemoteFleetButton('Shot', 'Request thumbnail', 'thumbnail-device');
14069
+ thumbnailButton.dataset.deviceId = deviceId;
14070
+ actions.appendChild(thumbnailButton);
14071
+ }
14072
+ const pingButton = createRemoteFleetButton('Ping', 'Ping device', 'ping-device');
14073
+ pingButton.dataset.deviceId = deviceId;
14074
+ actions.appendChild(pingButton);
14075
+ }
14076
+ panel.appendChild(actions);
14077
+ return panel;
14078
+ };
14079
+
14080
+ const monitorWorkspace = document.createElement('div');
14081
+ monitorWorkspace.dataset.remoteFleetMonitorWorkspace = 'true';
14082
+ monitorWorkspace.style.cssText = `
14083
+ flex: 1 1 auto;
14084
+ min-height: 0;
14085
+ overflow: hidden;
14086
+ display: grid;
14087
+ grid-template-columns: minmax(0, 1fr) minmax(230px, 270px);
14088
+ gap: 10px;
14089
+ align-items: stretch;
14090
+ `;
14091
+
13629
14092
  const grid = document.createElement('div');
14093
+ grid.dataset.remoteFleetDeviceGrid = 'true';
13630
14094
  grid.style.cssText = `
13631
- flex: 1 1 auto;
13632
14095
  min-height: 0;
13633
14096
  overflow-y: auto;
13634
14097
  overflow-x: hidden;
13635
14098
  display: grid;
13636
- grid-template-columns: ${densityState === 'dense' ? 'repeat(auto-fill, minmax(220px, 1fr))' : 'repeat(auto-fill, minmax(168px, 1fr))'};
14099
+ grid-template-columns: ${densityState === 'dense' ? 'repeat(auto-fill, minmax(118px, 1fr))' : 'repeat(auto-fill, minmax(148px, 1fr))'};
13637
14100
  align-content: start;
13638
14101
  gap: 8px;
13639
14102
  padding-right: 4px;
13640
14103
  `;
13641
14104
 
13642
- if (devices.length === 0) {
13643
- const empty = document.createElement('div');
13644
- empty.textContent = 'No devices connected yet.';
13645
- empty.style.cssText = `
13646
- grid-column: 1 / -1;
13647
- display: flex;
13648
- align-items: center;
13649
- min-height: 94px;
13650
- padding: 14px;
13651
- border-radius: 8px;
13652
- border: 1px dashed rgba(100, 116, 139, 0.36);
13653
- color: #475569;
13654
- font-size: 13px;
13655
- font-weight: 800;
13656
- background: rgba(255, 255, 255, 0.74);
13657
- `;
13658
- grid.appendChild(empty);
13659
- } else {
14105
+ if (devices.length > 0) {
13660
14106
  const groupStats = new Map();
13661
14107
  if (groupState !== 'none') {
13662
14108
  devices.forEach(device => {
@@ -13679,32 +14125,15 @@
13679
14125
  devices.forEach(device => {
13680
14126
  const connectedDevice = isRemoteFleetDeviceConnected(device);
13681
14127
  const name = getRemoteFleetDeviceName(device);
13682
- const platform = [device?.platform || device?.Platform, device?.arch || device?.Arch]
13683
- .filter(Boolean)
13684
- .join(' / ') || 'unknown';
13685
- const release = String(device?.release || device?.Release || '');
13686
14128
  const deviceId = getRemoteFleetDeviceId(device);
13687
- const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
13688
- const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
13689
- const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
13690
14129
  const liveStreamEnabled = device?.liveStreamEnabled === true || device?.LiveStreamEnabled === true;
13691
14130
  const liveStreamActive = isRemoteFleetLiveActive(device);
13692
- const liveStreamId = String(device?.liveStreamId || device?.LiveStreamId || '');
13693
- const liveFrameDataUrl = String(device?.liveFrameDataUrl || device?.LiveFrameDataUrl || '');
13694
- const liveFrameReceivedAt = String(device?.liveFrameReceivedAt || device?.LiveFrameReceivedAt || '');
13695
14131
  const hasThumbnail = hasRemoteFleetThumbnail(device);
13696
14132
  const hasLiveFrame = hasRemoteFleetLiveFrame(device);
13697
14133
  const taskEnabled = isRemoteFleetDeviceTaskCapable(device);
13698
14134
  const aiAssistEnabled = isRemoteFleetDeviceAiCapable(device);
13699
- const aiModel = String(device?.aiModel || device?.AiModel || '');
13700
14135
  const latestTaskStatus = String(device?.latestTaskStatus || device?.LatestTaskStatus || '');
13701
- const latestTaskTitle = String(device?.latestTaskTitle || device?.LatestTaskTitle || '');
13702
- const latestTaskApproval = String(device?.latestTaskApprovalLevel || device?.LatestTaskApprovalLevel || '');
13703
- const latestTaskUpdatedAt = String(device?.latestTaskUpdatedAt || device?.LatestTaskUpdatedAt || '');
13704
14136
  const latestTaskError = String(device?.latestTaskError || device?.LatestTaskError || '');
13705
- const latestTaskResultModel = String(device?.latestTaskResultModel || device?.LatestTaskResultModel || '');
13706
- const latestTaskResultResponseId = String(device?.latestTaskResultResponseId || device?.LatestTaskResultResponseId || '');
13707
- const latestTaskResult = String(device?.latestTaskResultSummary || device?.LatestTaskResultSummary || '');
13708
14137
  const groupInfo = getRemoteFleetGroupInfo(device, groupState);
13709
14138
  if (groupState !== 'none' && groupInfo.key && groupInfo.key !== lastGroupKey) {
13710
14139
  const stat = groupStats.get(groupInfo.key) || { label: groupInfo.label, total: 0, connected: 0 };
@@ -13739,6 +14168,8 @@
13739
14168
  grid.appendChild(groupHeader);
13740
14169
  lastGroupKey = groupInfo.key;
13741
14170
  }
14171
+ const selectedDeviceId = selectedDevice ? getRemoteFleetDeviceId(selectedDevice) : '';
14172
+ const isSelected = deviceId && deviceId === selectedDeviceId;
13742
14173
  const card = document.createElement('article');
13743
14174
  card.dataset.deviceId = deviceId;
13744
14175
  card.dataset.remoteFleetSearchText = buildRemoteFleetSearchText(device);
@@ -13750,292 +14181,28 @@
13750
14181
  card.dataset.remoteFleetLiveCapable = liveStreamEnabled ? 'true' : 'false';
13751
14182
  card.dataset.remoteFleetLiveActive = liveStreamActive ? 'true' : 'false';
13752
14183
  card.dataset.remoteFleetIssue = (!connectedDevice || latestTaskStatus === 'failed' || !!latestTaskError) ? 'true' : 'false';
14184
+ card.dataset.remoteFleetSelected = isSelected ? 'true' : 'false';
14185
+ card.dataset.remoteFleetAction = 'select-device';
14186
+ card.setAttribute('role', 'button');
14187
+ card.setAttribute('aria-label', `Show details for ${name}`);
14188
+ card.tabIndex = 0;
14189
+ card.title = name;
13753
14190
  card.style.cssText = `
14191
+ position: relative;
13754
14192
  display: flex;
13755
- flex-direction: column;
13756
- gap: ${densityState === 'dense' ? '6px' : '8px'};
14193
+ align-items: stretch;
13757
14194
  min-width: 0;
13758
- min-height: ${densityState === 'dense' ? '92px' : '134px'};
13759
- padding: ${densityState === 'dense' ? '8px' : '10px'};
14195
+ min-height: ${densityState === 'dense' ? '66px' : '84px'};
14196
+ padding: 2px;
13760
14197
  border-radius: 8px;
13761
14198
  background: #ffffff;
13762
- border: 1px solid ${connectedDevice ? 'rgba(16, 185, 129, 0.34)' : 'rgba(148, 163, 184, 0.28)'};
13763
- box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
13764
- `;
13765
-
13766
- if (densityState !== 'dense') {
13767
- const previewDataUrl = hasLiveFrame ? liveFrameDataUrl : thumbnailDataUrl;
13768
- const previewAt = hasLiveFrame ? liveFrameReceivedAt : thumbnailCapturedAt;
13769
- const preview = document.createElement('div');
13770
- preview.style.cssText = `
13771
- position: relative;
13772
- width: 100%;
13773
- aspect-ratio: 16 / 9;
13774
- overflow: hidden;
13775
- border-radius: 7px;
13776
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
13777
- border: 1px solid rgba(15, 23, 42, 0.12);
13778
- `;
13779
- if (hasLiveFrame || hasThumbnail) {
13780
- const image = document.createElement('img');
13781
- image.src = previewDataUrl;
13782
- image.alt = hasLiveFrame ? `${name} live frame` : `${name} thumbnail`;
13783
- image.loading = 'lazy';
13784
- image.decoding = 'async';
13785
- image.style.cssText = `
13786
- width: 100%;
13787
- height: 100%;
13788
- object-fit: cover;
13789
- display: block;
13790
- `;
13791
- preview.appendChild(image);
13792
- } else {
13793
- const placeholder = document.createElement('div');
13794
- placeholder.textContent = thumbnailEnabled ? 'No frame yet' : 'Status only';
13795
- placeholder.style.cssText = `
13796
- position: absolute;
13797
- inset: 0;
13798
- display: flex;
13799
- align-items: center;
13800
- justify-content: center;
13801
- color: rgba(226, 232, 240, 0.78);
13802
- font-size: 11px;
13803
- font-weight: 900;
13804
- letter-spacing: 0;
13805
- `;
13806
- preview.appendChild(placeholder);
13807
- }
13808
- if (previewAt) {
13809
- const badge = document.createElement('span');
13810
- badge.textContent = hasLiveFrame
13811
- ? `LIVE ${formatRemoteFleetAge(previewAt)}`
13812
- : formatRemoteFleetAge(previewAt);
13813
- badge.style.cssText = `
13814
- position: absolute;
13815
- right: 6px;
13816
- bottom: 6px;
13817
- max-width: calc(100% - 12px);
13818
- padding: 3px 6px;
13819
- border-radius: 999px;
13820
- background: ${hasLiveFrame ? 'rgba(220, 38, 38, 0.82)' : 'rgba(15, 23, 42, 0.74)'};
13821
- color: #e2e8f0;
13822
- font-size: 9px;
13823
- font-weight: 900;
13824
- line-height: 1;
13825
- overflow: hidden;
13826
- text-overflow: ellipsis;
13827
- white-space: nowrap;
13828
- `;
13829
- preview.appendChild(badge);
13830
- }
13831
- card.appendChild(preview);
13832
- }
13833
-
13834
- const cardHeader = document.createElement('div');
13835
- cardHeader.style.cssText = 'display:flex;align-items:flex-start;gap:8px;min-width:0;';
13836
- const dot = document.createElement('span');
13837
- dot.style.cssText = `
13838
- flex: 0 0 auto;
13839
- width: 9px;
13840
- height: 9px;
13841
- margin-top: 4px;
13842
- border-radius: 999px;
13843
- background: ${connectedDevice ? '#10b981' : '#94a3b8'};
13844
- box-shadow: 0 0 0 4px ${connectedDevice ? 'rgba(16,185,129,0.14)' : 'rgba(148,163,184,0.14)'};
13845
- `;
13846
-
13847
- const titleBox = document.createElement('div');
13848
- titleBox.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:2px;';
13849
- const title = document.createElement('strong');
13850
- title.textContent = name;
13851
- title.title = name;
13852
- title.style.cssText = `
13853
- color: #0f172a;
13854
- font-size: 13px;
13855
- font-weight: 900;
13856
- line-height: 1.2;
13857
- overflow: hidden;
13858
- text-overflow: ellipsis;
13859
- white-space: nowrap;
13860
- letter-spacing: 0;
13861
- `;
13862
- const subtitle = document.createElement('span');
13863
- subtitle.textContent = release ? `${platform} ${release}` : platform;
13864
- subtitle.title = subtitle.textContent;
13865
- subtitle.style.cssText = `
13866
- color: #64748b;
13867
- font-size: 11px;
13868
- line-height: 1.2;
13869
- overflow: hidden;
13870
- text-overflow: ellipsis;
13871
- white-space: nowrap;
13872
- letter-spacing: 0;
13873
- `;
13874
- titleBox.appendChild(title);
13875
- titleBox.appendChild(subtitle);
13876
- cardHeader.appendChild(dot);
13877
- cardHeader.appendChild(titleBox);
13878
- card.appendChild(cardHeader);
13879
-
13880
- const metrics = document.createElement('div');
13881
- metrics.style.cssText = `
13882
- display: grid;
13883
- grid-template-columns: repeat(2, minmax(0, 1fr));
13884
- gap: 6px;
13885
- `;
13886
- const addMetric = (label, value) => {
13887
- const metric = document.createElement('div');
13888
- metric.style.cssText = `
13889
- min-width: 0;
13890
- padding: 6px 7px;
13891
- border-radius: 7px;
13892
- background: rgba(241, 245, 249, 0.84);
13893
- `;
13894
- const labelEl = document.createElement('div');
13895
- labelEl.textContent = label;
13896
- labelEl.style.cssText = 'color:#64748b;font-size:9px;font-weight:800;letter-spacing:0;text-transform:uppercase;';
13897
- const valueEl = document.createElement('div');
13898
- valueEl.textContent = value;
13899
- valueEl.title = value;
13900
- valueEl.style.cssText = 'color:#0f172a;font-size:12px;font-weight:900;letter-spacing:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
13901
- metric.appendChild(labelEl);
13902
- metric.appendChild(valueEl);
13903
- metrics.appendChild(metric);
13904
- };
13905
- addMetric('Seen', formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt));
13906
- addMetric('Uptime', formatRemoteFleetDuration(device?.uptimeSec ?? device?.UptimeSec));
13907
- addMetric('Mem', formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio));
13908
- addMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
13909
- if (densityState === 'dense') {
13910
- const denseMeta = document.createElement('div');
13911
- denseMeta.textContent = `Seen ${formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt)} - Mem ${formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio)} - Load ${formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2)}`;
13912
- denseMeta.style.cssText = `
13913
- color: #475569;
13914
- font-size: 10px;
13915
- font-weight: 750;
13916
- line-height: 1.2;
13917
- overflow: hidden;
13918
- text-overflow: ellipsis;
13919
- white-space: nowrap;
13920
- letter-spacing: 0;
13921
- `;
13922
- card.appendChild(denseMeta);
13923
- } else {
13924
- card.appendChild(metrics);
13925
- }
13926
-
13927
- if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
13928
- const taskBox = document.createElement('div');
13929
- const taskTone = latestTaskError || latestTaskStatus === 'failed'
13930
- ? 'error'
13931
- : (latestTaskStatus === 'completed' ? 'done' : 'pending');
13932
- taskBox.style.cssText = `
13933
- display: flex;
13934
- flex-direction: column;
13935
- gap: 3px;
13936
- min-width: 0;
13937
- padding: 7px 8px;
13938
- border-radius: 7px;
13939
- background: ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.12)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
13940
- 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)'};
13941
- `;
13942
- const taskLine = document.createElement('div');
13943
- const taskModeLabel = latestTaskApproval === 'ai-assist' ? 'AI' : 'Task';
13944
- taskLine.textContent = `${taskModeLabel} ${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}${latestTaskResultModel ? ` - ${latestTaskResultModel}` : ''}`;
13945
- taskLine.title = latestTaskResultResponseId
13946
- ? `${taskLine.textContent} (${latestTaskResultResponseId})`
13947
- : taskLine.textContent;
13948
- taskLine.style.cssText = `
13949
- color: ${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};
13950
- font-size: 10px;
13951
- font-weight: 900;
13952
- line-height: 1.2;
13953
- overflow: hidden;
13954
- text-overflow: ellipsis;
13955
- white-space: nowrap;
13956
- letter-spacing: 0;
13957
- `;
13958
- const taskSummary = document.createElement('div');
13959
- taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
13960
- taskSummary.title = taskSummary.textContent;
13961
- taskSummary.style.cssText = `
13962
- color: #334155;
13963
- font-size: 10px;
13964
- font-weight: 700;
13965
- line-height: 1.25;
13966
- overflow: hidden;
13967
- display: -webkit-box;
13968
- -webkit-line-clamp: 2;
13969
- -webkit-box-orient: vertical;
13970
- letter-spacing: 0;
13971
- `;
13972
- taskBox.appendChild(taskLine);
13973
- taskBox.appendChild(taskSummary);
13974
- card.appendChild(taskBox);
13975
- }
13976
-
13977
- const actions = document.createElement('div');
13978
- actions.style.cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:auto;';
13979
- const status = document.createElement('span');
13980
- status.textContent = connectedDevice
13981
- ? (liveStreamActive ? 'Live' : (aiAssistEnabled ? `AI ${aiModel || 'ready'}` : 'Connected'))
13982
- : 'Offline';
13983
- status.style.cssText = `
13984
- min-width: 0;
13985
- color: ${liveStreamActive ? '#b91c1c' : (connectedDevice ? '#047857' : '#64748b')};
13986
- font-size: 11px;
13987
- font-weight: 900;
13988
- overflow: hidden;
13989
- text-overflow: ellipsis;
13990
- white-space: nowrap;
14199
+ border: 2px solid ${isSelected ? 'rgba(37, 99, 235, 0.76)' : (connectedDevice ? 'rgba(16, 185, 129, 0.30)' : 'rgba(148, 163, 184, 0.24)')};
14200
+ box-shadow: ${isSelected ? '0 0 0 3px rgba(37, 99, 235, 0.15), 0 10px 22px rgba(15, 23, 42, 0.08)' : '0 8px 18px rgba(15, 23, 42, 0.05)'};
14201
+ cursor: pointer;
14202
+ pointer-events: auto;
14203
+ user-select: none;
13991
14204
  `;
13992
- actions.appendChild(status);
13993
- if (deviceId) {
13994
- const pinButton = createRemoteFleetButton('Pin', 'Pin this device as a canvas node', 'pin-device');
13995
- pinButton.dataset.deviceId = deviceId;
13996
- pinButton.style.height = '24px';
13997
- pinButton.style.fontSize = '10px';
13998
- actions.appendChild(pinButton);
13999
- }
14000
- if (connectedDevice && deviceId) {
14001
- const focusButton = createRemoteFleetButton('Focus', 'Show this device in focused live panel', 'live-focus');
14002
- focusButton.dataset.deviceId = deviceId;
14003
- focusButton.style.height = '24px';
14004
- focusButton.style.fontSize = '10px';
14005
- actions.appendChild(focusButton);
14006
- if (liveStreamEnabled) {
14007
- const liveButton = createRemoteFleetButton(liveStreamActive ? 'Stop' : 'Live', liveStreamActive ? 'Stop live stream' : 'Start focused live stream', liveStreamActive ? 'live-stop' : 'live-start');
14008
- liveButton.dataset.deviceId = deviceId;
14009
- liveButton.dataset.streamId = liveStreamId;
14010
- liveButton.style.height = '24px';
14011
- liveButton.style.fontSize = '10px';
14012
- if (liveStreamActive) {
14013
- liveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
14014
- liveButton.style.color = '#b91c1c';
14015
- }
14016
- actions.appendChild(liveButton);
14017
- }
14018
- if (taskEnabled) {
14019
- const taskButton = createRemoteFleetButton('Task', 'Dispatch task to this device', 'task-device');
14020
- taskButton.dataset.deviceId = deviceId;
14021
- taskButton.style.height = '24px';
14022
- taskButton.style.fontSize = '10px';
14023
- actions.appendChild(taskButton);
14024
- }
14025
- if (thumbnailEnabled) {
14026
- const thumbnailButton = createRemoteFleetButton('Shot', 'Request thumbnail', 'thumbnail-device');
14027
- thumbnailButton.dataset.deviceId = deviceId;
14028
- thumbnailButton.style.height = '24px';
14029
- thumbnailButton.style.fontSize = '10px';
14030
- actions.appendChild(thumbnailButton);
14031
- }
14032
- const pingButton = createRemoteFleetButton('Ping', 'Ping device', 'ping-device');
14033
- pingButton.dataset.deviceId = deviceId;
14034
- pingButton.style.height = '24px';
14035
- pingButton.style.fontSize = '10px';
14036
- actions.appendChild(pingButton);
14037
- }
14038
- card.appendChild(actions);
14205
+ card.appendChild(createDevicePreview(device, 'tile'));
14039
14206
  grid.appendChild(card);
14040
14207
  });
14041
14208
 
@@ -14058,27 +14225,15 @@
14058
14225
  grid.appendChild(noMatch);
14059
14226
  }
14060
14227
 
14061
- bodyView.appendChild(grid);
14062
-
14063
- const footer = document.createElement('div');
14064
- footer.textContent = `Endpoint ${endpoint} - all devices, no paging - group ${groupState} - ${autoMonitorState ? 'auto monitor' : 'manual'} - refreshed ${formatRemoteFleetAge(refreshedAt)}`;
14065
- footer.style.cssText = `
14066
- flex: 0 0 auto;
14067
- color: #64748b;
14068
- font-size: 11px;
14069
- font-weight: 700;
14070
- line-height: 1.2;
14071
- overflow: hidden;
14072
- text-overflow: ellipsis;
14073
- white-space: nowrap;
14074
- `;
14075
- bodyView.appendChild(footer);
14076
-
14077
- copyButton.addEventListener('click', event => {
14078
- event.preventDefault();
14079
- event.stopPropagation();
14080
- navigator.clipboard?.writeText?.(command).catch(() => { });
14081
- });
14228
+ if (devices.length === 0) {
14229
+ monitorWorkspace.style.display = 'flex';
14230
+ monitorWorkspace.style.flexDirection = 'column';
14231
+ monitorWorkspace.appendChild(createRemoteFleetEmptyScreens());
14232
+ } else {
14233
+ monitorWorkspace.appendChild(grid);
14234
+ monitorWorkspace.appendChild(createSelectedDevicePanel(selectedDevice));
14235
+ }
14236
+ bodyView.appendChild(monitorWorkspace);
14082
14237
 
14083
14238
  const setTaskFeedback = (message, tone = 'info') => {
14084
14239
  taskFeedback.textContent = message || '';
@@ -14284,9 +14439,12 @@
14284
14439
  sendConnectedButton.title = allTargetCount > 0
14285
14440
  ? `Dispatch to ${allTargetCount} connected target(s)`
14286
14441
  : 'No connected targetable devices';
14287
- grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14288
- const card = button.closest('article[data-device-id]');
14289
- button.disabled = wantsAi && card?.dataset.remoteFleetAiCapable !== 'true';
14442
+ bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14443
+ const deviceId = String(button.dataset.deviceId || '').trim();
14444
+ const card = getDeviceCards().find(item => String(item.dataset.deviceId || '').trim() === deviceId);
14445
+ const aiCapable = button.dataset.remoteFleetAiCapable === 'true'
14446
+ || card?.dataset.remoteFleetAiCapable === 'true';
14447
+ button.disabled = wantsAi && !aiCapable;
14290
14448
  button.title = button.disabled ? 'AI assist is not enabled on this device' : 'Dispatch task to this device';
14291
14449
  });
14292
14450
 
@@ -14303,19 +14461,34 @@
14303
14461
  control.addEventListener(eventName, event => event.stopPropagation());
14304
14462
  });
14305
14463
  });
14306
- [autoMonitorLabel, autoMonitorToggle].forEach(control => {
14464
+ [hostButton, stopHostButton].forEach(control => {
14465
+ if (!control) return;
14307
14466
  ['mousedown', 'mouseup', 'click', 'dblclick', 'keydown'].forEach(eventName => {
14308
14467
  control.addEventListener(eventName, event => event.stopPropagation());
14309
14468
  });
14310
14469
  });
14470
+ getDeviceCards().forEach(card => {
14471
+ ['mousedown', 'mouseup', 'dblclick'].forEach(eventName => {
14472
+ card.addEventListener(eventName, event => event.stopPropagation());
14473
+ });
14474
+ const selectCard = event => {
14475
+ event.preventDefault();
14476
+ event.stopPropagation();
14477
+ const deviceId = String(card.dataset.deviceId || '').trim();
14478
+ if (!deviceId) return;
14479
+ bodyView.dataset.remoteFleetSelectedDeviceId = deviceId;
14480
+ renderRemoteFleetMonitor(bodyView, nodeModel);
14481
+ };
14482
+ card.addEventListener('click', selectCard);
14483
+ card.addEventListener('keydown', event => {
14484
+ if (event.key !== 'Enter' && event.key !== ' ') return;
14485
+ selectCard(event);
14486
+ });
14487
+ });
14311
14488
 
14312
14489
  searchInput.addEventListener('input', applyRemoteFleetFilters);
14313
14490
  filterSelect.addEventListener('change', applyRemoteFleetFilters);
14314
14491
  aiToggle.addEventListener('change', applyRemoteFleetFilters);
14315
- autoMonitorToggle.addEventListener('change', () => {
14316
- bodyView.dataset.remoteFleetAutoMonitor = autoMonitorToggle.checked ? 'true' : 'false';
14317
- renderRemoteFleetMonitor(bodyView, nodeModel);
14318
- });
14319
14492
  sortSelect.addEventListener('change', () => {
14320
14493
  bodyView.dataset.remoteFleetSort = String(sortSelect.value || 'status');
14321
14494
  renderRemoteFleetMonitor(bodyView, nodeModel);
@@ -14387,18 +14560,61 @@
14387
14560
  }
14388
14561
  });
14389
14562
 
14390
- refreshButton.addEventListener('click', async event => {
14391
- event.preventDefault();
14392
- event.stopPropagation();
14393
- refreshButton.disabled = true;
14563
+ const setRemoteFleetHostTarget = async (enabled, options = {}) => {
14564
+ const quiet = options?.quiet === true;
14565
+ const activeButton = enabled ? hostButton : stopHostButton;
14566
+ if (!quiet && activeButton) {
14567
+ activeButton.disabled = true;
14568
+ }
14569
+
14570
+ if (!quiet) {
14571
+ setTaskFeedback(enabled ? 'Setting host target...' : 'Stopping host target...');
14572
+ }
14394
14573
  try {
14395
- await refreshRemoteFleetNode();
14574
+ const result = await invokeDotNetAsync('SetRemoteFleetHostFromJs', nodeId, enabled === true);
14575
+ window.RuntimeTrace?.emit?.('remote.hostTarget.result', {
14576
+ nodeId,
14577
+ enabled: enabled === true,
14578
+ success: isRemoteFleetResultSuccess(result),
14579
+ active: isRemoteFleetResultActive(result),
14580
+ error: result?.error || result?.Error || ''
14581
+ });
14582
+ rememberRemoteFleetLocalHostTarget(nodeId, enabled === true, result);
14583
+ await syncRemoteFleetNodeStateFromResult(result);
14584
+ if (quiet) {
14585
+ return result;
14586
+ }
14587
+ if (isRemoteFleetResultSuccess(result) && enabled !== true) {
14588
+ stopRemoteFleetHostLeaseTimer(nodeId);
14589
+ }
14590
+ if (isRemoteFleetResultSuccess(result)) {
14591
+ setTaskFeedback(enabled ? 'Host target set.' : 'Host target stopped.', 'success');
14592
+ } else {
14593
+ setTaskFeedback(result?.error || result?.Error || 'Host target update failed.', 'error');
14594
+ }
14595
+ return result;
14396
14596
  } finally {
14397
- refreshButton.disabled = false;
14597
+ if (!quiet && activeButton) {
14598
+ activeButton.disabled = false;
14599
+ }
14398
14600
  }
14601
+ };
14602
+
14603
+ hostButton.addEventListener('click', async event => {
14604
+ event.preventDefault();
14605
+ event.stopPropagation();
14606
+ await setRemoteFleetHostTarget(true);
14399
14607
  });
14400
14608
 
14401
- grid.querySelectorAll('[data-remote-fleet-action="pin-device"]').forEach(button => {
14609
+ if (stopHostButton) {
14610
+ stopHostButton.addEventListener('click', async event => {
14611
+ event.preventDefault();
14612
+ event.stopPropagation();
14613
+ await setRemoteFleetHostTarget(false);
14614
+ });
14615
+ }
14616
+
14617
+ bodyView.querySelectorAll('[data-remote-fleet-action="pin-device"]').forEach(button => {
14402
14618
  button.addEventListener('click', async event => {
14403
14619
  event.preventDefault();
14404
14620
  event.stopPropagation();
@@ -14476,7 +14692,7 @@
14476
14692
  });
14477
14693
  });
14478
14694
 
14479
- grid.querySelectorAll('[data-remote-fleet-action="ping-device"]').forEach(button => {
14695
+ bodyView.querySelectorAll('[data-remote-fleet-action="ping-device"]').forEach(button => {
14480
14696
  button.addEventListener('click', async event => {
14481
14697
  event.preventDefault();
14482
14698
  event.stopPropagation();
@@ -14487,7 +14703,7 @@
14487
14703
  });
14488
14704
  });
14489
14705
 
14490
- grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14706
+ bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14491
14707
  button.addEventListener('click', async event => {
14492
14708
  event.preventDefault();
14493
14709
  event.stopPropagation();
@@ -14516,7 +14732,7 @@
14516
14732
  });
14517
14733
  });
14518
14734
 
14519
- grid.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').forEach(button => {
14735
+ bodyView.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').forEach(button => {
14520
14736
  button.addEventListener('click', async event => {
14521
14737
  event.preventDefault();
14522
14738
  event.stopPropagation();
@@ -14527,6 +14743,10 @@
14527
14743
  });
14528
14744
  });
14529
14745
 
14746
+ if (isHostingTarget) {
14747
+ startRemoteFleetHostLeaseTimer(nodeId, () => setRemoteFleetHostTarget(true, { quiet: true }));
14748
+ }
14749
+
14530
14750
  if (hasActiveLiveStream) {
14531
14751
  bodyView._remoteFleetLiveRefreshTimer = setInterval(async () => {
14532
14752
  if (!document.body.contains(bodyView)) {