@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -20,11 +20,11 @@
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 && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs",
24
- "test:remote": "node scripts/remote-hub-smoke.mjs",
25
- "test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
26
- "test:remote:render": "node scripts/remote-fleet-render-smoke.mjs",
27
- "test:remote:http": "node scripts/remote-http-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 && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs",
24
+ "test:remote": "node scripts/remote-hub-smoke.mjs",
25
+ "test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
26
+ "test:remote:render": "node scripts/remote-fleet-render-smoke.mjs",
27
+ "test:remote:http": "node scripts/remote-http-smoke.mjs",
28
28
  "pack:dry": "npm pack --dry-run",
29
29
  "setup:grammars": "node scripts/setup-tree-sitter-grammars.mjs",
30
30
  "postinstall": "npm run setup:grammars"
package/remote-hub.js CHANGED
@@ -15,6 +15,7 @@ const RECENT_TASK_LIMIT = 12;
15
15
  const RECENT_TASK_BATCH_LIMIT = 16;
16
16
  const REMOTE_PROTOCOL_VERSION = 1;
17
17
  const MAX_SYNTHETIC_DEVICES = 1000;
18
+ const DEFAULT_HOST_TARGET_LEASE_MS = 30000;
18
19
  const SYNTHETIC_FRAME_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC';
19
20
 
