@mindexec/cli 0.2.93 → 0.2.95

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 (19) hide show
  1. package/package.json +1 -1
  2. package/remote-hub.js +118 -9
  3. package/scripts/remote-http-smoke.mjs +2 -0
  4. package/server.js +194 -9
  5. package/wwwroot/_framework/MindExecution.Core.eyl4o4qxg8.dll +0 -0
  6. package/wwwroot/_framework/{MindExecution.Kernel.55q6x6zeoc.dll → MindExecution.Kernel.hxyapaztgr.dll} +0 -0
  7. package/wwwroot/_framework/{MindExecution.Plugins.Admin.peei1t6ncj.dll → MindExecution.Plugins.Admin.7kemovesa7.dll} +0 -0
  8. package/wwwroot/_framework/{MindExecution.Plugins.Business.04t0oe4t4k.dll → MindExecution.Plugins.Business.t370in2g1b.dll} +0 -0
  9. package/wwwroot/_framework/{MindExecution.Plugins.Concept.f4gpqdzqwj.dll → MindExecution.Plugins.Concept.oqhv1116kb.dll} +0 -0
  10. package/wwwroot/_framework/{MindExecution.Plugins.Directory.az6m63vnll.dll → MindExecution.Plugins.Directory.0v80a0hi9m.dll} +0 -0
  11. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.o2xcmf9i46.dll → MindExecution.Plugins.PlanMaster.hn5sfx4z2l.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.xnrrltoofq.dll → MindExecution.Plugins.YouTube.c8sc0tlc6o.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Shared.6qq547u3iu.dll → MindExecution.Shared.osv9nbh3ym.dll} +0 -0
  14. package/wwwroot/_framework/MindExecution.Web.cdu22y82zq.dll +0 -0
  15. package/wwwroot/_framework/blazor.boot.json +21 -21
  16. package/wwwroot/service-worker-assets.js +22 -22
  17. package/wwwroot/service-worker.js +1 -1
  18. package/wwwroot/_framework/MindExecution.Core.e31e775w23.dll +0 -0
  19. package/wwwroot/_framework/MindExecution.Web.nfbmmsf2rl.dll +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.93",
3
+ "version": "0.2.95",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
package/remote-hub.js CHANGED
@@ -77,10 +77,72 @@ function isPrivateIPv4(value) {
77
77
  || (parts[0] === 192 && parts[1] === 168);
78
78
  }
79
79
 
