@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/package.json +1 -1
- package/remote-hub.js +17 -5
- package/scripts/remote-agent-managed-smoke.mjs +28 -4
- package/scripts/remote-hub-smoke.mjs +1 -1
- package/scripts/remote-registry-follower-smoke.mjs +33 -1
- package/server.js +331 -17
- package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +15 -3
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +24 -21
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +107 -12
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +32 -5
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +11 -5
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-overlay-v2.js +61 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +6 -1
- package/wwwroot/service-worker-assets.js +8 -8
- package/wwwroot/service-worker.js +1 -1
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
|
|
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
|
-
|
|
2123
|
-
|
|
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 =
|
|
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
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
520
|
-
|
|
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:
|
|
680
|
-
enablePassiveOverlayForVisibleNodes:
|
|
681
|
-
enableOverlayCssZoom:
|
|
682
|
-
enableSharedNodeViewOverlay:
|
|
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 =
|
|
1374
|
-
|
|
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
|
-
:
|
|
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 =
|
|
3085
|
-
this.useDomTextOverlay =
|
|
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
|
|
6022
|
+
const hasActualTextOverlay = !!selectableOverlayNodeId || !!editingOverlayNodeId;
|
|
6023
|
+
const hasEditingOverlay = !!editingOverlayNodeId || this.isEditingNote === true;
|
|
6020
6024
|
const shouldTrackTextOverlayDirty =
|
|
6021
6025
|
isPassiveDomOverlayRuntimeEnabled === true ||
|
|
6022
|
-
|
|
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) {
|