@mindexec/cli 0.2.395 → 0.2.397

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.
package/server.js CHANGED
@@ -42,6 +42,8 @@ const BRIDGE_INSTANCE_ID = String(process.env.MINDEXEC_BRIDGE_INSTANCE_ID || REM
42
42
  const REMOTE_HUB_PAIR_TOKEN = String(process.env.REMOTE_HUB_PAIR_TOKEN || process.env.MINDEXEC_REMOTE_PAIR_TOKEN || REMOTE_HUB_IDENTITY.pairToken || crypto.randomBytes(18).toString('hex')).trim() || crypto.randomBytes(18).toString('hex');
43
43
  const TREE_SITTER_GRAMMAR_DIR = path.join(BRIDGE_ROOT, 'tree-sitter-grammars');
44
44
  const VERBOSE_CODEX_TRACE = /^(1|true|yes|on)$/i.test(String(process.env.BRIDGE_VERBOSE_CODEX || ''));
45
+ const VERBOSE_REMOTE_HTTP_LOGS = /^(1|true|yes|on)$/i.test(String(process.env.MINDEXEC_VERBOSE_REMOTE_HTTP || process.env.BRIDGE_VERBOSE_REMOTE_HTTP || ''));
46
+ const VERBOSE_REMOTE_AGENT_LOGS = /^(1|true|yes|on)$/i.test(String(process.env.MINDEXEC_VERBOSE_REMOTE_AGENT || process.env.BRIDGE_VERBOSE_REMOTE_AGENT || ''));
45
47
  const COLOR_LOGS_ENABLED = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
46
48
  const DEFAULT_WEB_APP_ROOT = path.join(BRIDGE_ROOT, 'wwwroot');
47
49
 
@@ -331,7 +333,7 @@ function logSection(title, lines = []) {
331
333
  }
332
334
  }
333
335
 
334
- function logHttpRequest(method, requestPath) {
336
+ function logHttpRequest(method, requestPath) {
335
337
  let methodTone = 'bridge';
336
338
  switch (String(method || '').toUpperCase()) {
337
339
  case 'POST':
@@ -353,10 +355,32 @@ function logHttpRequest(method, requestPath) {
353
355
  }
354
356
 
355
357
  const methodLabel = tone(String(method || 'REQ').padEnd(6, ' '), methodTone);
356
- console.log(`${formatLogTime()} ${formatScope('http', 'muted')} ${methodLabel} ${requestPath}`);
357
- }
358
-
359
- function normalizeWorkspaceStylePath(inputPath) {
358
+ console.log(`${formatLogTime()} ${formatScope('http', 'muted')} ${methodLabel} ${requestPath}`);
359
+ }
360
+
361
+ function shouldLogHttpRequest(method, requestPath) {
362
+ if (VERBOSE_REMOTE_HTTP_LOGS) {
363
+ return true;
364
+ }
365
+
366
+ const normalizedPath = String(requestPath || '').toLowerCase();
367
+ if (normalizedPath === '/api/remote/status'
368
+ || normalizedPath === '/api/remote/devices'
369
+ || normalizedPath === '/api/remote/frames'
370
+ || normalizedPath === '/api/remote/agent/status'
371
+ || normalizedPath === '/api/remote/agent/connect'
372
+ || normalizedPath === '/api/remote/agent/sync-report') {
373
+ return false;
374
+ }
375
+
376
+ if (!normalizedPath.startsWith('/api/remote/devices/')) {
377
+ return true;
378
+ }
379
+
380
+ return !(/\/(thumbnail|thumbnail\/request|live\/frame|live\/start|live\/stop)$/i.test(normalizedPath));
381
+ }
382
+
383
+ function normalizeWorkspaceStylePath(inputPath) {
360
384
  return String(inputPath || '').replaceAll('\\', '/').trim();
361
385
  }
362
386
 
@@ -2117,11 +2141,13 @@ app.use('/assets/thumbs', async (req, res) => {
2117
2141
  });
2118
2142
  });
2119
2143
 
2120
- // Logging middleware
2121
- app.use((req, res, next) => {
2122
- logHttpRequest(req.method, req.path);
2123
- next();
2124
- });
2144
+ // Logging middleware
2145
+ app.use((req, res, next) => {
2146
+ if (shouldLogHttpRequest(req.method, req.path)) {
2147
+ logHttpRequest(req.method, req.path);
2148
+ }
2149
+ next();
2150
+ });
2125
2151
 
