@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
@@ -51,11 +51,11 @@ async function fetchJson(url, options = {}) {
51
51
  };
52
52
  }
53
53
 
54
- async function waitForBridge(baseUrl, getFailureDetails) {
54
+ async function waitForBridge(baseUrl, getFailureDetails, bridgeToken = BRIDGE_TOKEN) {
55
55
  const startedAt = Date.now();
56
56
  while (Date.now() - startedAt < 30000) {
57
57
  try {
58
- const result = await fetchJson(`${baseUrl}/api/remote/status`, { token: BRIDGE_TOKEN });
58
+ const result = await fetchJson(`${baseUrl}/api/remote/status`, { token: bridgeToken });
59
59
  if (result.ok && result.payload?.started === true) {
60
60
  return result.payload;
61
61
  }
@@ -70,49 +70,96 @@ async function waitForBridge(baseUrl, getFailureDetails) {
70
70
 
71
71
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
72
72
  const bridgeRoot = path.resolve(__dirname, '..');
73
- const bridgePort = await findFreePort();
74
- const remoteHubPort = await findFreePort();
75
- const baseUrl = `http://127.0.0.1:${bridgePort}`;
76
-
77
- const child = spawn(process.execPath, ['server.js'], {
78
- cwd: bridgeRoot,
79
- stdio: ['ignore', 'pipe', 'pipe'],
80
- windowsHide: true,
81
- env: {
73
+
74
+ function spawnBridge({
75
+ bridgePort,
76
+ remoteHubPort,
77
+ bridgeToken = BRIDGE_TOKEN,
78
+ pairToken = PAIR_TOKEN,
79
+ syntheticFleetEnabled = false
80
+ }) {
81
+ const env = {
82
82
  ...process.env,
83
83
  BRIDGE_PORT: String(bridgePort),
84
- BRIDGE_TOKEN,
84
+ BRIDGE_TOKEN: bridgeToken,
85
85
  BRIDGE_REQUIRE_TOKEN: '1',
86
86
  MINDEXEC_REMOTE_HUB: '1',
87
87
  REMOTE_HUB_HOST: '127.0.0.1',
88
88
  REMOTE_HUB_PORT: String(remoteHubPort),
89
- REMOTE_HUB_PAIR_TOKEN: PAIR_TOKEN,
90
- MINDEXEC_REMOTE_SYNTHETIC_FLEET: '1',
89
+ REMOTE_HUB_PAIR_TOKEN: pairToken,
91
90
  NO_COLOR: '1'
91
+ };
92
+
93
+ delete env.MINDEXEC_REMOTE_SYNTHETIC_FLEET;
94
+ delete env.REMOTE_HUB_SYNTHETIC_FLEET;
95
+ if (syntheticFleetEnabled) {
96
+ env.MINDEXEC_REMOTE_SYNTHETIC_FLEET = '1';
92
97
  }
93
- });
94
-
95
- let stdout = '';
96
- let stderr = '';
97
- child.stdout.on('data', chunk => {
98
- stdout += chunk.toString();
99
- });
100
- child.stderr.on('data', chunk => {
101
- stderr += chunk.toString();
102
- });
103
-
104
- const exitPromise = new Promise(resolve => child.once('exit', resolve));
105
- const details = () => `stdout=${stdout}\nstderr=${stderr}`;
106
-
107
- try {
108
- const status = await waitForBridge(baseUrl, details);
98
+
99
+ const child = spawn(process.execPath, ['server.js'], {
100
+ cwd: bridgeRoot,
101
+ stdio: ['ignore', 'pipe', 'pipe'],
102
+ windowsHide: true,
103
+ env
104
+ });
105
+
106
+ let stdout = '';
107
+ let stderr = '';
108
+ child.stdout.on('data', chunk => {
109
+ stdout += chunk.toString();
110
+ });
111
+ child.stderr.on('data', chunk => {
112
+ stderr += chunk.toString();
113
+ });
114
+
115
+ const exitPromise = new Promise(resolve => child.once('exit', resolve));
116
+ const details = () => `stdout=${stdout}\nstderr=${stderr}`;
117
+ const stop = async () => {
118
+ if (child.exitCode === null && !child.killed) {
119
+ child.kill('SIGTERM');
120
+ await Promise.race([
121
+ exitPromise,
122
+ wait(5000)
123
+ ]);
124
+ if (child.exitCode === null && !child.killed) {
125
+ child.kill('SIGKILL');
126
+ }
127
+ }
128
+ };
129
+
130
+ return {
131
+ baseUrl: `http://127.0.0.1:${bridgePort}`,
132
+ bridgePort,
133
+ remoteHubPort,
134
+ bridgeToken,
135
+ pairToken,
136
+ child,
137
+ details,
138
+ stop
139
+ };
140
+ }
141
+
142
+ async function runSyntheticEnabledSmoke() {
143
+ const bridgePort = await findFreePort();
144
+ const remoteHubPort = await findFreePort();
145
+ const bridge = spawnBridge({
146
+ bridgePort,
147
+ remoteHubPort,
148
+ syntheticFleetEnabled: true
149
+ });
150
+ const { baseUrl } = bridge;
151
+ const details = bridge.details;
152
+
153
+ try {
154
+ const status = await waitForBridge(baseUrl, details, bridge.bridgeToken);
109
155
  assert.equal(status.started, true);
110
156
  assert.equal(status.port, remoteHubPort);
111
157
  assert.equal(status.managerPackage, '@mindexec/cli');
112
- assert.equal(status.managerVersion, '0.2.24');
158
+ assert.equal(status.managerVersion, '0.2.25');
113
159
  assert.equal(status.agentPackage, '@mindexec/remote');
114
160
  assert.equal(status.canvasPagination, 'none');
115
161
  assert.equal(status.canvasDeviceListMode, 'all-devices');
162
+ assert.equal(status.hostTargetActive, false);
116
163
  assert.equal(status.pairToken, PAIR_TOKEN);
117
164
 
118
165
  const unauthorizedStatus = await fetchJson(`${baseUrl}/api/remote/status`);
@@ -124,6 +171,58 @@ try {
124
171
  });
125
172
  assert.equal(unauthorizedDelete.status, 401);
126
173
 
174
+ const unauthorizedHost = await fetchJson(`${baseUrl}/api/remote/host-target`, {
175
+ method: 'POST',
176
+ body: JSON.stringify({
177
+ nodeId: 'remote-fleet-host-unauthorized',
178
+ enabled: true
179
+ })
180
+ });
181
+ assert.equal(unauthorizedHost.status, 401);
182
+
183
+ const setHost = await fetchJson(`${baseUrl}/api/remote/host-target`, {
184
+ method: 'POST',
185
+ token: BRIDGE_TOKEN,
186
+ body: JSON.stringify({
187
+ nodeId: 'remote-fleet-monitor-a',
188
+ enabled: true,
189
+ leaseMs: 30000
190
+ })
191
+ });
192
+ assert.equal(setHost.ok, true, JSON.stringify(setHost.payload));
193
+ assert.equal(setHost.payload?.ok, true);
194
+ assert.equal(setHost.payload?.active, true);
195
+ assert.equal(setHost.payload?.hostTarget?.nodeId, 'remote-fleet-monitor-a');
196
+ assert.equal(setHost.payload?.hostTarget?.endpoint, `127.0.0.1:${remoteHubPort}`);
197
+
198
+ const hostStatus = await fetchJson(`${baseUrl}/api/remote/status`, { token: BRIDGE_TOKEN });
199
+ assert.equal(hostStatus.ok, true, JSON.stringify(hostStatus.payload));
200
+ assert.equal(hostStatus.payload?.hostTargetActive, true);
201
+ assert.equal(hostStatus.payload?.hostTargetNodeId, 'remote-fleet-monitor-a');
202
+ assert.equal(hostStatus.payload?.hostTargetEndpoint, `127.0.0.1:${remoteHubPort}`);
203
+
204
+ const clearHostMismatch = await fetchJson(`${baseUrl}/api/remote/host-target`, {
205
+ method: 'DELETE',
206
+ token: BRIDGE_TOKEN,
207
+ body: JSON.stringify({
208
+ nodeId: 'remote-fleet-monitor-b'
209
+ })
210
+ });
211
+ assert.equal(clearHostMismatch.ok, true, JSON.stringify(clearHostMismatch.payload));
212
+ assert.equal(clearHostMismatch.payload?.ok, false);
213
+ assert.equal(clearHostMismatch.payload?.error, 'host-target-node-mismatch');
214
+
215
+ const clearHost = await fetchJson(`${baseUrl}/api/remote/host-target`, {
216
+ method: 'DELETE',
217
+ token: BRIDGE_TOKEN,
218
+ body: JSON.stringify({
219
+ nodeId: 'remote-fleet-monitor-a'
220
+ })
221
+ });
222
+ assert.equal(clearHost.ok, true, JSON.stringify(clearHost.payload));
223
+ assert.equal(clearHost.payload?.ok, true);
224
+ assert.equal(clearHost.payload?.active, false);
225
+
127
226
  const seed = await fetchJson(`${baseUrl}/api/remote/synthetic/seed`, {
128
227
  method: 'POST',
129
228
  token: BRIDGE_TOKEN,
@@ -283,14 +382,93 @@ try {
283
382
 
284
383
  console.log(`RemoteHub HTTP smoke OK (${SYNTHETIC_COUNT} synthetic devices, bridge ${bridgePort}, remote ${remoteHubPort})`);
285
384
  } finally {
286
- if (child.exitCode === null && !child.killed) {
287
- child.kill('SIGTERM');
288
- await Promise.race([
289
- exitPromise,
290
- wait(5000)
291
- ]);
292
- if (child.exitCode === null && !child.killed) {
293
- child.kill('SIGKILL');
294
- }
385
+ await bridge.stop();
386
+ }
387
+ }
388
+
389
+ async function runSyntheticDisabledSmoke() {
390
+ const bridgePort = await findFreePort();
391
+ const remoteHubPort = await findFreePort();
392
+ const bridgeToken = `${BRIDGE_TOKEN}-disabled`;
393
+ const pairToken = `${PAIR_TOKEN}-disabled`;
394
+ const bridge = spawnBridge({
395
+ bridgePort,
396
+ remoteHubPort,
397
+ bridgeToken,
398
+ pairToken,
399
+ syntheticFleetEnabled: false
400
+ });
401
+ const { baseUrl } = bridge;
402
+
403
+ try {
404
+ const status = await waitForBridge(baseUrl, bridge.details, bridge.bridgeToken);
405
+ assert.equal(status.started, true);
406
+ assert.equal(status.port, remoteHubPort);
407
+ assert.equal(status.pairToken, pairToken);
408
+ assert.equal(status.canvasPagination, 'none');
409
+ assert.equal(status.canvasDeviceListMode, 'all-devices');
410
+ assert.equal(status.hostTargetActive, false);
411
+
412
+ const seedDenied = await fetchJson(`${baseUrl}/api/remote/synthetic/seed`, {
413
+ method: 'POST',
414
+ token: bridge.bridgeToken,
415
+ body: JSON.stringify({
416
+ count: 3,
417
+ replace: true
418
+ })
419
+ });
420
+ assert.equal(seedDenied.status, 403);
421
+ assert.equal(seedDenied.payload?.ok, false);
422
+ assert.equal(seedDenied.payload?.error, 'synthetic-fleet-disabled');
423
+
424
+ const clearDenied = await fetchJson(`${baseUrl}/api/remote/synthetic`, {
425
+ method: 'DELETE',
426
+ token: bridge.bridgeToken
427
+ });
428
+ assert.equal(clearDenied.status, 403);
429
+ assert.equal(clearDenied.payload?.ok, false);
430
+ assert.equal(clearDenied.payload?.error, 'synthetic-fleet-disabled');
431
+
432
+ const devicesResult = await fetchJson(`${baseUrl}/api/remote/devices`, { token: bridge.bridgeToken });
433
+ assert.equal(devicesResult.ok, true, JSON.stringify(devicesResult.payload));
434
+ assert.equal(devicesResult.payload?.total, 0);
435
+ assert.equal(devicesResult.payload?.pagination, 'none');
436
+ assert.equal(devicesResult.payload?.canvasDeviceListMode, 'all-devices');
437
+ assert.equal(devicesResult.payload?.devices?.length, 0);
438
+
439
+ const emptyBatch = await fetchJson(`${baseUrl}/api/remote/tasks`, {
440
+ method: 'POST',
441
+ token: bridge.bridgeToken,
442
+ body: JSON.stringify({
443
+ allConnected: true,
444
+ title: 'HTTP smoke no target batch',
445
+ instruction: 'This should not queue because no remote devices are connected.',
446
+ approvalLevel: 'task-only'
447
+ })
448
+ });
449
+ assert.equal(emptyBatch.ok, true, JSON.stringify(emptyBatch.payload));
450
+ assert.equal(emptyBatch.payload?.ok, false);
451
+ assert.equal(emptyBatch.payload?.total, 0);
452
+ assert.equal(emptyBatch.payload?.queued, 0);
453
+ assert.equal(emptyBatch.payload?.error, 'no-target-devices');
454
+
455
+ const hostTarget = await fetchJson(`${baseUrl}/api/remote/host-target`, {
456
+ method: 'POST',
457
+ token: bridge.bridgeToken,
458
+ body: JSON.stringify({
459
+ nodeId: 'disabled-synthetic-host',
460
+ enabled: true
461
+ })
462
+ });
463
+ assert.equal(hostTarget.ok, true, JSON.stringify(hostTarget.payload));
464
+ assert.equal(hostTarget.payload?.ok, true);
465
+ assert.equal(hostTarget.payload?.active, true);
466
+
467
+ console.log(`RemoteHub HTTP safety smoke OK (synthetic disabled, bridge ${bridgePort}, remote ${remoteHubPort})`);
468
+ } finally {
469
+ await bridge.stop();
295
470
  }
296
471
  }
472
+
473
+ await runSyntheticEnabledSmoke();
474
+ await runSyntheticDisabledSmoke();
@@ -47,6 +47,7 @@ try {
47
47
  socket.once('connect', resolve);
48
48
  socket.once('error', reject);
49
49
  });
50
+ socket.on('error', () => { });
50
51
 
51
52
  writeJsonLine(socket, {
52
53
  type: 'hello',
@@ -306,7 +307,145 @@ try {
306
307
  assert.equal(lateTimeoutDevice.latestTask.resultSummary, 'task-timeout');
307
308
  assert.equal(lateTimeoutDevice.counters.taskResultsReceived, 4);
308
309
 
310
+ const timeoutBatch = hub.requestAgentTaskBatch(['smoke-device'], {
311
+ instruction: 'This batch item intentionally times out before a late result arrives.',
312
+ title: 'Timeout batch'
313
+ });
314
+ assert.equal(timeoutBatch.ok, true);
315
+ assert.equal(timeoutBatch.total, 1);
316
+ assert.equal(timeoutBatch.queued, 1);
317
+ assert.equal(timeoutBatch.batch?.pending, 1);
318
+ const timeoutBatchItem = timeoutBatch.results.find(result => result.deviceId === 'smoke-device');
319
+ assert.ok(timeoutBatchItem?.commandId);
320
+ assert.ok(timeoutBatchItem?.taskId);
321
+
322
+ const timedOutBatch = await waitFor(() => {
323
+ const latest = hub.getLatestTaskBatch();
324
+ return latest?.batchId === timeoutBatch.batchId
325
+ && latest.timedOut === 1
326
+ && latest.pending === 0
327
+ ? latest
328
+ : null;
329
+ }, 1000);
330
+ assert.equal(timedOutBatch.status, 'completed-with-failures');
331
+ assert.equal(timedOutBatch.failed, 1);
332
+ assert.equal(timedOutBatch.results[0]?.status, 'failed');
333
+ assert.equal(timedOutBatch.results[0]?.error, 'task-timeout');
334
+
335
+ writeJsonLine(socket, {
336
+ type: 'command.result',
337
+ commandId: timeoutBatchItem.commandId,
338
+ result: {
339
+ kind: 'agent.task',
340
+ taskId: timeoutBatchItem.taskId,
341
+ status: 'completed',
342
+ summary: 'This late batch timeout result must be ignored.',
343
+ completedAt: new Date().toISOString()
344
+ }
345
+ });
346
+ await wait(100);
347
+ const lateTimedOutBatch = hub.getLatestTaskBatch();
348
+ const lateTimedOutDevice = hub.listDevices()[0];
349
+ assert.equal(lateTimedOutBatch.batchId, timeoutBatch.batchId);
350
+ assert.equal(lateTimedOutBatch.status, 'completed-with-failures');
351
+ assert.equal(lateTimedOutBatch.completed, 0);
352
+ assert.equal(lateTimedOutBatch.failed, 1);
353
+ assert.equal(lateTimedOutBatch.timedOut, 1);
354
+ assert.equal(lateTimedOutBatch.results[0]?.status, 'failed');
355
+ assert.equal(lateTimedOutBatch.results[0]?.error, 'task-timeout');
356
+ assert.equal(lateTimedOutDevice.latestTask.taskId, timeoutBatchItem.taskId);
357
+ assert.equal(lateTimedOutDevice.latestTask.status, 'failed');
358
+ assert.equal(lateTimedOutDevice.latestTask.error, 'task-timeout');
359
+ assert.equal(lateTimedOutDevice.latestTask.resultSummary, 'task-timeout');
360
+ assert.equal(lateTimedOutDevice.counters.taskResultsReceived, 5);
361
+ assert.equal(lateTimedOutDevice.counters.taskResultsFailed, 2);
362
+ assert.equal(lateTimedOutDevice.counters.taskResultsTimedOut, 2);
363
+
364
+ const staleSessionTask = hub.requestAgentTask('smoke-device', {
365
+ instruction: 'This old session task must not survive reconnect.',
366
+ title: 'Reconnect stale task'
367
+ });
368
+ assert.equal(staleSessionTask.ok, true);
369
+ const oldSessionId = lateTimedOutDevice.sessionId;
370
+ const replacementSocket = net.createConnection({ host: '127.0.0.1', port: status.port });
371
+ replacementSocket.setEncoding('utf8');
372
+ await new Promise((resolve, reject) => {
373
+ replacementSocket.once('connect', resolve);
374
+ replacementSocket.once('error', reject);
375
+ });
376
+ replacementSocket.on('error', () => { });
377
+ writeJsonLine(replacementSocket, {
378
+ type: 'hello',
379
+ pairToken: 'smoke-token',
380
+ deviceId: 'smoke-device',
381
+ deviceName: 'Smoke Device Reconnected',
382
+ hostname: 'smoke-host-reconnected',
383
+ platform: process.platform,
384
+ arch: process.arch,
385
+ pid: process.pid,
386
+ agentVersion: '0.0.0-smoke-reconnected',
387
+ capabilities: {
388
+ status: true,
389
+ thumbnail: false,
390
+ control: false,
391
+ liveStream: true,
392
+ computerAgent: true,
393
+ taskDispatch: true,
394
+ aiAssist: true,
395
+ aiModel: 'fake-ai'
396
+ }
397
+ });
398
+ writeJsonLine(replacementSocket, {
399
+ type: 'status',
400
+ status: {
401
+ uptimeSec: 2,
402
+ totalMem: 4,
403
+ freeMem: 3
404
+ }
405
+ });
406
+
407
+ const reconnectedDevice = await waitFor(() => {
408
+ const current = hub.listDevices();
409
+ return current.length === 1
410
+ && current[0]?.deviceName === 'Smoke Device Reconnected'
411
+ && current[0]?.status?.uptimeSec === 2
412
+ ? current[0]
413
+ : null;
414
+ });
415
+ assert.notEqual(reconnectedDevice.sessionId, oldSessionId);
416
+ assert.equal(reconnectedDevice.connected, true);
417
+ assert.equal(reconnectedDevice.latestTask, null);
418
+ assert.equal(reconnectedDevice.counters.taskResultsReceived, 0);
419
+
420
+ writeJsonLine(socket, {
421
+ type: 'status',
422
+ status: {
423
+ uptimeSec: 999,
424
+ totalMem: 999,
425
+ freeMem: 0
426
+ }
427
+ });
428
+ writeJsonLine(socket, {
429
+ type: 'command.result',
430
+ commandId: staleSessionTask.commandId,
431
+ result: {
432
+ kind: 'agent.task',
433
+ taskId: staleSessionTask.taskId,
434
+ status: 'completed',
435
+ summary: 'This old-session result must not mutate the reconnected device.',
436
+ completedAt: new Date().toISOString()
437
+ }
438
+ });
439
+ await wait(100);
440
+ const staleSessionIgnoredDevice = hub.listDevices()[0];
441
+ assert.equal(staleSessionIgnoredDevice.sessionId, reconnectedDevice.sessionId);
442
+ assert.equal(staleSessionIgnoredDevice.deviceName, 'Smoke Device Reconnected');
443
+ assert.equal(staleSessionIgnoredDevice.status.uptimeSec, 2);
444
+ assert.equal(staleSessionIgnoredDevice.latestTask, null);
445
+ assert.equal(staleSessionIgnoredDevice.counters.taskResultsReceived, 0);
446
+
309
447
  socket.destroy();
448
+ replacementSocket.destroy();
310
449
  await waitFor(() => hub.listDevices()[0]?.connected === false);
311
450
  console.log('RemoteHub smoke OK');
312
451
  } finally {
package/server.js CHANGED
@@ -6978,6 +6978,23 @@ app.get('/api/remote/devices', (req, res) => {
6978
6978
  });
6979
6979
  });
6980
6980
 
6981
+ app.post('/api/remote/host-target', (req, res) => {
6982
+ res.setHeader('Cache-Control', 'no-store');
6983
+ res.json(remoteHub.setHostTarget({
6984
+ enabled: req.body?.enabled !== false,
6985
+ nodeId: req.body?.nodeId,
6986
+ leaseMs: req.body?.leaseMs
6987
+ }));
6988
+ });
6989
+
6990
+ app.delete('/api/remote/host-target', (req, res) => {
6991
+ res.setHeader('Cache-Control', 'no-store');
6992
+ res.json(remoteHub.setHostTarget({
6993
+ enabled: false,
6994
+ nodeId: req.body?.nodeId
6995
+ }));
6996
+ });
6997
+
6981
6998
  function isSyntheticRemoteFleetEnabled() {
6982
6999
  return /^(1|true|yes|on)$/i.test(String(
6983
7000
  process.env.MINDEXEC_REMOTE_SYNTHETIC_FLEET
@@ -6280,7 +6280,24 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
6280
6280
 
6281
6281
  const x = Number(moduleInstance.cursorPosition?.x ?? moduleInstance.camera?.position?.x ?? 0);
6282
6282
  const y = Number(moduleInstance.cursorPosition?.y ?? moduleInstance.camera?.position?.y ?? 0);
6283
- return await moduleInstance.dotNetHelper.invokeMethodAsync('AddRemoteFleetMonitorNodeFromJs', x, y);
6283
+ const result = await moduleInstance.dotNetHelper.invokeMethodAsync('AddRemoteFleetMonitorNodeFromJs', x, y);
6284
+ const nodeId = String(result?.nodeId ?? result?.NodeId ?? '').trim();
6285
+ if (nodeId) {
6286
+ const alreadyExists = result?.alreadyExists === true || result?.AlreadyExists === true;
6287
+ const focusExistingMonitor = () => window.mindMap?.focusNode?.(nodeId, {
6288
+ selectNode: true,
6289
+ preferReadableZoom: true,
6290
+ highlightSearchFocus: alreadyExists
6291
+ }) === true;
6292
+ if (!focusExistingMonitor()) {
6293
+ setTimeout(focusExistingMonitor, 120);
6294
+ }
6295
+ window.RuntimeTrace?.emit?.('remote.monitor.focused', {
6296
+ nodeId,
6297
+ alreadyExists
6298
+ });
6299
+ }
6300
+ return result;
6284
6301
  },
6285
6302
  // ▼▼▼ [Perf] Expose nodeObjectsById for will-change optimization ▼▼▼
6286
6303
  getNodeObjectsById: () => moduleInstance?.nodeObjectsById || null,