20
21
  function isEnabledValue(value, fallback = true) {
@@ -219,9 +220,129 @@ export function createRemoteHub(options = {}) {
219
220
  let started = false;
220
221
  let boundPort = requestedPort;
221
222
  let lastError = '';
223
+ let hostTarget = null;
224
+
225
+ function getAgentEndpoint() {
226
+ return `${host}:${boundPort || requestedPort}`;
227
+ }
228
+
229
+ function getActiveHostTarget() {
230
+ if (!hostTarget) {
231
+ return null;
232
+ }
233
+
234
+ const expiresMs = Date.parse(hostTarget.expiresAt || '');
235
+ if (Number.isFinite(expiresMs) && expiresMs <= Date.now()) {
236
+ const expiredTarget = hostTarget;
237
+ hostTarget = null;
238
+ emitRemoteEvent('RemoteHostTargetExpired', null, {
239
+ hostTarget: {
240
+ ...expiredTarget,
241
+ active: false
242
+ }
243
+ });
244
+ return null;
245
+ }
246
+
247
+ return hostTarget;
248
+ }
249
+
250
+ function serializeHostTarget(target = getActiveHostTarget()) {
251
+ if (!target) {
252
+ return {
253
+ active: false,
254
+ nodeId: '',
255
+ leaseId: '',
256
+ endpoint: '',
257
+ activatedAt: '',
258
+ updatedAt: '',
259
+ expiresAt: ''
260
+ };
261
+ }
262
+
263
+ return {
264
+ active: true,
265
+ nodeId: target.nodeId,
266
+ leaseId: target.leaseId,
267
+ endpoint: target.endpoint,
268
+ activatedAt: target.activatedAt,
269
+ updatedAt: target.updatedAt,
270
+ expiresAt: target.expiresAt
271
+ };
272
+ }
273
+
274
+ function setHostTarget(options = {}) {
275
+ const enabled = options.enabled !== false;
276
+ const nodeId = safeString(options.nodeId, 128);
277
+ if (!enabled) {
278
+ const previous = getActiveHostTarget();
279
+ if (!previous) {
280
+ return {
281
+ ok: true,
282
+ active: false,
283
+ hostTarget: serializeHostTarget(null)
284
+ };
285
+ }
286
+
287
+ if (nodeId && previous.nodeId && nodeId !== previous.nodeId) {
288
+ return {
289
+ ok: false,
290
+ error: 'host-target-node-mismatch',
291
+ active: true,
292
+ hostTarget: serializeHostTarget(previous)
293
+ };
294
+ }
295
+
296
+ hostTarget = null;
297
+ emitRemoteEvent('RemoteHostTargetCleared', null, {
298
+ hostTarget: {
299
+ ...serializeHostTarget(previous),
300
+ active: false
301
+ }
302
+ });
303
+ return {
304
+ ok: true,
305
+ active: false,
306
+ hostTarget: serializeHostTarget(null)
307
+ };
308
+ }
309
+
310
+ if (!nodeId) {
311
+ return {
312
+ ok: false,
313
+ error: 'missing-node-id',
314
+ active: false,
315
+ hostTarget: serializeHostTarget()
316
+ };
317
+ }
318
+
319
+ const now = new Date();
320
+ const leaseMs = clampNumber(options.leaseMs, 5000, 10 * 60 * 1000, DEFAULT_HOST_TARGET_LEASE_MS);
321
+ const previous = getActiveHostTarget();
322
+ const activeSameNode = previous?.nodeId === nodeId;
323
+ hostTarget = {
324
+ nodeId,
325
+ leaseId: activeSameNode && previous?.leaseId ? previous.leaseId : crypto.randomUUID(),
326
+ endpoint: getAgentEndpoint(),
327
+ activatedAt: activeSameNode && previous?.activatedAt ? previous.activatedAt : now.toISOString(),
328
+ updatedAt: now.toISOString(),
329
+ expiresAt: new Date(now.getTime() + leaseMs).toISOString()
330
+ };
331
+
332
+ emitRemoteEvent('RemoteHostTargetSet', null, {
333
+ hostTarget: serializeHostTarget(hostTarget),
334
+ replacedNodeId: previous && previous.nodeId !== nodeId ? previous.nodeId : ''
335
+ });
336
+ return {
337
+ ok: true,
338
+ active: true,
339
+ hostTarget: serializeHostTarget(hostTarget)
340
+ };
341
+ }
222
342
 
223
343
  function getStatus({ includeSecrets = false } = {}) {
224
344
  const connectedDevices = [...devices.values()].filter(device => device.connected).length;
345
+ const activeHostTarget = serializeHostTarget();
225
346
  return {
226
347
  enabled,
227
348
  started,
@@ -234,13 +355,20 @@ export function createRemoteHub(options = {}) {
234
355
  managerPackage,
235
356
  managerVersion,
236
357
  agentPackage: '@mindexec/remote',
237
- agentEndpoint: `${host}:${boundPort || requestedPort}`,
358
+ agentEndpoint: getAgentEndpoint(),
238
359
  pairToken: includeSecrets ? pairToken : undefined,
239
360
  pairTokenPreview: maskToken(pairToken),
240
361
  deviceCount: devices.size,
241
362
  connectedDeviceCount: connectedDevices,
242
363
  canvasDeviceListMode: 'all-devices',
243
364
  canvasPagination: 'none',
365
+ hostTargetActive: activeHostTarget.active,
366
+ hostTargetNodeId: activeHostTarget.nodeId,
367
+ hostTargetLeaseId: activeHostTarget.leaseId,
368
+ hostTargetEndpoint: activeHostTarget.endpoint,
369
+ hostTargetActivatedAt: activeHostTarget.activatedAt,
370
+ hostTargetUpdatedAt: activeHostTarget.updatedAt,
371
+ hostTargetExpiresAt: activeHostTarget.expiresAt,
244
372
  externalExposure: host === '0.0.0.0' || host === '::',
245
373
  lastError
246
374
  };
@@ -1690,6 +1818,7 @@ export function createRemoteHub(options = {}) {
1690
1818
  sendCommand,
1691
1819
  requestAgentTask,
1692
1820
  requestAgentTaskBatch,
1821
+ setHostTarget,
1693
1822
  requestThumbnail,
1694
1823
  startLiveStream,
1695
1824
  stopLiveStream,
@@ -416,6 +416,9 @@ function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTask
416
416
  const connected = devices.filter(device => device.Connected).length;
417
417
  const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5197';
418
418
  const pairToken = hubStatus.pairToken || 'render-smoke-token';
419
+ const hostTargetState = hubStatus.hostTargetActive
420
+ ? (hubStatus.hostTargetNodeId === 'remote-fleet-render-smoke' ? 'hosting' : 'other-monitor')
421
+ : 'inactive';
419
422
  return {
420
423
  id: 'remote-fleet-render-smoke',
421
424
  contentType: 'memo',
@@ -434,6 +437,13 @@ function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTask
434
437
  RemoteFleetDeviceCount: String(devices.length),
435
438
  RemoteFleetConnectedDeviceCount: String(connected),
436
439
  RemoteFleetCanvasPagination: 'none',
440
+ RemoteFleetHostTargetState: hostTargetState,
441
+ RemoteFleetHostTargetNodeId: hubStatus.hostTargetNodeId || '',
442
+ RemoteFleetHostTargetLeaseId: hubStatus.hostTargetLeaseId || '',
443
+ RemoteFleetHostTargetEndpoint: hubStatus.hostTargetEndpoint || endpoint,
444
+ RemoteFleetHostTargetActivatedAtUtc: hubStatus.hostTargetActivatedAt || '',
445
+ RemoteFleetHostTargetUpdatedAtUtc: hubStatus.hostTargetUpdatedAt || '',
446
+ RemoteFleetHostTargetExpiresAtUtc: hubStatus.hostTargetExpiresAt || '',
437
447
  RemoteFleetDevicesJson: JSON.stringify(devices),
438
448
  RemoteFleetLatestTaskBatchJson: latestTaskBatch ? JSON.stringify(latestTaskBatch) : '',
439
449
  RemoteFleetRecentTaskBatchesJson: JSON.stringify(recentTaskBatches),
@@ -547,7 +557,6 @@ try {
547
557
  const rawDevices = hub.listDevices();
548
558
  const devices = rawDevices.map(projectDevice);
549
559
  const connectedCount = devices.filter(device => device.Connected).length;
550
- const offlineCount = devices.length - connectedCount;
551
560
  const aiCount = devices.filter(device => device.Connected && device.AiAssistEnabled).length;
552
561
  const focusedDevice = devices.find(device => device.Connected && device.LiveStreamActive)
553
562
  || devices.find(device => device.Connected);
@@ -645,31 +654,112 @@ try {
645
654
  };
646
655
  }
647
656
 
657
+ if (methodName === 'SetRemoteFleetHostFromJs') {
658
+ return {
659
+ success: true,
660
+ active: args[1] === true
661
+ };
662
+ }
663
+
648
664
  return { success: true };
649
665
  }
650
666
  };
651
667
  manager.setModuleForTest({ dotNetHelper });
652
668
 
669
+ const nodeShell = document.createElement('div');
670
+ nodeShell.setAttribute('class', 'map-node-remote-fleet map-node-memo');
671
+ const header = document.createElement('div');
672
+ header.setAttribute('class', 'map-node-memo__header');
673
+ const iconWrap = document.createElement('div');
674
+ iconWrap.setAttribute('class', 'map-node-memo__icon-wrap');
675
+ const iconButton = document.createElement('button');
676
+ iconButton.setAttribute('class', 'map-node-memo__icon-button');
677
+ iconWrap.appendChild(iconButton);
678
+ const titleWrap = document.createElement('div');
679
+ titleWrap.setAttribute('class', 'map-node-memo__title-wrap');
680
+ titleWrap.textContent = 'Remote Fleet Monitor';
681
+ header.appendChild(iconWrap);
682
+ header.appendChild(titleWrap);
653
683
  const bodyView = document.createElement('div');
654
684
  bodyView.dataset.remoteFleetAutoMonitor = 'false';
655
685
  bodyView.dataset.remoteFleetFocusDeviceId = focusedDevice.DeviceId;
656
- document.body.appendChild(bodyView);
686
+ nodeShell.appendChild(header);
687
+ nodeShell.appendChild(bodyView);
688
+ document.body.appendChild(nodeShell);
657
689
 
658
690
  manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
659
691
 
660
692
  let cards = bodyView.querySelectorAll('article[data-device-id]');
661
693
  assert.equal(cards.length, SYNTHETIC_COUNT);
662
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${SYNTHETIC_COUNT}/${SYNTHETIC_COUNT}`);
663
- assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="pin-device"]').length, SYNTHETIC_COUNT);
664
- assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').length, connectedCount);
665
- assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').length, devices.filter(device => device.Connected && device.ThumbnailEnabled).length);
694
+ assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]'), null);
695
+ assert.equal(bodyView.querySelector('[data-remote-fleet-search="true"]'), null);
696
+ assert.equal(bodyView.querySelectorAll('select').length, 0);
697
+ assert.ok(bodyView.querySelector('[data-remote-fleet-device-grid="true"]'));
698
+ const initialDetailPanel = bodyView.querySelector('[data-remote-fleet-detail-panel="true"]');
699
+ assert.equal(initialDetailPanel?.dataset.deviceId, focusedDevice.DeviceId);
700
+ assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="pin-device"]').length, 1);
701
+ assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').length, 0);
702
+ assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').length, focusedDevice.Connected && focusedDevice.ThumbnailEnabled ? 1 : 0);
703
+ assert.ok(initialDetailPanel?.textContent.includes('Seen'));
704
+ assert.ok(initialDetailPanel?.textContent.includes('Uptime'));
705
+ assert.ok(initialDetailPanel?.textContent.includes('Mem'));
706
+ assert.ok(initialDetailPanel?.textContent.includes('Load'));
707
+ assert.ok(cards[0]?.querySelector('[data-remote-fleet-device-preview="tile"]'));
708
+ assert.equal(/\b(Seen|Uptime|Mem|Load)\b/.test(cards[0]?.textContent || ''), false);
709
+ const alternateDevice = devices.find(device => device.DeviceId !== focusedDevice.DeviceId);
710
+ assert.ok(alternateDevice);
711
+ const alternateCard = Array.from(cards).find(card => card.dataset.deviceId === alternateDevice.DeviceId);
712
+ assert.ok(alternateCard);
713
+ alternateCard.dispatchEvent({ type: 'click' });
714
+ assert.equal(bodyView.dataset.remoteFleetSelectedDeviceId, alternateDevice.DeviceId);
715
+ assert.equal(bodyView.querySelector('[data-remote-fleet-detail-panel="true"]')?.dataset.deviceId, alternateDevice.DeviceId);
666
716
  const livePanel = bodyView.querySelector('[data-remote-fleet-live-panel="true"]');
667
717
  assert.equal(livePanel?.dataset.deviceId, focusedDevice.DeviceId);
668
- assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-visible"]')?.disabled, false);
669
- assert.ok(bodyView.textContent.includes('all devices, no paging'));
670
- assert.match(bodyView.querySelector('[data-remote-fleet-manager-version="true"]')?.textContent || '', /^@mindexec\/cli render-smoke-manager - 127\.0\.0\.1:\d+$/);
718
+ assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-visible"]'), null);
719
+ assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-connected"]'), null);
720
+ assert.equal(bodyView.querySelector('[data-remote-fleet-task-input="true"]'), null);
721
+ assert.equal(bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]'), null);
722
+ assert.equal(bodyView.textContent.includes('all devices, no paging'), false);
723
+ assert.equal(bodyView.textContent.includes('Host: Inactive'), false);
724
+ assert.equal(bodyView.textContent.includes('No screen'), false);
725
+ assert.equal(bodyView.textContent.includes('@mindexec/cli'), false);
726
+ assert.equal(nodeShell.querySelector('[data-remote-fleet-host-indicator="true"]')?.dataset.remoteFleetHostActive, 'false');
727
+ const initialHostButton = nodeShell.querySelector('[data-remote-fleet-action="set-host"]');
728
+ assert.equal(initialHostButton?.textContent, 'Set Host');
729
+ initialHostButton.dispatchEvent({ type: 'click' });
730
+ await wait();
731
+ const setHostCall = dotNetCalls.find(call => call.methodName === 'SetRemoteFleetHostFromJs');
732
+ assert.ok(setHostCall);
733
+ assert.equal(setHostCall.args[0], 'remote-fleet-render-smoke');
734
+ assert.equal(setHostCall.args[1], true);
735
+
736
+ hub.setHostTarget({
737
+ nodeId: 'remote-fleet-render-smoke',
738
+ enabled: true,
739
+ leaseMs: 30000
740
+ });
741
+ manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
742
+ assert.equal(bodyView.textContent.includes('Host: Active'), false);
743
+ assert.equal(nodeShell.querySelector('[data-remote-fleet-host-indicator="true"]')?.dataset.remoteFleetHostActive, 'true');
744
+ assert.equal(nodeShell.querySelector('[data-remote-fleet-action="set-host"]')?.textContent, 'Set Host');
745
+ const stopHostButton = nodeShell.querySelector('[data-remote-fleet-action="stop-host"]');
746
+ assert.ok(stopHostButton);
747
+ const stopHostStart = dotNetCalls.length;
748
+ stopHostButton.dispatchEvent({ type: 'click' });
749
+ await wait();
750
+ const stopHostCall = dotNetCalls
751
+ .slice(stopHostStart)
752
+ .find(call => call.methodName === 'SetRemoteFleetHostFromJs');
753
+ assert.ok(stopHostCall);
754
+ assert.equal(stopHostCall.args[0], 'remote-fleet-render-smoke');
755
+ assert.equal(stopHostCall.args[1], false);
756
+
671
757
  assert.ok(livePanel?.textContent.includes('RemoteFast 20 fps'), livePanel?.textContent || bodyView.textContent);
672
- assert.ok(bodyView.querySelector('code')?.textContent.includes('npx @mindexec/remote connect'));
758
+ assert.equal(bodyView.querySelector('code'), null);
759
+ assert.equal(bodyView.textContent.includes('npx @mindexec/remote connect'), false);
760
+ assert.equal(bodyView.querySelector('[data-remote-fleet-action="copy-command"]'), null);
761
+ assert.equal(bodyView.querySelector('[data-remote-fleet-action="refresh"]'), null);
762
+ assert.equal(bodyView.querySelector('[data-remote-fleet-auto-toggle="true"]'), null);
673
763
  assert.ok(bodyView.textContent.includes('synthetic-render-ai'));
674
764
  assert.ok(bodyView.textContent.includes('Task timed out waiting for the agent response.'));
675
765
  const batchSummary = bodyView.querySelector('[data-remote-fleet-task-batch="true"]');
@@ -691,91 +781,30 @@ try {
691
781
  assert.equal(bodyView.dataset.remoteFleetTaskFollowKey, 'render-smoke-batch');
692
782
  assert.ok(Number(bodyView.dataset.remoteFleetTaskFollowTicks || '0') >= 1);
693
783
 
694
- const searchInput = bodyView.querySelector('[data-remote-fleet-search="true"]');
695
- searchInput.value = 'synthetic pc 0001';
696
- searchInput.dispatchEvent({ type: 'input' });
697
- cards = bodyView.querySelectorAll('article[data-device-id]');
698
- assert.equal(cards.filter(card => card.style.display !== 'none').length, 1);
699
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `1/${SYNTHETIC_COUNT}`);
700
-
701
- const selects = bodyView.querySelectorAll('select');
702
- assert.equal(selects.length, 4);
703
- const [filterSelect, , groupSelect] = selects;
704
- searchInput.value = '';
705
- searchInput.dispatchEvent({ type: 'input' });
706
- filterSelect.value = 'offline';
707
- filterSelect.dispatchEvent({ type: 'change' });
708
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${offlineCount}/${SYNTHETIC_COUNT}`);
709
-
710
- filterSelect.value = 'ai';
711
- filterSelect.dispatchEvent({ type: 'change' });
712
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${aiCount}/${SYNTHETIC_COUNT}`);
713
-
714
- filterSelect.value = 'all';
715
- filterSelect.dispatchEvent({ type: 'change' });
716
- groupSelect.value = 'status';
717
- groupSelect.dispatchEvent({ type: 'change' });
718
- assert.equal(bodyView.dataset.remoteFleetGroup, 'status');
719
- assert.ok(bodyView.querySelectorAll('[data-remote-fleet-group-header="true"]').length >= 2);
720
- assert.equal(bodyView.querySelectorAll('article[data-device-id]').length, SYNTHETIC_COUNT);
721
-
722
- const taskInput = bodyView.querySelector('[data-remote-fleet-task-input="true"]');
723
- const sendVisibleButton = bodyView.querySelector('[data-remote-fleet-action="task-visible"]');
724
- assert.ok(taskInput);
725
- assert.ok(sendVisibleButton);
726
- taskInput.value = 'Dispatch smoke partial failure';
727
- sendVisibleButton.dispatchEvent({ type: 'click' });
728
- await wait();
729
- const batchCall = dotNetCalls.find(call => call.methodName === 'DispatchRemoteFleetTaskBatchFromJs');
730
- assert.ok(batchCall);
731
- const batchTargetIds = Array.isArray(batchCall.args[1]) ? batchCall.args[1] : [];
732
- assert.equal(batchTargetIds.length, connectedCount);
733
784
  const feedback = bodyView.querySelector('[data-remote-fleet-task-feedback="true"]');
734
785
  assert.ok(feedback);
735
- assert.equal(feedback.style.display, 'block');
736
- assert.match(feedback.textContent, new RegExp(`Queued ${connectedCount - 1}/${connectedCount} visible remote task\\(s\\); 1 failed`));
737
- assert.match(feedback.textContent, /device-ai-assist-unavailable/);
738
-
739
- const activeSelects = bodyView.querySelectorAll('select');
740
- assert.equal(activeSelects.length, 4);
741
- const [aiFilterSelect] = activeSelects;
742
- const aiToggle = bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]');
743
- assert.ok(aiToggle);
744
- aiFilterSelect.value = 'ai';
745
- aiFilterSelect.dispatchEvent({ type: 'change' });
746
- aiToggle.checked = true;
747
- aiToggle.dispatchEvent({ type: 'change' });
748
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${aiCount}/${SYNTHETIC_COUNT}`);
749
- assert.equal(sendVisibleButton.disabled, false);
750
- taskInput.value = 'Dispatch visible AI smoke';
751
- const aiDispatchCallStart = dotNetCalls.length;
752
- sendVisibleButton.dispatchEvent({ type: 'click' });
753
- await wait();
754
- const aiBatchCall = dotNetCalls
755
- .slice(aiDispatchCallStart)
756
- .find(call => call.methodName === 'DispatchRemoteFleetTaskBatchFromJs');
757
- assert.ok(aiBatchCall);
758
- const aiTargetIds = Array.isArray(aiBatchCall.args[1]) ? aiBatchCall.args[1] : [];
759
- const expectedAiTargetIds = devices
760
- .filter(device => device.Connected && device.AiAssistEnabled)
761
- .map(device => device.DeviceId)
762
- .sort();
763
- assert.deepEqual([...aiTargetIds].sort(), expectedAiTargetIds);
764
- assert.equal(aiBatchCall.args[3], true);
765
-
766
- const sendConnectedButton = bodyView.querySelector('[data-remote-fleet-action="task-connected"]');
767
- assert.ok(sendConnectedButton);
768
- taskInput.value = 'Dispatch all AI smoke';
769
- const allAiDispatchCallStart = dotNetCalls.length;
770
- sendConnectedButton.dispatchEvent({ type: 'click' });
771
- await wait();
772
- const allAiCall = dotNetCalls
773
- .slice(allAiDispatchCallStart)
774
- .find(call => call.methodName === 'DispatchRemoteFleetTaskFromJs');
775
- assert.ok(allAiCall);
776
- assert.equal(allAiCall.args[1], '');
777
- assert.equal(allAiCall.args[3], true);
778
- assert.match(feedback.textContent, new RegExp(`Queued ${aiCount}/${aiCount} AI task\\(s\\)`));
786
+
787
+ assert.equal(bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]'), null);
788
+ assert.equal(dotNetCalls.some(call =>
789
+ call.methodName === 'DispatchRemoteFleetTaskBatchFromJs'
790
+ || call.methodName === 'DispatchRemoteFleetTaskFromJs'), false);
791
+
792
+ const emptyShell = document.createElement('div');
793
+ emptyShell.setAttribute('class', 'map-node-remote-fleet map-node-memo');
794
+ const emptyHeader = document.createElement('div');
795
+ emptyHeader.setAttribute('class', 'map-node-memo__header');
796
+ emptyHeader.appendChild(document.createElement('div'));
797
+ emptyHeader.appendChild(document.createElement('div'));
798
+ const emptyBody = document.createElement('div');
799
+ emptyShell.appendChild(emptyHeader);
800
+ emptyShell.appendChild(emptyBody);
801
+ document.body.appendChild(emptyShell);
802
+ manager.renderRemoteFleetMonitorForTest(emptyBody, buildMonitorNode([], hub.getStatus({ includeSecrets: true })));
803
+ assert.equal(emptyBody.querySelectorAll('[data-remote-fleet-empty-screen="true"]').length, 6);
804
+ assert.equal(emptyBody.textContent.includes('No devices connected yet.'), false);
805
+ assert.equal(emptyBody.textContent.includes('Select a screen'), false);
806
+ assert.equal(emptyBody.textContent.includes('No screen'), false);
807
+ assert.equal(emptyBody.textContent.includes('Endpoint 127.0.0.1'), false);
779
808
 
780
809
  const deviceBody = document.createElement('div');
781
810
  document.body.appendChild(deviceBody);