@mindexec/cli 0.2.23 → 0.2.25

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 +1 -1
  2. package/remote-hub.js +134 -1
  3. package/scripts/remote-fleet-render-smoke.mjs +87 -53
  4. package/scripts/remote-http-smoke.mjs +219 -39
  5. package/scripts/remote-hub-smoke.mjs +139 -0
  6. package/server.js +20 -1
  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 +670 -395
  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.badrt1tkvv.dll → MindExecution.Kernel.z56elxihok.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Admin.73w1bvz4r1.dll → MindExecution.Plugins.Admin.p5cs4ap87v.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Business.dvd82y422m.dll → MindExecution.Plugins.Business.s35og5uz44.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Concept.m3ukc0xvom.dll → MindExecution.Plugins.Concept.zczca3fsxz.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.Directory.23tm2uvfvu.dll → MindExecution.Plugins.Directory.y74f55e8x3.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.8nrc7ge4ob.dll → MindExecution.Plugins.PlanMaster.jpdwbefrh1.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.3ox59073d8.dll → MindExecution.Plugins.YouTube.8nz4wv2nsj.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Shared.va1gxp0crd.dll → MindExecution.Shared.v6ani8nfp8.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.c9fyqe953v.dll +0 -0
  95. package/wwwroot/_framework/MindExecution.Web.jmawk7z8d3.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,47 +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);
157
+ assert.equal(status.managerPackage, '@mindexec/cli');
158
+ assert.equal(status.managerVersion, '0.2.25');
111
159
  assert.equal(status.agentPackage, '@mindexec/remote');
112
160
  assert.equal(status.canvasPagination, 'none');
113
161
  assert.equal(status.canvasDeviceListMode, 'all-devices');
162
+ assert.equal(status.hostTargetActive, false);
114
163
  assert.equal(status.pairToken, PAIR_TOKEN);
115
164
 
116
165
  const unauthorizedStatus = await fetchJson(`${baseUrl}/api/remote/status`);
@@ -122,6 +171,58 @@ try {
122
171
  });
123
172
  assert.equal(unauthorizedDelete.status, 401);
124
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
+
125
226
  const seed = await fetchJson(`${baseUrl}/api/remote/synthetic/seed`, {
126
227
  method: 'POST',
127
228
  token: BRIDGE_TOKEN,
@@ -281,14 +382,93 @@ try {
281
382
 
282
383
  console.log(`RemoteHub HTTP smoke OK (${SYNTHETIC_COUNT} synthetic devices, bridge ${bridgePort}, remote ${remoteHubPort})`);
283
384
  } finally {
284
- if (child.exitCode === null && !child.killed) {
285
- child.kill('SIGTERM');
286
- await Promise.race([
287
- exitPromise,
288
- wait(5000)
289
- ]);
290
- if (child.exitCode === null && !child.killed) {
291
- child.kill('SIGKILL');
292
- }
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();
293
470
  }
294
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
@@ -2464,7 +2464,9 @@ const remoteHub = createRemoteHub({
2464
2464
  logEvent,
2465
2465
  logWarn,
2466
2466
  logError,
2467
- emitEvent: emitBridgeEvent
2467
+ emitEvent: emitBridgeEvent,
2468
+ managerPackage: BRIDGE_PACKAGE_NAME,
2469
+ managerVersion: BRIDGE_VERSION
2468
2470
  });
2469
2471
 
2470
2472
  function trimShellOutput(value, maxLength) {
@@ -6976,6 +6978,23 @@ app.get('/api/remote/devices', (req, res) => {
6976
6978
  });
6977
6979
  });
6978
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
+
6979
6998
  function isSyntheticRemoteFleetEnabled() {
6980
6999
  return /^(1|true|yes|on)$/i.test(String(
6981
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,