2126
2152
  const PROTECTED_BRIDGE_ROUTES = [
2127
2153
  { method: 'POST', exact: '/api/workspace/browse' },
@@ -3633,9 +3659,15 @@ const REMOTE_AGENT_OUTPUT_RECONNECT_GRACE_MS = Math.max(
3633
3659
  Number(process.env.MINDEXEC_REMOTE_AGENT_OUTPUT_RECONNECT_GRACE_MS || 6000) || 6000);
3634
3660
  const REMOTE_AGENT_DEFAULT_ENGINE = 'auto';
3635
3661
  const REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS = 30000;
3662
+ const REMOTE_AGENT_DUPLICATE_ACTIVE_LOG_REPEAT_MS = 60000;
3663
+ const REMOTE_AGENT_SOFT_SYNC_REPORT_LOG_REPEAT_MS = Math.max(
3664
+ REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS,
3665
+ Number(process.env.MINDEXEC_REMOTE_AGENT_SOFT_SYNC_REPORT_LOG_MS || 300000) || 300000);
3636
3666
  const REMOTE_AGENT_SYNC_REPORT_WAKE_MS = Math.max(500, Number(process.env.MINDEXEC_REMOTE_AGENT_SYNC_REPORT_WAKE_MS || 1500) || 1500);
3637
3667
  const REMOTE_AGENT_RACE_START_STAGGER_MS = Math.max(0, Number(process.env.MINDEXEC_REMOTE_AGENT_RACE_STAGGER_MS ?? 0) || 0);
3638
- const REMOTE_AGENT_MAX_PARALLEL_CANDIDATES = 6;
3668
+ const REMOTE_AGENT_MAX_PARALLEL_CANDIDATES = Math.max(
3669
+ 1,
3670
+ Math.min(6, Number(process.env.MINDEXEC_REMOTE_AGENT_MAX_PARALLEL_CANDIDATES || 1) || 1));
3639
3671
  const REMOTE_AGENT_RECENT_MANAGER_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
3640
3672
  const REMOTE_AGENT_RECENT_MANAGER_CACHE_LIMIT = 64;
3641
3673
  const REMOTE_AGENT_RECENT_MANAGER_DEFAULT_KEY = 'default';
@@ -3678,6 +3710,8 @@ let remoteAgentSyncReportState = null;
3678
3710
  let remoteAgentSyncReportLogKey = '';
3679
3711
  let remoteAgentSyncReportLogAt = 0;
3680
3712
  let remoteAgentSyncReportWakeAt = 0;
3713
+ let remoteAgentConnectFailureLogKey = '';
3714
+ let remoteAgentConnectFailureLogAt = 0;
3681
3715
  let remoteAgentConnectPromise = null;
3682
3716
  const remoteAgentIntentionalStopKeys = new Set();
3683
3717
  const remoteAgentRecentSuccessfulManagers = new Map();
@@ -3844,6 +3878,66 @@ function createRemoteAgentSyncReport(body = {}) {
3844
3878
  };
3845
3879
  }
3846
3880
 