80
- function getReachableIPv4Candidates() {
80
+ function isPreferredPhysicalInterfaceName(value) {
81
+ const name = String(value || '').toLowerCase();
82
+ return /(^|[\s_\-()])((wi[\s_\-]?fi)|wifi|wireless|wlan|ethernet|lan|이더넷|en\d+|eth\d+)(?=$|[\s_\-()])/i.test(name);
83
+ }
84
+
85
+ function isVirtualInterfaceName(value) {
86
+ const name = String(value || '').toLowerCase();
87
+ return /virtual|vethernet|hyper-v|hyperv|vmware|virtualbox|vbox|docker|wsl|container|bluetooth|loopback|npcap|isatap|teredo/i.test(name);
88
+ }
89
+
90
+ function getIPv4LastOctet(value) {
91
+ const parts = String(value || '').split('.').map(part => Number(part));
92
+ if (parts.length !== 4 || parts.some(part => !Number.isInteger(part))) {
93
+ return -1;
94
+ }
95
+
96
+ return parts[3];
97
+ }
98
+
99
+ function scoreReachableIPv4Candidate(candidate) {
100
+ const address = String(candidate?.address || '');
101
+ const name = String(candidate?.name || '');
102
+ let score = 0;
103
+
104
+ if (isPrivateIPv4(address)) {
105
+ score += 1000;
106
+ }
107
+
108
+ if (/^192\.168\./.test(address)) {
109
+ score += 90;
110
+ } else if (/^10\./.test(address)) {
111
+ score += 80;
112
+ } else if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(address)) {
113
+ score += 70;
114
+ }
115
+
116
+ if (isPreferredPhysicalInterfaceName(name)) {
117
+ score += 500;
118
+ }
119
+
120
+ if (isVirtualInterfaceName(name)) {
121
+ score -= 1200;
122
+ }
123
+
124
+ if (/^169\.254\./.test(address)) {
125
+ score -= 2000;
126
+ }
127
+
128
+ const lastOctet = getIPv4LastOctet(address);
129
+ if (lastOctet === 1) {
130
+ score -= 60;
131
+ } else if (lastOctet === 0 || lastOctet === 255) {
132
+ score -= 100;
133
+ }
134
+
135
+ if (candidate?.mac && candidate.mac === '00:00:00:00:00:00') {
136
+ score -= 50;
137
+ }
138
+
139
+ return score;
140
+ }
141
+
142
+ function getReachableIPv4CandidateEntries() {
81
143
  const candidates = [];
82
144
  const interfaces = os.networkInterfaces();
83
- for (const entries of Object.values(interfaces)) {
145
+ for (const [name, entries] of Object.entries(interfaces)) {
84
146
  for (const entry of entries || []) {
85
147
  if (entry?.family !== 'IPv4' || entry.internal) {
86
148
  continue;
@@ -91,13 +153,28 @@ function getReachableIPv4Candidates() {
91
153
  continue;
92
154
  }
93
155
 
94
- candidates.push(address);
156
+ candidates.push({
157
+ address,
158
+ name: safeString(name, 128),
159
+ mac: safeString(entry.mac, 32),
160
+ score: 0,
161
+ virtual: isVirtualInterfaceName(name),
162
+ preferred: isPreferredPhysicalInterfaceName(name)
163
+ });
95
164
  }
96
165
  }
97
166
 
98
167
  return candidates
99
- .sort((left, right) => Number(isPrivateIPv4(right)) - Number(isPrivateIPv4(left)))
100
- .filter((address, index, all) => all.indexOf(address) === index);
168
+ .map(candidate => ({
169
+ ...candidate,
170
+ score: scoreReachableIPv4Candidate(candidate)
171
+ }))
172
+ .sort((left, right) => right.score - left.score || left.address.localeCompare(right.address))
173
+ .filter((candidate, index, all) => all.findIndex(item => item.address === candidate.address) === index);
174
+ }
175
+
176
+ function getReachableIPv4Candidates() {
177
+ return getReachableIPv4CandidateEntries().map(candidate => candidate.address);
101
178
  }
102
179
 
103
180
  function getReachableIPv4Address() {
@@ -105,6 +182,19 @@ function getReachableIPv4Address() {
105
182
  return candidates.find(isPrivateIPv4) || candidates[0] || '127.0.0.1';
106
183
  }
107
184
 
185
+ function buildEndpoint(hostValue, portValue) {
186
+ const hostText = safeString(hostValue, 128);
187
+ const port = normalizePort(portValue);
188
+ if (!hostText || !port) {
189
+ return '';
190
+ }
191
+
192
+ const hostPart = hostText.includes(':') && !hostText.startsWith('[')
193
+ ? `[${hostText}]`
194
+ : hostText;
195
+ return `${hostPart}:${port}`;
196
+ }
197
+
108
198
  function parseManagerEndpoint(value) {
109
199
  const endpoint = safeString(value, 256);
110
200
  const match = endpoint.match(/^(\[[^\]]+\]|[^:\s]+):(\d{1,5})$/);
@@ -409,13 +499,26 @@ export function createRemoteHub(options = {}) {
409
499
  }
410
500
 
411
501
  function getAgentEndpointRouteInfo() {
412
- const candidates = getReachableIPv4Candidates();
502
+ const includeInterfaceCandidates = !publicEndpoint && isWildcardHost(host);
503
+ const candidateDetails = includeInterfaceCandidates
504
+ ? getReachableIPv4CandidateEntries()
505
+ : [];
506
+ const candidateEndpoints = candidateDetails
507
+ .map(candidate => ({
508
+ ...candidate,
509
+ endpoint: buildEndpoint(candidate.address, boundPort || requestedPort)
510
+ }))
511
+ .filter(candidate => candidate.endpoint);
413
512
  const endpoint = getAgentEndpoint();
513
+ const endpointCandidates = [
514
+ endpoint,
515
+ ...candidateEndpoints.map(candidate => candidate.endpoint)
516
+ ].filter((value, index, all) => value && all.indexOf(value) === index);
414
517
  const wildcardWithoutReachableAddress =
415
518
  !publicEndpoint
416
519
  && !publicHost
417
520
  && isWildcardHost(host)
418
- && candidates.length === 0;
521
+ && candidateDetails.length === 0;
419
522
  const reason = getAccountRouteEndpointReason(endpoint, { wildcardWithoutReachableAddress });
420
523
  const parsed = parseManagerEndpoint(endpoint);
421
524
  return {
@@ -424,7 +527,8 @@ export function createRemoteHub(options = {}) {
424
527
  port: parsed?.port || 0,
425
528
  accountRouteReady: reason === 'ok',
426
529
  reason,
427
- candidates
530
+ candidates: endpointCandidates,
531
+ candidateDetails: candidateEndpoints
428
532
  };
429
533
  }
430
534
 
@@ -457,6 +561,7 @@ export function createRemoteHub(options = {}) {
457
561
  leaseId: '',
458
562
  hostInstanceId: '',
459
563
  endpoint: '',
564
+ endpointCandidates: [],
460
565
  activatedAt: '',
461
566
  updatedAt: '',
462
567
  expiresAt: ''
@@ -469,6 +574,7 @@ export function createRemoteHub(options = {}) {
469
574
  leaseId: target.leaseId,
470
575
  hostInstanceId: target.hostInstanceId || hostInstanceId,
471
576
  endpoint: target.endpoint,
577
+ endpointCandidates: Array.isArray(target.endpointCandidates) ? [...target.endpointCandidates] : [target.endpoint].filter(Boolean),
472
578
  activatedAt: target.activatedAt,
473
579
  updatedAt: target.updatedAt,
474
580
  expiresAt: target.expiresAt
@@ -530,6 +636,7 @@ export function createRemoteHub(options = {}) {
530
636
  leaseId: activeSameNode && previous?.leaseId ? previous.leaseId : crypto.randomUUID(),
531
637
  hostInstanceId,
532
638
  endpoint: routeInfo.endpoint,
639
+ endpointCandidates: routeInfo.candidates,
533
640
  activatedAt: activeSameNode && previous?.activatedAt ? previous.activatedAt : now.toISOString(),
534
641
  updatedAt: now.toISOString(),
535
642
  expiresAt: new Date(now.getTime() + leaseMs).toISOString()
@@ -542,7 +649,7 @@ export function createRemoteHub(options = {}) {
542
649
  if (!activeSameNode || !routeInfo.accountRouteReady) {
543
650
  logEvent(
544
651
  'remote',
545
- `host target ${routeInfo.accountRouteReady ? 'account-ready' : 'local-only'} endpoint=${routeInfo.endpoint} reason=${routeInfo.reason}`,
652
+ `host target ${routeInfo.accountRouteReady ? 'account-ready' : 'local-only'} endpoint=${routeInfo.endpoint} candidates=${routeInfo.candidates.length} reason=${routeInfo.reason}`,
546
653
  'remote');
547
654
  }
548
655
  return {
@@ -574,6 +681,7 @@ export function createRemoteHub(options = {}) {
574
681
  agentEndpointAccountRouteReady: routeInfo.accountRouteReady,
575
682
  agentEndpointRouteReason: routeInfo.reason,
576
683
  agentEndpointCandidates: routeInfo.candidates,
684
+ agentEndpointCandidateDetails: routeInfo.candidateDetails,
577
685
  pairToken: includeSecrets ? pairToken : undefined,
578
686
  pairTokenPreview: maskToken(pairToken),
579
687
  deviceCount: devices.size,
@@ -585,6 +693,7 @@ export function createRemoteHub(options = {}) {
585
693
  hostTargetLeaseId: activeHostTarget.leaseId,
586
694
  hostTargetHostInstanceId: activeHostTarget.hostInstanceId,
587
695
  hostTargetEndpoint: activeHostTarget.endpoint,
696
+ hostTargetEndpointCandidates: activeHostTarget.endpointCandidates,
588
697
  hostTargetActivatedAt: activeHostTarget.activatedAt,
589
698
  hostTargetUpdatedAt: activeHostTarget.updatedAt,
590
699
  hostTargetExpiresAt: activeHostTarget.expiresAt,
@@ -217,12 +217,14 @@ async function runSyntheticEnabledSmoke() {
217
217
  assert.equal(setHost.payload?.active, true);
218
218
  assert.equal(setHost.payload?.hostTarget?.nodeId, 'remote-fleet-monitor-a');
219
219
  assert.equal(setHost.payload?.hostTarget?.endpoint, `127.0.0.1:${remoteHubPort}`);
220
+ assert.deepEqual(setHost.payload?.hostTarget?.endpointCandidates, [`127.0.0.1:${remoteHubPort}`]);
220
221
 
221
222
  const hostStatus = await fetchJson(`${baseUrl}/api/remote/status`, { token: BRIDGE_TOKEN });
222
223
  assert.equal(hostStatus.ok, true, JSON.stringify(hostStatus.payload));
223
224
  assert.equal(hostStatus.payload?.hostTargetActive, true);
224
225
  assert.equal(hostStatus.payload?.hostTargetNodeId, 'remote-fleet-monitor-a');
225
226
  assert.equal(hostStatus.payload?.hostTargetEndpoint, `127.0.0.1:${remoteHubPort}`);
227
+ assert.deepEqual(hostStatus.payload?.hostTargetEndpointCandidates, [`127.0.0.1:${remoteHubPort}`]);
226
228
 
227
229
  const clearHostMismatch = await fetchJson(`${baseUrl}/api/remote/host-target`, {
228
230
  method: 'DELETE',
package/server.js CHANGED
@@ -2733,6 +2733,7 @@ const remoteHub = createRemoteHub({
2733
2733
 
2734
2734
  const REMOTE_AGENT_STDIO_TAIL_CHARS = 12000;
2735
2735
  const REMOTE_AGENT_EARLY_EXIT_MS = 1200;
2736
+ const REMOTE_AGENT_READY_TIMEOUT_MS = 5000;
2736
2737
  const REMOTE_AGENT_DEFAULT_ENGINE = 'auto';
2737
2738
  let remoteAgentState = createRemoteAgentIdleState();
2738
2739
 
@@ -2740,6 +2741,7 @@ function createRemoteAgentIdleState(overrides = {}) {
2740
2741
  return {
2741
2742
  status: 'idle',
2742
2743
  manager: '',
2744
+ managerCandidates: [],
2743
2745
  leaseId: '',
2744
2746
  nodeId: '',
2745
2747
  engine: REMOTE_AGENT_DEFAULT_ENGINE,
@@ -2748,6 +2750,8 @@ function createRemoteAgentIdleState(overrides = {}) {
2748
2750
  pid: 0,
2749
2751
  startedAt: '',
2750
2752
  updatedAt: new Date().toISOString(),
2753
+ ready: false,
2754
+ connectedAt: '',
2751
2755
  exitedAt: '',
2752
2756
  exitCode: null,
2753
2757
  signal: '',
@@ -2772,10 +2776,18 @@ function serializeRemoteAgentState() {
2772
2776
  };
2773
2777
  }
2774
2778
 
2775
- function appendRemoteAgentOutput(stream, chunk) {
2779
+ function appendRemoteAgentOutput(stream, chunk, stateConnectionKey = '') {
2780
+ if (stateConnectionKey && remoteAgentState.connectionKey !== stateConnectionKey) {
2781
+ return;
2782
+ }
2783
+
2776
2784
  const text = chunk.toString();
2777
2785
  const key = stream === 'stderr' ? 'stderrTail' : 'stdoutTail';
2778
2786
  remoteAgentState[key] = String((remoteAgentState[key] || '') + text).slice(-REMOTE_AGENT_STDIO_TAIL_CHARS);
2787
+ if (/Connected to RemoteHub/i.test(text)) {
2788
+ remoteAgentState.ready = true;
2789
+ remoteAgentState.connectedAt = new Date().toISOString();
2790
+ }
2779
2791
  remoteAgentState.updatedAt = new Date().toISOString();
2780
2792
  }
2781
2793
 
@@ -2844,6 +2856,53 @@ function waitForRemoteAgentEarlyExit(child, settleMs = REMOTE_AGENT_EARLY_EXIT_M
2844
2856
  });
2845
2857
  }
2846
2858
 
2859
+ function waitForRemoteAgentReady(child, timeoutMs = REMOTE_AGENT_READY_TIMEOUT_MS) {
2860
+ return new Promise(resolve => {
2861
+ if (!child || child.exitCode !== null || child.killed) {
2862
+ resolve(false);
2863
+ return;
2864
+ }
2865
+
2866
+ if (remoteAgentState.ready === true) {
2867
+ resolve(true);
2868
+ return;
2869
+ }
2870
+
2871
+ let settled = false;
2872
+ let timer = null;
2873
+ let poller = null;
2874
+ const cleanup = () => {
2875
+ if (timer) {
2876
+ clearTimeout(timer);
2877
+ }
2878
+ if (poller) {
2879
+ clearInterval(poller);
2880
+ }
2881
+ child.off('exit', onExit);
2882
+ child.off('error', onError);
2883
+ };
2884
+ const finish = value => {
2885
+ if (settled) {
2886
+ return;
2887
+ }
2888
+ settled = true;
2889
+ cleanup();
2890
+ resolve(value);
2891
+ };
2892
+ const onExit = () => finish(false);
2893
+ const onError = () => finish(false);
2894
+
2895
+ child.once('exit', onExit);
2896
+ child.once('error', onError);
2897
+ poller = setInterval(() => {
2898
+ if (remoteAgentState.ready === true) {
2899
+ finish(true);
2900
+ }
2901
+ }, 100);
2902
+ timer = setTimeout(() => finish(remoteAgentState.ready === true), timeoutMs);
2903
+ });
2904
+ }
2905
+
2847
2906
  function normalizeRemoteAgentEngine(value) {
2848
2907
  const engine = String(value || REMOTE_AGENT_DEFAULT_ENGINE).trim().toLowerCase();
2849
2908
  if (engine === 'fast' || engine === 'csharp' || engine === 'c#') {
@@ -2871,6 +2930,34 @@ function normalizeRemoteManagerEndpoint(value) {
2871
2930
  return `${match[1]}:${port}`;
2872
2931
  }
2873
2932
 
2933
+ function normalizeRemoteManagerEndpointList(...values) {
2934
+ const rawValues = [];
2935
+ for (const value of values) {
2936
+ if (Array.isArray(value)) {
2937
+ rawValues.push(...value);
2938
+ } else if (value && typeof value[Symbol.iterator] === 'function' && typeof value !== 'string') {
2939
+ rawValues.push(...value);
2940
+ } else {
2941
+ rawValues.push(value);
2942
+ }
2943
+ }
2944
+
2945
+ const endpoints = [];
2946
+ const seen = new Set();
2947
+ for (const value of rawValues) {
2948
+ const endpoint = normalizeRemoteManagerEndpoint(value);
2949
+ const key = endpoint.toLowerCase();
2950
+ if (!endpoint || seen.has(key)) {
2951
+ continue;
2952
+ }
2953
+
2954
+ seen.add(key);
2955
+ endpoints.push(endpoint);
2956
+ }
2957
+
2958
+ return endpoints;
2959
+ }
2960
+
2874
2961
  function safeRemoteAgentField(value, maxLength = 128) {
2875
2962
  return String(value || '').replace(/[\r\n\t]/g, ' ').trim().slice(0, maxLength);
2876
2963
  }
@@ -2928,6 +3015,7 @@ async function stopRemoteAgentConnection(reason = 'stopped') {
2928
3015
  remoteAgentState = createRemoteAgentIdleState({
2929
3016
  status: 'stopped',
2930
3017
  manager: previous.manager,
3018
+ managerCandidates: previous.managerCandidates,
2931
3019
  leaseId: previous.leaseId,
2932
3020
  nodeId: previous.nodeId,
2933
3021
  engine: previous.engine,
@@ -2946,23 +3034,31 @@ async function stopRemoteAgentConnection(reason = 'stopped') {
2946
3034
  }
2947
3035
 
2948
3036
  async function startRemoteAgentConnection(options = {}) {
2949
- const manager = normalizeRemoteManagerEndpoint(options.manager || options.managerEndpoint || options.endpoint);
2950
3037
  const pairToken = safeRemoteAgentField(options.pairToken || options.pair, 512);
2951
3038
  const leaseId = safeRemoteAgentField(options.leaseId, 128);
2952
3039
  const nodeId = safeRemoteAgentField(options.nodeId, 128);
2953
3040
  const source = safeRemoteAgentField(options.source || 'registry', 64);
2954
3041
  const engine = normalizeRemoteAgentEngine(options.engine);
2955
-
2956
- if (!manager) {
3042
+ const managers = normalizeRemoteManagerEndpointList(
3043
+ options.manager || options.managerEndpoint || options.endpoint,
3044
+ options.managerCandidates,
3045
+ options.endpointCandidates,
3046
+ options.candidates);
3047
+
3048
+ if (managers.length === 0) {
3049
+ logWarn('remote', 'managed RemoteAgent connect rejected: invalid manager endpoint');
2957
3050
  return { ok: false, error: 'invalid-manager-endpoint', agent: serializeRemoteAgentState() };
2958
3051
  }
2959
3052
 
2960
3053
  if (!pairToken) {
3054
+ logWarn('remote', `managed RemoteAgent connect rejected: missing pair token ${formatKeyValue('manager', managers[0])}`);
2961
3055
  return { ok: false, error: 'missing-pair-token', agent: serializeRemoteAgentState() };
2962
3056
  }
2963
3057
 
2964
- const connectionKey = createRemoteAgentConnectionKey(manager, leaseId);
2965
- if (isRemoteAgentProcessRunning() && remoteAgentState.connectionKey === connectionKey) {
3058
+ const activeConnectionKeys = new Set(managers.map(manager => createRemoteAgentConnectionKey(manager, leaseId)));
3059
+ if (isRemoteAgentProcessRunning()
3060
+ && remoteAgentState.ready === true
3061
+ && activeConnectionKeys.has(remoteAgentState.connectionKey)) {
2966
3062
  return { ok: true, alreadyRunning: true, agent: serializeRemoteAgentState() };
2967
3063
  }
2968
3064
 
@@ -2970,6 +3066,66 @@ async function startRemoteAgentConnection(options = {}) {
2970
3066
  await stopRemoteAgentConnection('replaced-by-new-target');
2971
3067
  }
2972
3068
 
3069
+ let lastResult = null;
3070
+ for (let index = 0; index < managers.length; index += 1) {
3071
+ const manager = managers[index];
3072
+ if (managers.length > 1) {
3073
+ logEvent(
3074
+ 'remote',
3075
+ `managed RemoteAgent candidate ${index + 1}/${managers.length} ${formatKeyValue('manager', manager)}`,
3076
+ 'remote');
3077
+ }
3078
+
3079
+ const result = await startRemoteAgentConnectionAttempt({
3080
+ manager,
3081
+ managerCandidates: managers,
3082
+ pairToken,
3083
+ leaseId,
3084
+ nodeId,
3085
+ source,
3086
+ engine
3087
+ });
3088
+ if (result?.ok === true) {
3089
+ return result;
3090
+ }
3091
+
3092
+ lastResult = result;
3093
+ if (isRemoteAgentProcessRunning()) {
3094
+ await stopRemoteAgentConnection(`candidate-failed:${manager}`);
3095
+ }
3096
+
3097
+ if (index + 1 < managers.length) {
3098
+ logWarn(
3099
+ 'remote',
3100
+ `managed RemoteAgent candidate failed; trying next ${formatKeyValue('manager', manager)} ${formatKeyValue('error', result?.error || 'unknown')}`);
3101
+ }
3102
+ }
3103
+
3104
+ return {
3105
+ ok: false,
3106
+ error: lastResult?.error || 'all-manager-candidates-failed',
3107
+ agent: lastResult?.agent || serializeRemoteAgentState()
3108
+ };
3109
+ }
3110
+
3111
+ async function startRemoteAgentConnectionAttempt(options = {}) {
3112
+ const manager = normalizeRemoteManagerEndpoint(options.manager || options.managerEndpoint || options.endpoint);
3113
+ const pairToken = safeRemoteAgentField(options.pairToken || options.pair, 512);
3114
+ const leaseId = safeRemoteAgentField(options.leaseId, 128);
3115
+ const nodeId = safeRemoteAgentField(options.nodeId, 128);
3116
+ const source = safeRemoteAgentField(options.source || 'registry', 64);
3117
+ const engine = normalizeRemoteAgentEngine(options.engine);
3118
+ const managerCandidates = normalizeRemoteManagerEndpointList(options.managerCandidates, manager);
3119
+
3120
+ if (!manager) {
3121
+ return { ok: false, error: 'invalid-manager-endpoint', agent: serializeRemoteAgentState() };
3122
+ }
3123
+
3124
+ if (!pairToken) {
3125
+ return { ok: false, error: 'missing-pair-token', agent: serializeRemoteAgentState() };
3126
+ }
3127
+
3128
+ const connectionKey = createRemoteAgentConnectionKey(manager, leaseId);
2973
3129
  const launcher = resolveRemoteAgentLauncher();
2974
3130
  const args = [
2975
3131
  ...launcher.argsPrefix,
@@ -2985,12 +3141,15 @@ async function startRemoteAgentConnection(options = {}) {
2985
3141
  remoteAgentState = createRemoteAgentIdleState({
2986
3142
  status: 'starting',
2987
3143
  manager,
3144
+ managerCandidates,
2988
3145
  leaseId,
2989
3146
  nodeId,
2990
3147
  engine,
2991
3148
  source,
2992
3149
  connectionKey,
2993
3150
  startedAt: new Date().toISOString(),
3151
+ ready: false,
3152
+ connectedAt: '',
2994
3153
  launcher: launcher.launcher,
2995
3154
  usingNpx: launcher.usingNpx
2996
3155
  });
@@ -3020,10 +3179,15 @@ async function startRemoteAgentConnection(options = {}) {
3020
3179
  remoteAgentState.pid = Number(child.pid || 0);
3021
3180
  remoteAgentState.status = 'running';
3022
3181
  remoteAgentState.updatedAt = new Date().toISOString();
3182
+ const stateConnectionKey = connectionKey;
3023
3183
 
3024
- child.stdout.on('data', chunk => appendRemoteAgentOutput('stdout', chunk));
3025
- child.stderr.on('data', chunk => appendRemoteAgentOutput('stderr', chunk));
3184
+ child.stdout.on('data', chunk => appendRemoteAgentOutput('stdout', chunk, stateConnectionKey));
3185
+ child.stderr.on('data', chunk => appendRemoteAgentOutput('stderr', chunk, stateConnectionKey));
3026
3186
  child.once('error', err => {
3187
+ if (remoteAgentState.connectionKey !== stateConnectionKey) {
3188
+ return;
3189
+ }
3190
+
3027
3191
  remoteAgentState.status = 'failed';
3028
3192
  remoteAgentState.lastError = err?.message || String(err);
3029
3193
  remoteAgentState.updatedAt = new Date().toISOString();
@@ -3031,6 +3195,10 @@ async function startRemoteAgentConnection(options = {}) {
3031
3195
  emitBridgeEvent('RemoteAgentFailed', serializeRemoteAgentState());
3032
3196
  });
3033
3197
  child.once('exit', (code, signal) => {
3198
+ if (remoteAgentState.connectionKey !== stateConnectionKey) {
3199
+ return;
3200
+ }
3201
+
3034
3202
  remoteAgentState.status = code === 0 ? 'exited' : 'failed';
3035
3203
  remoteAgentState.exitCode = Number.isFinite(code) ? code : null;
3036
3204
  remoteAgentState.signal = signal || '';
@@ -3056,6 +3224,18 @@ async function startRemoteAgentConnection(options = {}) {
3056
3224
  const exitedEarly = await waitForRemoteAgentEarlyExit(child);
3057
3225
  if (exitedEarly) {
3058
3226
  const error = formatRemoteAgentFailureSummary();
3227
+ logWarn('remote', `managed RemoteAgent exited during startup ${formatKeyValue('manager', manager)} ${formatKeyValue('error', error)}`);
3228
+ return { ok: false, error, agent: serializeRemoteAgentState() };
3229
+ }
3230
+
3231
+ const ready = await waitForRemoteAgentReady(child);
3232
+ if (!ready) {
3233
+ const error = `remote-agent-not-ready:${manager}`;
3234
+ remoteAgentState.status = 'failed';
3235
+ remoteAgentState.lastError = error;
3236
+ remoteAgentState.updatedAt = new Date().toISOString();
3237
+ logWarn('remote', `managed RemoteAgent startup did not connect ${formatKeyValue('manager', manager)} ${formatKeyValue('error', formatRemoteAgentFailureSummary())}`);
3238
+ emitBridgeEvent('RemoteAgentFailed', serializeRemoteAgentState());
3059
3239
  return { ok: false, error, agent: serializeRemoteAgentState() };
3060
3240
  }
3061
3241
 
@@ -7615,14 +7795,19 @@ app.get('/api/remote/agent/status', (req, res) => {
7615
7795
  app.post('/api/remote/agent/connect', async (req, res) => {
7616
7796
  res.setHeader('Cache-Control', 'no-store');
7617
7797
  try {
7798
+ const requestedManager = normalizeRemoteManagerEndpoint(req.body?.manager || req.body?.managerEndpoint);
7618
7799
  const result = await startRemoteAgentConnection({
7619
- manager: req.body?.manager || req.body?.managerEndpoint,
7800
+ manager: requestedManager,
7801
+ managerCandidates: req.body?.managerCandidates || req.body?.endpointCandidates || req.body?.candidates,
7620
7802
  pairToken: req.body?.pairToken || req.body?.pair,
7621
7803
  leaseId: req.body?.leaseId,
7622
7804
  nodeId: req.body?.nodeId,
7623
7805
  engine: req.body?.engine,
7624
7806
  source: req.body?.source || 'registry'
7625
7807
  });
7808
+ if (!result.ok) {
7809
+ logWarn('remote', `managed RemoteAgent connect failed ${formatKeyValue('manager', requestedManager || 'invalid')} ${formatKeyValue('error', result.error || 'unknown')}`);
7810
+ }
7626
7811
  res.status(result.ok ? 200 : 400).json(result);
7627
7812
  } catch (err) {
7628
7813
  logError('remote', 'managed RemoteAgent connect failed.', err);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-En37ftP0ESJ05mTVQWmF9DLUneIu5kqMcNkEfGC06KA=",
4
+ "hash": "sha256-5gnfR4WvV/8ivk6zDF09/vNhA5Ptgr7fb/UhdwOAmHM=",
5
5
  "fingerprinting": {
6
6
  "Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
7
7
  "Markdig.d1j7v41cl1.dll": "Markdig.dll",
@@ -123,16 +123,16 @@
123
123
  "System.brmz7yk5qh.dll": "System.dll",
124
124
  "netstandard.yvr3prsx0x.dll": "netstandard.dll",
125
125
  "System.Private.CoreLib.c1dbswx1b2.dll": "System.Private.CoreLib.dll",
126
- "MindExecution.Core.e31e775w23.dll": "MindExecution.Core.dll",
127
- "MindExecution.Kernel.55q6x6zeoc.dll": "MindExecution.Kernel.dll",
128
- "MindExecution.Plugins.Admin.peei1t6ncj.dll": "MindExecution.Plugins.Admin.dll",
129
- "MindExecution.Plugins.Business.04t0oe4t4k.dll": "MindExecution.Plugins.Business.dll",
130
- "MindExecution.Plugins.Concept.f4gpqdzqwj.dll": "MindExecution.Plugins.Concept.dll",
131
- "MindExecution.Plugins.Directory.az6m63vnll.dll": "MindExecution.Plugins.Directory.dll",
132
- "MindExecution.Plugins.PlanMaster.o2xcmf9i46.dll": "MindExecution.Plugins.PlanMaster.dll",
133
- "MindExecution.Plugins.YouTube.xnrrltoofq.dll": "MindExecution.Plugins.YouTube.dll",
134
- "MindExecution.Shared.6qq547u3iu.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.nfbmmsf2rl.dll": "MindExecution.Web.dll",
126
+ "MindExecution.Core.eyl4o4qxg8.dll": "MindExecution.Core.dll",
127
+ "MindExecution.Kernel.hxyapaztgr.dll": "MindExecution.Kernel.dll",
128
+ "MindExecution.Plugins.Admin.7kemovesa7.dll": "MindExecution.Plugins.Admin.dll",
129
+ "MindExecution.Plugins.Business.t370in2g1b.dll": "MindExecution.Plugins.Business.dll",
130
+ "MindExecution.Plugins.Concept.oqhv1116kb.dll": "MindExecution.Plugins.Concept.dll",
131
+ "MindExecution.Plugins.Directory.0v80a0hi9m.dll": "MindExecution.Plugins.Directory.dll",
132
+ "MindExecution.Plugins.PlanMaster.hn5sfx4z2l.dll": "MindExecution.Plugins.PlanMaster.dll",
133
+ "MindExecution.Plugins.YouTube.c8sc0tlc6o.dll": "MindExecution.Plugins.YouTube.dll",
134
+ "MindExecution.Shared.osv9nbh3ym.dll": "MindExecution.Shared.dll",
135
+ "MindExecution.Web.cdu22y82zq.dll": "MindExecution.Web.dll",
136
136
  "dotnet.js": "dotnet.js",
137
137
  "dotnet.native.qc8g39g30v.js": "dotnet.native.js",
138
138
  "dotnet.native.boem75ye5i.wasm": "dotnet.native.wasm",
@@ -278,18 +278,18 @@
278
278
  "System.Xml.XDocument.sn51jas17n.dll": "sha256-GNI2kFgFmPTwzuzwUn8gxK+AzGLUWRJFdg9JzIbrybQ=",
279
279
  "System.brmz7yk5qh.dll": "sha256-CfM2miyj1KHApFmqMdLYWio3S/jrdON2pW9Xr2nTwlo=",
280
280
  "netstandard.yvr3prsx0x.dll": "sha256-EksNn8Luo4bOWqJ6X7dIe9qG9oOqwOVzjH2xYyMNi+E=",
281
- "MindExecution.Core.e31e775w23.dll": "sha256-CjA5yl5JfpYf6+7TuOwW4XZuBybv5Y6g5sAYXgoaq2M=",
282
- "MindExecution.Kernel.55q6x6zeoc.dll": "sha256-2RLhrX3JMUc5g/mGCTwFB61ND7dgYYPaFFFPUAakdiU=",
283
- "MindExecution.Plugins.Concept.f4gpqdzqwj.dll": "sha256-33uklvsdPqUhPHgnHxyQLPMNbo22Ztttf6r3foxoq7M=",
284
- "MindExecution.Plugins.PlanMaster.o2xcmf9i46.dll": "sha256-kEtfwKXd/IeopHD65TdAZ+8RpdLvnRFJhJ5m2PXozYk=",
285
- "MindExecution.Shared.6qq547u3iu.dll": "sha256-jrYtMJjsdd4gl1VIp/WvUr1j6xPzFcb6KsRP8gVxz9I=",
286
- "MindExecution.Web.nfbmmsf2rl.dll": "sha256-neaU6eYk85SeylghFpeBWqjRvKdscMU7Rfx2aPgn9N4="
281
+ "MindExecution.Core.eyl4o4qxg8.dll": "sha256-JtBC04XSxkMOl0cZOA2ZWvyV88yYPyU0nkIbBCF9Jtc=",
282
+ "MindExecution.Kernel.hxyapaztgr.dll": "sha256-q59U8/001rTsB1m+pkA4PkI9a9lvf5fijDWKXbN7uQ4=",
283
+ "MindExecution.Plugins.Concept.oqhv1116kb.dll": "sha256-kEAfhSpocd0AdwocZnMU2+v0W2y6HJV7TRiOB6FbmDY=",
284
+ "MindExecution.Plugins.PlanMaster.hn5sfx4z2l.dll": "sha256-Y2F3UYa2JgW9pCxL/YlCHo6gUohqHjQ4Z59cL/fdDBU=",
285
+ "MindExecution.Shared.osv9nbh3ym.dll": "sha256-yKMhEPXMN1XCDYyFMEbhio9mvn7j2JkPK/upMkWEfOE=",
286
+ "MindExecution.Web.cdu22y82zq.dll": "sha256-wO+wguSU+8uumbvn/I2fjxrD0N3+xXvJZZWTmE2EjKg="
287
287
  },
288
288
  "lazyAssembly": {
289
- "MindExecution.Plugins.Admin.peei1t6ncj.dll": "sha256-cW6VWE09PF8oNMZ07w2mgGKAKt2tTWuGBvEvhG8ReFI=",
290
- "MindExecution.Plugins.Business.04t0oe4t4k.dll": "sha256-YCPKCRRQ/Mhy8GKyovbWLGa1RV+RAdsX9ZqvTBztSpg=",
291
- "MindExecution.Plugins.Directory.az6m63vnll.dll": "sha256-KtGyNwTSSCOz2ArDDURYQ9FZABwjPuD9Cf47ck0KH10=",
292
- "MindExecution.Plugins.YouTube.xnrrltoofq.dll": "sha256-zQ2pUfGZpcRDKDvxrdKZ8YMSVts3fH2+Lz5j6HaUrTE="
289
+ "MindExecution.Plugins.Admin.7kemovesa7.dll": "sha256-N8cYgaOErldVhLpnvnh4R9kf7f7fmt+16KhBWlnfG1I=",
290
+ "MindExecution.Plugins.Business.t370in2g1b.dll": "sha256-B66uDzMZQfzN3fDnRSM1TvWh0uZjD6AVfUvJAyuGMdI=",
291
+ "MindExecution.Plugins.Directory.0v80a0hi9m.dll": "sha256-JPn9UkEDTmb2CO4o/CnuG76R4RgQStGYZ6jH5pzblXk=",
292
+ "MindExecution.Plugins.YouTube.c8sc0tlc6o.dll": "sha256-7P2anWE7Z/EXJfm/2CSQ1SWL1qPRtZG+gMwZEeqQaKM="
293
293
  }
294
294
  },
295
295
  "cacheBootResources": true,
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "8tpj6Cb7",
2
+ "version": "Gwzavwgq",
3
3
  "assets": [
4
4
  {
5
5
  "hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
@@ -410,44 +410,44 @@
410
410
  "url": "_framework/MimeMapping.og9ys58ylm.dll"
411
411
  },
412
412
  {
413
- "hash": "sha256-CjA5yl5JfpYf6+7TuOwW4XZuBybv5Y6g5sAYXgoaq2M=",
414
- "url": "_framework/MindExecution.Core.e31e775w23.dll"
413
+ "hash": "sha256-JtBC04XSxkMOl0cZOA2ZWvyV88yYPyU0nkIbBCF9Jtc=",
414
+ "url": "_framework/MindExecution.Core.eyl4o4qxg8.dll"
415
415
  },
416
416
  {
417
- "hash": "sha256-2RLhrX3JMUc5g/mGCTwFB61ND7dgYYPaFFFPUAakdiU=",
418
- "url": "_framework/MindExecution.Kernel.55q6x6zeoc.dll"
417
+ "hash": "sha256-q59U8/001rTsB1m+pkA4PkI9a9lvf5fijDWKXbN7uQ4=",
418
+ "url": "_framework/MindExecution.Kernel.hxyapaztgr.dll"
419
419
  },
420
420
  {
421
- "hash": "sha256-cW6VWE09PF8oNMZ07w2mgGKAKt2tTWuGBvEvhG8ReFI=",
422
- "url": "_framework/MindExecution.Plugins.Admin.peei1t6ncj.dll"
421
+ "hash": "sha256-N8cYgaOErldVhLpnvnh4R9kf7f7fmt+16KhBWlnfG1I=",
422
+ "url": "_framework/MindExecution.Plugins.Admin.7kemovesa7.dll"
423
423
  },
424
424
  {
425
- "hash": "sha256-YCPKCRRQ/Mhy8GKyovbWLGa1RV+RAdsX9ZqvTBztSpg=",
426
- "url": "_framework/MindExecution.Plugins.Business.04t0oe4t4k.dll"
425
+ "hash": "sha256-B66uDzMZQfzN3fDnRSM1TvWh0uZjD6AVfUvJAyuGMdI=",
426
+ "url": "_framework/MindExecution.Plugins.Business.t370in2g1b.dll"
427
427
  },
428
428
  {
429
- "hash": "sha256-33uklvsdPqUhPHgnHxyQLPMNbo22Ztttf6r3foxoq7M=",
430
- "url": "_framework/MindExecution.Plugins.Concept.f4gpqdzqwj.dll"
429
+ "hash": "sha256-kEAfhSpocd0AdwocZnMU2+v0W2y6HJV7TRiOB6FbmDY=",
430
+ "url": "_framework/MindExecution.Plugins.Concept.oqhv1116kb.dll"
431
431
  },
432
432
  {
433
- "hash": "sha256-KtGyNwTSSCOz2ArDDURYQ9FZABwjPuD9Cf47ck0KH10=",
434
- "url": "_framework/MindExecution.Plugins.Directory.az6m63vnll.dll"
433
+ "hash": "sha256-JPn9UkEDTmb2CO4o/CnuG76R4RgQStGYZ6jH5pzblXk=",
434
+ "url": "_framework/MindExecution.Plugins.Directory.0v80a0hi9m.dll"
435
435
  },
436
436
  {
437
- "hash": "sha256-kEtfwKXd/IeopHD65TdAZ+8RpdLvnRFJhJ5m2PXozYk=",
438
- "url": "_framework/MindExecution.Plugins.PlanMaster.o2xcmf9i46.dll"
437
+ "hash": "sha256-Y2F3UYa2JgW9pCxL/YlCHo6gUohqHjQ4Z59cL/fdDBU=",
438
+ "url": "_framework/MindExecution.Plugins.PlanMaster.hn5sfx4z2l.dll"
439
439
  },
440
440
  {
441
- "hash": "sha256-zQ2pUfGZpcRDKDvxrdKZ8YMSVts3fH2+Lz5j6HaUrTE=",
442
- "url": "_framework/MindExecution.Plugins.YouTube.xnrrltoofq.dll"
441
+ "hash": "sha256-7P2anWE7Z/EXJfm/2CSQ1SWL1qPRtZG+gMwZEeqQaKM=",
442
+ "url": "_framework/MindExecution.Plugins.YouTube.c8sc0tlc6o.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-jrYtMJjsdd4gl1VIp/WvUr1j6xPzFcb6KsRP8gVxz9I=",
446
- "url": "_framework/MindExecution.Shared.6qq547u3iu.dll"
445
+ "hash": "sha256-yKMhEPXMN1XCDYyFMEbhio9mvn7j2JkPK/upMkWEfOE=",
446
+ "url": "_framework/MindExecution.Shared.osv9nbh3ym.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-neaU6eYk85SeylghFpeBWqjRvKdscMU7Rfx2aPgn9N4=",
450
- "url": "_framework/MindExecution.Web.nfbmmsf2rl.dll"
449
+ "hash": "sha256-wO+wguSU+8uumbvn/I2fjxrD0N3+xXvJZZWTmE2EjKg=",
450
+ "url": "_framework/MindExecution.Web.cdu22y82zq.dll"
451
451
  },
452
452
  {
453
453
  "hash": "sha256-IsZJ91/OW+fHzNqIgEc7Y072ns8z9dGritiSyvR9Wgc=",
@@ -770,7 +770,7 @@
770
770
  "url": "_framework/Websocket.Client.vapounvmnl.dll"
771
771
  },
772
772
  {
773
- "hash": "sha256-xRkkZYryfd2M9u7R9q8FWfp9HwacRvKIdqvSNyvTEik=",
773
+ "hash": "sha256-IDuQS33M39C51AcKlK3xYrMwyKOYKfuVq1o1JIS4wkw=",
774
774
  "url": "_framework/blazor.boot.json"
775
775
  },
776
776
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: 8tpj6Cb7 */
1
+ /* Manifest version: Gwzavwgq */
2
2
  // Hosted deployments should prefer the network over stale offline caches.
3
3
  // This service worker immediately clears old Blazor offline caches and unregisters itself.
4
4