3881
+ function isRemoteAgentDuplicateActiveText(value) {
3882
+ return /duplicate-device-active/i.test(String(value || ''));
3883
+ }
3884
+
3885
+ function isRemoteAgentDuplicateActiveReport(report) {
3886
+ return !!report
3887
+ && (isRemoteAgentDuplicateActiveText(report.reason)
3888
+ || isRemoteAgentDuplicateActiveText(report.error));
3889
+ }
3890
+
3891
+ function isRemoteAgentSoftSyncReportReason(reason) {
3892
+ return /^(local-monitor-is-host|local-monitor-host-revived|local-monitor-host-republished|local-monitor-host-republish-[\w-]+|same-host|local-host-superseded)$/i
3893
+ .test(String(reason || '').trim());
3894
+ }
3895
+
3896
+ function isRemoteAgentDuplicateActiveState(state = remoteAgentState) {
3897
+ return isRemoteAgentDuplicateActiveText(state?.lastError)
3898
+ || isRemoteAgentDuplicateActiveText(state?.reconnectReason)
3899
+ || isRemoteAgentDuplicateActiveText(state?.stderrTail)
3900
+ || isRemoteAgentDuplicateActiveText(state?.stdoutTail);
3901
+ }
3902
+
3903
+ function findRemoteAgentDuplicateActiveAttempt(attempts = []) {
3904
+ return attempts.find(attempt =>
3905
+ attempt?.child
3906
+ && attempt.child.exitCode === null
3907
+ && !attempt.child.killed
3908
+ && isRemoteAgentDuplicateActiveState(attempt.state));
3909
+ }
3910
+
3911
+ function logRemoteAgentConnectFailure(manager, error = 'unknown') {
3912
+ const duplicateActive = isRemoteAgentDuplicateActiveText(error);
3913
+ const safeManager = normalizeRemoteManagerEndpoint(manager) || 'invalid';
3914
+ const safeError = duplicateActive
3915
+ ? 'duplicate-device-active'
3916
+ : safeRemoteAgentField(error || 'unknown', 700);
3917
+ const key = `${duplicateActive ? 'duplicate-active' : 'connect-failed'}|${safeManager}|${safeError}`;
3918
+ const intervalMs = duplicateActive
3919
+ ? REMOTE_AGENT_DUPLICATE_ACTIVE_LOG_REPEAT_MS
3920
+ : REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS;
3921
+ const now = Date.now();
3922
+ if (key === remoteAgentConnectFailureLogKey
3923
+ && now - remoteAgentConnectFailureLogAt < intervalMs) {
3924
+ return;
3925
+ }
3926
+
3927
+ remoteAgentConnectFailureLogKey = key;
3928
+ remoteAgentConnectFailureLogAt = now;
3929
+
3930
+ if (duplicateActive) {
3931
+ logEvent(
3932
+ 'remote',
3933
+ `managed RemoteAgent duplicate active; keeping existing session ${formatKeyValue('manager', safeManager)}`,
3934
+ 'remote');
3935
+ return;
3936
+ }
3937
+
3938
+ logWarn('remote', `managed RemoteAgent connect failed ${formatKeyValue('manager', safeManager)} ${formatKeyValue('error', safeError)}`);
3939
+ }
3940
+
3847
3941
  function isRemoteAgentSyncReportReconnectSignal(report) {
3848
3942
  if (!report || report.connected === true) {
3849
3943
  return false;
@@ -3856,6 +3950,10 @@ function isRemoteAgentSyncReportReconnectSignal(report) {
3856
3950
  return false;
3857
3951
  }
3858
3952
 
3953
+ if (isRemoteAgentDuplicateActiveText(text)) {
3954
+ return false;
3955
+ }
3956
+
3859
3957
  if (/(throttled|registry-not-authenticated|registry-auth-pending|session-expired|no-active-target|registry-inactive|local-monitor-is-host|local-host-superseded|same-host|target-missing-endpoint-or-pair)/i.test(text)) {
3860
3958
  return false;
3861
3959
  }
@@ -3994,6 +4092,10 @@ function isRemoteAgentOutputReconnectSignal(text) {
3994
4092
  return false;
3995
4093
  }
3996
4094
 
4095
+ if (isRemoteAgentDuplicateActiveText(normalized)) {
4096
+ return false;
4097
+ }
4098
+
3997
4099
  if (/connected to remotehub/i.test(normalized) && !/(disconnect|closed|failed|econnreset|etimedout|epipe|socket|websocket|remotehub.*error)/i.test(normalized)) {
3998
4100
  return false;
3999
4101
  }
@@ -4037,6 +4139,21 @@ function rememberRemoteAgentSyncReport(report) {
4037
4139
  }
4038
4140
 
4039
4141
  remoteAgentSyncReportState = report;
4142
+ if (isRemoteAgentDuplicateActiveReport(report)) {
4143
+ const now = Date.now();
4144
+ const key = `duplicate-active|${report.targetEndpoint || ''}|${report.targetLeaseId || ''}`;
4145
+ if (key !== remoteAgentSyncReportLogKey
4146
+ || now - remoteAgentSyncReportLogAt >= REMOTE_AGENT_DUPLICATE_ACTIVE_LOG_REPEAT_MS) {
4147
+ remoteAgentSyncReportLogKey = key;
4148
+ remoteAgentSyncReportLogAt = now;
4149
+ logEvent(
4150
+ 'remote',
4151
+ `registry sync duplicate active ${formatKeyValue('target', report.targetEndpoint || '-')} ${formatKeyValue('auth', report.authenticated ? 'yes' : 'no')} ${formatKeyValue('note', 'existing-session-kept')}`,
4152
+ 'remote');
4153
+ }
4154
+ return true;
4155
+ }
4156
+
4040
4157
  const reason = report.reason || report.error || (report.ok ? 'ok' : 'failed');
4041
4158
  const status = report.connected
4042
4159
  ? 'connected'
@@ -4049,6 +4166,34 @@ function rememberRemoteAgentSyncReport(report) {
4049
4166
  : report.ok
4050
4167
  ? 'ok'
4051
4168
  : 'failed';
4169
+
4170
+ if (isRemoteAgentSoftSyncReportReason(reason)) {
4171
+ const key = [
4172
+ 'soft',
4173
+ status,
4174
+ reason,
4175
+ report.targetEndpoint || '',
4176
+ report.targetLeaseId || '',
4177
+ report.localHostTargetActive ? 'local-host' : ''
4178
+ ].join('|');
4179
+ const now = Date.now();
4180
+ const repeatMs = VERBOSE_REMOTE_AGENT_LOGS
4181
+ ? REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS
4182
+ : REMOTE_AGENT_SOFT_SYNC_REPORT_LOG_REPEAT_MS;
4183
+ if (key === remoteAgentSyncReportLogKey
4184
+ && now - remoteAgentSyncReportLogAt < repeatMs) {
4185
+ return true;
4186
+ }
4187
+
4188
+ remoteAgentSyncReportLogKey = key;
4189
+ remoteAgentSyncReportLogAt = now;
4190
+ logEvent(
4191
+ 'remote',
4192
+ `registry sync ${status} ${formatKeyValue('reason', reason)} ${formatKeyValue('target', report.targetEndpoint || '-')} ${formatKeyValue('auth', report.authenticated ? 'yes' : 'no')}`,
4193
+ 'remote');
4194
+ return true;
4195
+ }
4196
+
4052
4197
  markRemoteAgentReconnectNeededFromReport(report);
4053
4198
  const key = [
4054
4199
  status,
@@ -4089,6 +4234,14 @@ function appendRemoteAgentOutput(stream, chunk, stateConnectionKey = '') {
4089
4234
  remoteAgentState.needsReconnect = false;
4090
4235
  remoteAgentState.reconnectReason = '';
4091
4236
  remoteAgentState.reconnectRequestedAt = '';
4237
+ remoteAgentState.lastError = '';
4238
+ }
4239
+ if (isRemoteAgentDuplicateActiveText(text)) {
4240
+ remoteAgentState.ready = false;
4241
+ remoteAgentState.needsReconnect = false;
4242
+ remoteAgentState.reconnectReason = '';
4243
+ remoteAgentState.reconnectRequestedAt = '';
4244
+ remoteAgentState.lastError = 'duplicate-device-active';
4092
4245
  }
4093
4246
  if (isRemoteAgentOutputReconnectSignal(text)) {
4094
4247
  markRemoteAgentReconnectNeeded('process-output-disconnected', 'agent-process-output-disconnected');
@@ -4434,6 +4587,7 @@ function getRemoteAgentRunningAgeMs(agent = remoteAgentState) {
4434
4587
  function isRemoteAgentRunningButNotReadyStale() {
4435
4588
  return isRemoteAgentRegistryOwned()
4436
4589
  && remoteAgentState.ready !== true
4590
+ && !isRemoteAgentDuplicateActiveState(remoteAgentState)
4437
4591
  && getRemoteAgentRunningAgeMs(remoteAgentState) >= REMOTE_AGENT_READY_STALE_MS;
4438
4592
  }
4439
4593
 
@@ -4945,6 +5099,15 @@ function appendRemoteAgentAttemptOutput(attempt, stream, chunk) {
4945
5099
  attempt.state.connectedAt = new Date().toISOString();
4946
5100
  attempt.state.needsReconnect = false;
4947
5101
  attempt.state.reconnectReason = '';
5102
+ attempt.state.reconnectRequestedAt = '';
5103
+ attempt.state.lastError = '';
5104
+ }
5105
+ if (isRemoteAgentDuplicateActiveText(text)) {
5106
+ attempt.state.ready = false;
5107
+ attempt.state.needsReconnect = false;
5108
+ attempt.state.reconnectReason = '';
5109
+ attempt.state.reconnectRequestedAt = '';
5110
+ attempt.state.lastError = 'duplicate-device-active';
4948
5111
  }
4949
5112
  if (isRemoteAgentOutputReconnectSignal(text)) {
4950
5113
  attempt.state.ready = false;
@@ -5149,10 +5312,12 @@ async function startRemoteAgentConnectionRace(options = {}) {
5149
5312
  const attempts = [];
5150
5313
  for (let index = 0; index < managers.length; index += 1) {
5151
5314
  const manager = managers[index];
5152
- logEvent(
5153
- 'remote',
5154
- `managed RemoteAgent race ${index + 1}/${managers.length} ${formatKeyValue('manager', manager)}`,
5155
- 'remote');
5315
+ if (VERBOSE_REMOTE_AGENT_LOGS || managers.length > 1) {
5316
+ logEvent(
5317
+ 'remote',
5318
+ `managed RemoteAgent race ${index + 1}/${managers.length} ${formatKeyValue('manager', manager)}`,
5319
+ 'remote');
5320
+ }
5156
5321
 
5157
5322
  const attempt = createRemoteAgentAttempt({
5158
5323
  manager,
@@ -5217,6 +5382,33 @@ async function startRemoteAgentConnectionRace(options = {}) {
5217
5382
  return { ok: true, alreadyRunning: false, agent: serializeRemoteAgentState() };
5218
5383
  }
5219
5384
 
5385
+ const duplicateActiveAttempt = findRemoteAgentDuplicateActiveAttempt(attempts);
5386
+ if (duplicateActiveAttempt) {
5387
+ for (const attempt of attempts) {
5388
+ if (attempt !== duplicateActiveAttempt) {
5389
+ await stopRemoteAgentAttempt(attempt, 'race-lost-duplicate-active');
5390
+ }
5391
+ }
5392
+
5393
+ remoteAgentState = duplicateActiveAttempt.state;
5394
+ remoteAgentState.status = 'running';
5395
+ remoteAgentState.proc = duplicateActiveAttempt.child;
5396
+ remoteAgentState.ready = false;
5397
+ remoteAgentState.needsReconnect = false;
5398
+ remoteAgentState.reconnectReason = '';
5399
+ remoteAgentState.reconnectRequestedAt = '';
5400
+ remoteAgentState.lastError = 'duplicate-device-active';
5401
+ remoteAgentState.updatedAt = new Date().toISOString();
5402
+ emitBridgeEvent('RemoteAgentWaiting', serializeRemoteAgentState());
5403
+ logRemoteAgentConnectFailure(duplicateActiveAttempt.manager, 'duplicate-device-active');
5404
+ return {
5405
+ ok: true,
5406
+ waiting: true,
5407
+ reason: 'duplicate-device-active',
5408
+ agent: serializeRemoteAgentState()
5409
+ };
5410
+ }
5411
+
5220
5412
  const lastAttempt = attempts.findLast?.(attempt => attempt?.error || attempt?.state?.lastError)
5221
5413
  || attempts[attempts.length - 1]
5222
5414
  || null;
@@ -6326,6 +6518,14 @@ function isRemoteRegistryTargetEndpointLocal(localHub, target) {
6326
6518
  return targetEndpoints.some(endpoint => localSet.has(endpoint.toLowerCase()));
6327
6519
  }
6328
6520
 
6521
+ function shouldRepublishLocalHostForRegistryTarget(localHub, target) {
6522
+ return localHub?.hostTargetActive === true
6523
+ && target?.active === true
6524
+ && !isRemoteRegistryTargetExpired(target)
6525
+ && !isRemoteRegistryTargetSameAsLocalHost(localHub, target)
6526
+ && isRemoteRegistryTargetEndpointLocal(localHub, target);
6527
+ }
6528
+
6329
6529
  async function fetchRemoteRegistryTarget(config, session) {
6330
6530
  const url = new URL('/rest/v1/remote_host_targets', config.url);
6331
6531
  url.searchParams.set('select', '*');
@@ -6399,6 +6599,32 @@ async function readRemoteRegistryContext() {
6399
6599
  return { ok: true, config, session };
6400
6600
  }
6401
6601
 
6602
+ async function readCurrentRemoteRegistryTarget() {
6603
+ const context = await readRemoteRegistryContext();
6604
+ if (!context.ok) {
6605
+ return {
6606
+ ok: false,
6607
+ reason: context.reason,
6608
+ target: null
6609
+ };
6610
+ }
6611
+
6612
+ try {
6613
+ return {
6614
+ ok: true,
6615
+ reason: 'ok',
6616
+ target: await fetchRemoteRegistryTarget(context.config, context.session)
6617
+ };
6618
+ } catch (error) {
6619
+ return {
6620
+ ok: false,
6621
+ reason: getRemoteHostTargetRegistryErrorReason(error),
6622
+ error: error?.message || String(error || 'registry-fetch-failed'),
6623
+ target: null
6624
+ };
6625
+ }
6626
+ }
6627
+
6402
6628
  function getRemoteHostTargetEndpointReason(endpoint) {
6403
6629
  const normalized = normalizeRemoteManagerEndpoint(endpoint);
6404
6630
  if (!normalized) {
@@ -6888,6 +7114,37 @@ async function runRemoteHostTargetRenewOnce(trigger = 'timer', options = {}) {
6888
7114
  });
6889
7115
 
6890
7116
  if (registry.stale) {
7117
+ const registryTarget = await readCurrentRemoteRegistryTarget();
7118
+ if (shouldRepublishLocalHostForRegistryTarget(hub, registryTarget.target)) {
7119
+ const takeoverRegistry = await publishLocalRemoteHostTargetToRegistry(hub, { takeover: true });
7120
+ const reason = takeoverRegistry.ok
7121
+ ? 'local-monitor-host-republished'
7122
+ : `local-monitor-host-republish-${takeoverRegistry.reason || 'registry-publish-failed'}`;
7123
+ updateRemoteHostTargetRenewState({
7124
+ status: takeoverRegistry.ok ? 'active' : 'skipped',
7125
+ reason,
7126
+ nodeId: hub.hostTargetNodeId,
7127
+ leaseId: hub.hostTargetLeaseId,
7128
+ hostInstanceId: hub.hostTargetHostInstanceId || hub.hostInstanceId,
7129
+ endpoint: takeoverRegistry.endpoint || hub.hostTargetEndpoint,
7130
+ endpointCandidates: takeoverRegistry.endpointCandidates || hub.hostTargetEndpointCandidates || [],
7131
+ expiresAt: hub.hostTargetExpiresAt,
7132
+ lastAttemptAt: attemptedAt,
7133
+ lastSuccessAt: takeoverRegistry.ok ? new Date().toISOString() : remoteHostTargetRenewState.lastSuccessAt,
7134
+ lastError: takeoverRegistry.ok || isRemoteHostTargetRenewSoftSkipReason(takeoverRegistry.reason)
7135
+ ? ''
7136
+ : (takeoverRegistry.reason || 'registry-publish-failed')
7137
+ });
7138
+ logRemoteHostTargetRenew(takeoverRegistry.ok ? 'ok' : 'skipped', reason, takeoverRegistry.endpoint || hub.hostTargetEndpoint);
7139
+ scheduleRemoteHostTargetRenew(
7140
+ takeoverRegistry.ok || isRemoteHostTargetRenewSoftSkipReason(takeoverRegistry.reason)
7141
+ ? REMOTE_HOST_TARGET_RENEW_MS
7142
+ : REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS,
7143
+ takeoverRegistry.ok ? 'local-host-republished' : 'local-host-republish-retry');
7144
+ wakeRemoteRegistryFollower('local-host-republished').catch(() => {});
7145
+ return serializeRemoteHostTargetRenewState();
7146
+ }
7147
+
6891
7148
  remoteHub.setHostTarget({
6892
7149
  enabled: false,
6893
7150
  nodeId: hub.hostTargetNodeId
@@ -7019,6 +7276,14 @@ function scheduleRemoteRegistryWakeFromSyncReport(report) {
7019
7276
  };
7020
7277
  }
7021
7278
 
7279
+ if (isRemoteAgentDuplicateActiveReport(report)) {
7280
+ return {
7281
+ follower: false,
7282
+ hostRenew: false,
7283
+ reason: 'duplicate-device-active'
7284
+ };
7285
+ }
7286
+
7022
7287
  const now = Date.now();
7023
7288
  let follower = false;
7024
7289
  if (now - remoteAgentSyncReportWakeAt >= REMOTE_AGENT_SYNC_REPORT_WAKE_MS) {
@@ -7206,6 +7471,55 @@ async function runRemoteRegistryFollowerOnce(trigger = 'timer') {
7206
7471
 
7207
7472
  if (localHub?.hostTargetActive === true) {
7208
7473
  resetRemoteRegistryInactiveTargetGrace();
7474
+ if (shouldRepublishLocalHostForRegistryTarget(localHub, target)) {
7475
+ const renewed = remoteHub.setHostTarget({
7476
+ enabled: true,
7477
+ nodeId: localHub.hostTargetNodeId || target.nodeId,
7478
+ leaseMs: REMOTE_HOST_TARGET_LEASE_MS
7479
+ });
7480
+ const republishedHub = remoteHub.getStatus({ includeSecrets: true });
7481
+ const registry = renewed?.ok === true
7482
+ ? await publishLocalRemoteHostTargetToRegistry(republishedHub, { takeover: true })
7483
+ : {
7484
+ ok: false,
7485
+ reason: renewed?.error || 'local-host-renew-failed',
7486
+ endpoint: target.endpoint,
7487
+ endpointCandidates: target.endpointCandidates
7488
+ };
7489
+ const reason = registry.ok
7490
+ ? 'local-monitor-host-republished'
7491
+ : `local-monitor-host-republish-${registry.reason || 'registry-publish-failed'}`;
7492
+ updateRemoteRegistryFollowerState({
7493
+ status: 'skipped',
7494
+ reason,
7495
+ authenticated: true,
7496
+ lastAttemptAt: attemptedAt,
7497
+ lastSuccessAt: attemptedAt,
7498
+ targetEndpoint: registry.endpoint || target.endpoint,
7499
+ targetEndpointCandidates: registry.endpointCandidates?.length ? registry.endpointCandidates : target.endpointCandidates,
7500
+ targetLeaseId: republishedHub.hostTargetLeaseId || localHub.hostTargetLeaseId,
7501
+ targetNodeId: republishedHub.hostTargetNodeId || localHub.hostTargetNodeId,
7502
+ lastError: registry.ok || isRemoteHostTargetRenewSoftSkipReason(registry.reason) ? '' : (registry.reason || 'host-target-republish-failed')
7503
+ });
7504
+ await reportRemoteRegistryFollowerSync({
7505
+ ok: registry.ok !== false,
7506
+ skipped: true,
7507
+ reason,
7508
+ trigger,
7509
+ authenticated: true,
7510
+ localHostTargetActive: true,
7511
+ targetActive: true,
7512
+ targetEndpoint: registry.endpoint || target.endpoint,
7513
+ targetEndpointCandidates: registry.endpointCandidates?.length ? registry.endpointCandidates : target.endpointCandidates,
7514
+ targetLeaseId: republishedHub.hostTargetLeaseId || localHub.hostTargetLeaseId,
7515
+ targetNodeId: republishedHub.hostTargetNodeId || localHub.hostTargetNodeId,
7516
+ targetExpiresAt: republishedHub.hostTargetExpiresAt || localHub.hostTargetExpiresAt
7517
+ });
7518
+ scheduleRemoteHostTargetRenewWake('local-host-republished', { takeover: true });
7519
+ scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'local-host-republished');
7520
+ return serializeRemoteRegistryFollowerState();
7521
+ }
7522
+
7209
7523
  if (target?.active === true
7210
7524
  && !isRemoteRegistryTargetExpired(target)
7211
7525
  && !isRemoteRegistryTargetSameAsLocalHost(localHub, target)) {
@@ -7407,7 +7721,7 @@ async function runRemoteRegistryFollowerOnce(trigger = 'timer') {
7407
7721
  }
7408
7722
 
7409
7723
  const connect = await startRemoteAgentConnection({
7410
- manager: target.endpointCandidates[0],
7724
+ manager: target.endpoint,
7411
7725
  managerCandidates: target.endpointCandidates,
7412
7726
  pairToken: target.pairToken,
7413
7727
  leaseId: target.leaseId,
@@ -12178,7 +12492,7 @@ app.post('/api/remote/agent/connect', async (req, res) => {
12178
12492
  source: req.body?.source || 'registry'
12179
12493
  });
12180
12494
  if (!result.ok) {
12181
- logWarn('remote', `managed RemoteAgent connect failed ${formatKeyValue('manager', requestedManager || 'invalid')} ${formatKeyValue('error', result.error || 'unknown')}`);
12495
+ logRemoteAgentConnectFailure(requestedManager || 'invalid', result.error || 'unknown');
12182
12496
  }
12183
12497
  res.status(result.ok ? 200 : 400).json(result);
12184
12498
  } catch (err) {
@@ -2600,11 +2600,19 @@ html body .css3d-resolution-wrapper.lod-low.node-type-csv-table .csv-table td {
2600
2600
 
2601
2601
  .css3d-resolution-wrapper.node-type-templatelauncher > .map-node-template-card {
2602
2602
  border: 1px solid #111827 !important;
2603
- border-radius: 8px !important;
2603
+ border-radius: 0 !important;
2604
2604
  background: #f8fafc;
2605
2605
  box-shadow: none !important;
2606
2606
  }
2607
2607
 
2608
+ .css3d-resolution-wrapper.node-type-templatelauncher > .map-node-template-card.map-node-remote-fleet {
2609
+ border: 0 !important;
2610
+ }
2611
+
2612
+ .css3d-resolution-wrapper.node-type-templatelauncher.selected > .map-node-template-card.map-node-remote-fleet {
2613
+ border: 2px solid var(--node-selection-edge, #2563eb) !important;
2614
+ }
2615
+
2608
2616
  /* Template launcher and Multi Desktop Monitor own their visual chrome on the
2609
2617
  card itself. Keep the CSS3D wrapper neutral so motion CSS3D and the passive
2610
2618
  DOM overlay do not stack different selection chrome. */
@@ -2627,12 +2635,16 @@ html body .css3d-resolution-wrapper.lod-low.node-type-csv-table .csv-table td {
2627
2635
 
2628
2636
  .map-node-template-card {
2629
2637
  border: 1px solid #111827 !important;
2630
- border-radius: 8px;
2638
+ border-radius: 0;
2631
2639
  color: #10212f;
2632
2640
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
2633
2641
  box-shadow: none !important;
2634
2642
  }
2635
2643
 
2644
+ .map-node-template-card.map-node-remote-fleet {
2645
+ border: 0 !important;
2646
+ }
2647
+
2636
2648
  .template-card__shell {
2637
2649
  display: flex;
2638
2650
  flex-direction: column;
@@ -2641,7 +2653,7 @@ html body .css3d-resolution-wrapper.lod-low.node-type-csv-table .csv-table td {
2641
2653
  gap: 12px;
2642
2654
  padding: 18px;
2643
2655
  border: 0;
2644
- border-radius: 8px;
2656
+ border-radius: 0;
2645
2657
  background: #f8fafc;
2646
2658
  overflow: hidden;
2647
2659
  }
@@ -77,7 +77,7 @@
77
77
  });
78
78
  const CanvasPhaseSet = new Set(Object.values(CanvasPhase));
79
79
  const VIEWPORT_METRICS_POLL_INTERVAL = 480;
80
- const PASSIVE_DOM_OVERLAY_RUNTIME_ENABLED = true;
80
+ const PASSIVE_DOM_OVERLAY_RUNTIME_ENABLED = false;
81
81
  const PASSIVE_OVERLAY_ZOOM_DEFER_VISIBLE_THRESHOLD = 24;
82
82
  const PASSIVE_OVERLAY_INTERACTIVE_REFRESH_INTERVAL_MS = 48;
83
83
  const PASSIVE_OVERLAY_INTERACTIVE_DENSE_REFRESH_INTERVAL_MS = 72;
@@ -516,8 +516,11 @@
516
516
  }
517
517
 
518
518
  function hasPassiveOverlayMotionWork(module) {
519
- return PASSIVE_DOM_OVERLAY_RUNTIME_ENABLED === true ||
520
- hasPassiveOverlaySelectionSurface(module) === true;
519
+ if (PASSIVE_DOM_OVERLAY_RUNTIME_ENABLED !== true) {
520
+ return false;
521
+ }
522
+
523
+ return hasPassiveOverlaySelectionSurface(module) === true;
521
524
  }
522
525
 
523
526
  function deferPassiveOverlayForCameraMotion(module, nowMs = 0, delayMs = PASSIVE_OVERLAY_IDLE_DELAY_MS) {
@@ -676,10 +679,10 @@
676
679
  const DEFAULT_RENDER_DEBUG_FLAGS = Object.freeze({
677
680
  enableCss3d: true,
678
681
  enableCss3dTransformSync: true,
679
- enableDomOverlay: true,
680
- enablePassiveOverlayForVisibleNodes: true,
681
- enableOverlayCssZoom: true,
682
- enableSharedNodeViewOverlay: true,
682
+ enableDomOverlay: false,
683
+ enablePassiveOverlayForVisibleNodes: false,
684
+ enableOverlayCssZoom: false,
685
+ enableSharedNodeViewOverlay: false,
683
686
  preferOverlayNodeViewHost: false,
684
687
  enableVisibilityCulling: true,
685
688
  enableLodUpdate: true,
@@ -1067,6 +1070,12 @@
1067
1070
  }
1068
1071
  });
1069
1072
 
1073
+ next.enableDomOverlay = false;
1074
+ next.enablePassiveOverlayForVisibleNodes = false;
1075
+ next.enableOverlayCssZoom = false;
1076
+ next.enableSharedNodeViewOverlay = false;
1077
+ next.preferOverlayNodeViewHost = false;
1078
+
1070
1079
  return next;
1071
1080
  }
1072
1081
 
@@ -1370,16 +1379,10 @@
1370
1379
  const overlayPlacementZoomCount = Number(moduleInstance._overlayDebugPlacementZoomCount || 0);
1371
1380
  const overlayPlacementTransformCount = Number(moduleInstance._overlayDebugPlacementTransformCount || 0);
1372
1381
  const currentLodBand = String(moduleInstance.lodRenderer?._currentLodBand || '').trim().toUpperCase();
1373
- const overlayScaleMode = flags.enableOverlayCssZoom === false
1374
- ? 'transform only'
1375
- : (overlayPlacementZoomCount > 0
1376
- ? 'css zoom + transform fallback'
1377
- : 'transform only (text cards)');
1378
- const overlayCandidateMode = currentLodBand && currentLodBand !== 'NEAR'
1382
+ const overlayScaleMode = 'css3d only';
1383
+ const overlayCandidateMode = currentLodBand
1379
1384
  ? `css3d only (${currentLodBand.toLowerCase()} band)`
1380
- : (flags.enablePassiveOverlayForVisibleNodes !== false
1381
- ? 'all-visible passive'
1382
- : 'selected/editing only');
1385
+ : 'css3d only';
1383
1386
  const shouldBuildResidentDirtySnapshot =
1384
1387
  flags.enableFramePerfProbe === true ||
1385
1388
  flags.enableNodeFrameDebug === true;
@@ -3081,8 +3084,8 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
3081
3084
  this._overlayDirty = true;
3082
3085
  this._lastOverlayKey = '';
3083
3086
  this._lastOverlaySelectionKey = '';
3084
- this.useTextOverlayV2 = true;
3085
- this.useDomTextOverlay = true;
3087
+ this.useTextOverlayV2 = false;
3088
+ this.useDomTextOverlay = false;
3086
3089
  this.renderDebugFlags = getRenderDebugFlagsSnapshot();
3087
3090
  this._framePerfPeakProfile = createEmptyFramePerfPeakProfile();
3088
3091
 
@@ -6016,11 +6019,11 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
6016
6019
  ? String(this._textOverlayV2State?.editing?.nodeId || '').trim()
6017
6020
  : String(this._editingOverlayState?.nodeId || '').trim();
6018
6021
  const hasFocusedTextOverlay = !!selectableOverlayNodeId;
6019
- const hasEditingOverlay = !!editingOverlayNodeId;
6022
+ const hasActualTextOverlay = !!selectableOverlayNodeId || !!editingOverlayNodeId;
6023
+ const hasEditingOverlay = !!editingOverlayNodeId || this.isEditingNote === true;
6020
6024
  const shouldTrackTextOverlayDirty =
6021
6025
  isPassiveDomOverlayRuntimeEnabled === true ||
6022
- hasFocusedTextOverlay === true ||
6023
- hasEditingOverlay === true;
6026
+ hasActualTextOverlay === true;
6024
6027
  const selectedOverlayNodeId = String(this.selectedNodeIdJs || '').trim();
6025
6028
  const overlaySelectionKey = `${selectedOverlayNodeId}|${selectableOverlayNodeId}|${editingOverlayNodeId}|${useTextOverlayV2 ? 'v2' : 'legacy'}`;
6026
6029
  if (overlaySelectionKey !== this._lastOverlaySelectionKey) {