@mindexec/cli 0.2.188 → 0.2.190

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.188",
3
+ "version": "0.2.190",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -61,7 +61,7 @@
61
61
  "chokidar": "^3.6.0",
62
62
  "cors": "^2.8.5",
63
63
  "express": "^4.18.2",
64
- "multer": "^2.1.1",
64
+ "multer": "^2.2.0",
65
65
  "playwright-core": "^1.53.0",
66
66
  "sharp": "^0.33.2",
67
67
  "web-tree-sitter": "^0.22.6",
package/remote-hub.js CHANGED
@@ -875,7 +875,7 @@ export function createRemoteHub(options = {}) {
875
875
  const publicEndpoint = safeString(env.MINDEXEC_REMOTE_PUBLIC_ENDPOINT || env.REMOTE_HUB_PUBLIC_ENDPOINT, 256);
876
876
  const publicHost = safeString(env.MINDEXEC_REMOTE_PUBLIC_HOST || env.REMOTE_HUB_PUBLIC_HOST, 128);
877
877
  const pairToken = safeString(
878
- env.REMOTE_HUB_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(6).toString('hex'),
878
+ options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(6).toString('hex'),
879
879
  256);
880
880
 
881
881
  const devices = new Map();
@@ -1043,7 +1043,9 @@ export function createRemoteHub(options = {}) {
1043
1043
  const routeInfo = getAgentEndpointRouteInfo();
1044
1044
  hostTarget = {
1045
1045
  nodeId,
1046
- leaseId: activeSameNode && previous?.leaseId ? previous.leaseId : crypto.randomUUID(),
1046
+ leaseId: activeSameNode && previous?.leaseId
1047
+ ? previous.leaseId
1048
+ : (safeString(options.leaseId, 128) || crypto.randomUUID()),
1047
1049
  hostInstanceId,
1048
1050
  endpoint: routeInfo.endpoint,
1049
1051
  endpointCandidates: routeInfo.candidates,
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+
3
+ import assert from 'node:assert/strict';
4
+ import { spawn } from 'node:child_process';
5
+ import { mkdtemp, mkdir, rm } from 'node:fs/promises';
6
+ import net from 'node:net';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const BRIDGE_TOKEN = 'remote-hub-identity-smoke-token';
12
+ const LOCAL_BRIDGE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
13
+
14
+ function wait(ms) {
15
+ return new Promise(resolve => setTimeout(resolve, ms));
16
+ }
17
+
18
+ async function findFreePort() {
19
+ return await new Promise((resolve, reject) => {
20
+ const server = net.createServer();
21
+ server.unref();
22
+ server.once('error', reject);
23
+ server.listen(0, '127.0.0.1', () => {
24
+ const address = server.address();
25
+ const port = typeof address === 'object' && address ? address.port : 0;
26
+ server.close(() => resolve(port));
27
+ });
28
+ });
29
+ }
30
+
31
+ async function fetchJson(url, options = {}) {
32
+ const response = await fetch(url, {
33
+ ...options,
34
+ headers: {
35
+ 'X-Bridge-Token': BRIDGE_TOKEN,
36
+ ...(options.headers || {})
37
+ }
38
+ });
39
+ let payload = null;
40
+ try {
41
+ payload = await response.json();
42
+ } catch {
43
+ // Ignore non-JSON diagnostics.
44
+ }
45
+ return { ok: response.ok, status: response.status, payload };
46
+ }
47
+
48
+ function spawnBridge({ bridgePort, remoteHubPort, workspacePath, authDataRoot, label }) {
49
+ const child = spawn(process.execPath, ['server.js'], {
50
+ cwd: LOCAL_BRIDGE_DIR,
51
+ stdio: ['ignore', 'pipe', 'pipe'],
52
+ windowsHide: true,
53
+ env: {
54
+ ...process.env,
55
+ BRIDGE_PORT: String(bridgePort),
56
+ BRIDGE_TOKEN,
57
+ BRIDGE_REQUIRE_TOKEN: '1',
58
+ MINDEXEC_REMOTE_HUB: '1',
59
+ MINDEXEC_REMOTE_REGISTRY_FOLLOWER: '0',
60
+ REMOTE_HUB_HOST: '127.0.0.1',
61
+ REMOTE_HUB_PORT: String(remoteHubPort),
62
+ WORKSPACE_PATH: workspacePath,
63
+ MINDEXEC_AUTH_DATA_ROOT: authDataRoot,
64
+ REMOTE_HUB_PAIR_TOKEN: '',
65
+ MINDEXEC_REMOTE_PAIR_TOKEN: '',
66
+ MINDEXEC_BRIDGE_INSTANCE_ID: '',
67
+ NO_COLOR: '1'
68
+ }
69
+ });
70
+
71
+ let stdout = '';
72
+ let stderr = '';
73
+ child.stdout.on('data', chunk => {
74
+ stdout += chunk.toString();
75
+ });
76
+ child.stderr.on('data', chunk => {
77
+ stderr += chunk.toString();
78
+ });
79
+
80
+ const exitPromise = new Promise(resolve => child.once('exit', resolve));
81
+ const details = () => `${label} stdout=${stdout}\n${label} stderr=${stderr}`;
82
+ const stop = async () => {
83
+ if (child.exitCode === null && !child.killed) {
84
+ child.kill('SIGTERM');
85
+ await Promise.race([exitPromise, wait(3000)]);
86
+ if (child.exitCode === null && !child.killed) {
87
+ child.kill('SIGKILL');
88
+ }
89
+ }
90
+ };
91
+
92
+ return {
93
+ baseUrl: `http://127.0.0.1:${bridgePort}`,
94
+ details,
95
+ stop
96
+ };
97
+ }
98
+
99
+ async function waitForStatus(bridge) {
100
+ const startedAt = Date.now();
101
+ while (Date.now() - startedAt < 30000) {
102
+ try {
103
+ const result = await fetchJson(`${bridge.baseUrl}/api/remote/status`);
104
+ if (result.ok && result.payload?.started === true) {
105
+ return result.payload;
106
+ }
107
+ } catch {
108
+ // Bridge still starting.
109
+ }
110
+ await wait(100);
111
+ }
112
+
113
+ throw new Error(`Timed out waiting for bridge status.\n${bridge.details()}`);
114
+ }
115
+
116
+ async function runOnce({ workspacePath, authDataRoot, label }) {
117
+ const bridgePort = await findFreePort();
118
+ const remoteHubPort = await findFreePort();
119
+ const bridge = spawnBridge({ bridgePort, remoteHubPort, workspacePath, authDataRoot, label });
120
+ try {
121
+ return await waitForStatus(bridge);
122
+ } finally {
123
+ await bridge.stop();
124
+ }
125
+ }
126
+
127
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'mindexec-remote-hub-identity-smoke-'));
128
+ try {
129
+ const workspacePath = path.join(tempRoot, 'workspace');
130
+ const authDataRoot = path.join(tempRoot, 'auth');
131
+ await mkdir(workspacePath, { recursive: true });
132
+ await mkdir(authDataRoot, { recursive: true });
133
+
134
+ const first = await runOnce({ workspacePath, authDataRoot, label: 'first' });
135
+ const second = await runOnce({ workspacePath, authDataRoot, label: 'second' });
136
+
137
+ assert.ok(first.pairToken, JSON.stringify(first));
138
+ assert.ok(first.hostInstanceId, JSON.stringify(first));
139
+ assert.equal(second.pairToken, first.pairToken);
140
+ assert.equal(second.hostInstanceId, first.hostInstanceId);
141
+ assert.match(first.hostInstanceId, /^bridge-[a-f0-9]{32}$/);
142
+
143
+ console.log('RemoteHub identity persistence smoke OK');
144
+ } finally {
145
+ await rm(tempRoot, { recursive: true, force: true });
146
+ }
@@ -72,13 +72,13 @@ async function waitFor(predicate, timeoutMs, label) {
72
72
  throw new Error(`Timed out waiting for ${label}${lastError ? `: ${lastError.message}` : ''}.`);
73
73
  }
74
74
 
75
- function createRegistryTarget({ endpoint, endpointCandidates, leaseId, active = true }) {
75
+ function createRegistryTarget({ endpoint, endpointCandidates, leaseId, active = true, pairToken = PAIR_TOKEN }) {
76
76
  return {
77
77
  user_id: USER_ID,
78
78
  active,
79
79
  endpoint,
80
80
  endpoint_candidates: endpointCandidates,
81
- pair_token: PAIR_TOKEN,
81
+ pair_token: pairToken,
82
82
  lease_id: leaseId,
83
83
  node_id: 'remote-registry-follower-node',
84
84
  host_instance_id: `host-${leaseId}`,
package/server.js CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  import express from 'express';
10
10
  import cors from 'cors';
11
- import { promises as fs, readFileSync, statSync, createReadStream, chmodSync } from 'fs';
11
+ import { promises as fs, readFileSync, statSync, createReadStream, chmodSync, mkdirSync, writeFileSync } from 'fs';
12
12
  import path from 'path';
13
13
  import { exec, spawn, spawnSync, execFile } from 'child_process';
14
14
  import { promisify } from 'util';
@@ -32,10 +32,14 @@ const { normalizePort, releaseBridgePort } = portGuard;
32
32
  const app = express();
33
33
  const PORT = normalizePort(process.env.BRIDGE_PORT);
34
34
  const BRIDGE_ROOT = path.dirname(fileURLToPath(import.meta.url));
35
- const PACKAGE_INFO = JSON.parse(readFileSync(path.join(BRIDGE_ROOT, 'package.json'), 'utf8'));
35
+ const PACKAGE_INFO = JSON.parse(readFileSync(path.join(BRIDGE_ROOT, 'package.json'), 'utf8'));
36
36
  const BRIDGE_PACKAGE_NAME = String(PACKAGE_INFO.name || '@mindexec/cli');
37
37
  const BRIDGE_VERSION = String(PACKAGE_INFO.version || '0.1.0');
38
- const BRIDGE_INSTANCE_ID = String(process.env.MINDEXEC_BRIDGE_INSTANCE_ID || crypto.randomUUID()).trim() || crypto.randomUUID();
38
+ const REMOTE_HUB_IDENTITY_FILE_PREFIX = 'remote-hub-identity';
39
+ const REMOTE_HUB_IDENTITY_SCOPE = createRemoteHubIdentityScope();
40
+ const REMOTE_HUB_IDENTITY = loadOrCreateRemoteHubIdentity(REMOTE_HUB_IDENTITY_SCOPE);
41
+ const BRIDGE_INSTANCE_ID = String(process.env.MINDEXEC_BRIDGE_INSTANCE_ID || REMOTE_HUB_IDENTITY.hostInstanceId || crypto.randomUUID()).trim() || crypto.randomUUID();
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');
39
43
  const TREE_SITTER_GRAMMAR_DIR = path.join(BRIDGE_ROOT, 'tree-sitter-grammars');
40
44
  const VERBOSE_CODEX_TRACE = /^(1|true|yes|on)$/i.test(String(process.env.BRIDGE_VERBOSE_CODEX || ''));
41
45
  const COLOR_LOGS_ENABLED = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
@@ -404,22 +408,95 @@ function looksLikeWorkspaceRoot(candidatePath) {
404
408
  || pathExistsSync(path.join(normalizedPath, 'MindExecution.Shared'));
405
409
  }
406
410
 
407
- function resolveInitialWorkspacePath() {
408
- const envWorkspacePath = String(process.env.WORKSPACE_PATH || '').trim();
409
- if (envWorkspacePath) {
410
- return envWorkspacePath;
411
- }
411
+ function resolveInitialWorkspacePath() {
412
+ const envWorkspacePath = String(process.env.WORKSPACE_PATH || '').trim();
413
+ if (envWorkspacePath) {
414
+ return envWorkspacePath;
415
+ }
412
416
 
413
417
  const bridgeSiblingWorkspace = path.resolve(BRIDGE_ROOT, '..');
414
418
  if (looksLikeWorkspaceRoot(bridgeSiblingWorkspace)) {
415
419
  return bridgeSiblingWorkspace;
416
420
  }
417
-
418
- return path.join(os.homedir(), 'Documents', 'MindExecution');
419
- }
420
-
421
- // Default workspace path
422
- const DEFAULT_WORKSPACE = resolveInitialWorkspacePath();
421
+
422
+ return path.join(os.homedir(), 'Documents', 'MindExecution');
423
+ }
424
+
425
+ function createRemoteHubIdentityScope() {
426
+ const initialWorkspace = path.resolve(resolveInitialWorkspacePath());
427
+ const user = (() => {
428
+ try {
429
+ return os.userInfo().username || '';
430
+ } catch {
431
+ return '';
432
+ }
433
+ })();
434
+ return [
435
+ initialWorkspace.toLowerCase(),
436
+ os.hostname().toLowerCase(),
437
+ user.toLowerCase()
438
+ ].join('\n');
439
+ }
440
+
441
+ function createRemoteHubIdentityHash(scope) {
442
+ return crypto
443
+ .createHash('sha256')
444
+ .update(String(scope || 'mindexec-remote-hub'))
445
+ .digest('hex');
446
+ }
447
+
448
+ function getStableRemoteHubIdentityPath(scope) {
449
+ const scopeHash = createRemoteHubIdentityHash(scope).slice(0, 16);
450
+ return path.join(resolveAuthDataRoot(), `${REMOTE_HUB_IDENTITY_FILE_PREFIX}-${scopeHash}.json`);
451
+ }
452
+
453
+ function createStableRemoteHubInstanceId(scope) {
454
+ return `bridge-${createRemoteHubIdentityHash(scope).slice(0, 32)}`;
455
+ }
456
+
457
+ function loadOrCreateRemoteHubIdentity(scope) {
458
+ const identityPath = getStableRemoteHubIdentityPath(scope);
459
+ let existing = null;
460
+ try {
461
+ existing = JSON.parse(readFileSync(identityPath, 'utf8'));
462
+ } catch (err) {
463
+ if (err?.code !== 'ENOENT') {
464
+ // Ignore corrupt identity files and replace them with a fresh stable shape.
465
+ }
466
+ }
467
+
468
+ const hostInstanceId = String(existing?.hostInstanceId || '').trim()
469
+ || createStableRemoteHubInstanceId(scope);
470
+ const pairToken = String(existing?.pairToken || '').trim()
471
+ || crypto.randomBytes(18).toString('hex');
472
+ const next = {
473
+ version: 1,
474
+ scopeHash: createRemoteHubIdentityHash(scope).slice(0, 16),
475
+ hostInstanceId,
476
+ pairToken
477
+ };
478
+ const changed = existing?.version !== next.version
479
+ || existing?.scopeHash !== next.scopeHash
480
+ || existing?.hostInstanceId !== next.hostInstanceId
481
+ || existing?.pairToken !== next.pairToken;
482
+
483
+ if (changed) {
484
+ try {
485
+ mkdirSync(path.dirname(identityPath), { recursive: true });
486
+ writeFileSync(identityPath, `${JSON.stringify(next, null, 2)}\n`, {
487
+ encoding: 'utf8',
488
+ mode: 0o600
489
+ });
490
+ } catch (err) {
491
+ console.warn(`[RemoteHubIdentity] Failed to persist identity: ${err?.message || err}`);
492
+ }
493
+ }
494
+
495
+ return next;
496
+ }
497
+
498
+ // Default workspace path
499
+ const DEFAULT_WORKSPACE = resolveInitialWorkspacePath();
423
500
  let workspacePath = DEFAULT_WORKSPACE;
424
501
  const browserSessions = new Map();
425
502
  let currentBrowserSessionId = null;
@@ -3363,7 +3440,8 @@ const remoteHub = createRemoteHub({
3363
3440
  emitFrame: broadcastRemoteBinaryFrame,
3364
3441
  managerPackage: BRIDGE_PACKAGE_NAME,
3365
3442
  managerVersion: BRIDGE_VERSION,
3366
- hostInstanceId: BRIDGE_INSTANCE_ID
3443
+ hostInstanceId: BRIDGE_INSTANCE_ID,
3444
+ pairToken: REMOTE_HUB_PAIR_TOKEN
3367
3445
  });
3368
3446
 
3369
3447
  const REMOTE_AGENT_STDIO_TAIL_CHARS = 12000;
@@ -5841,6 +5919,23 @@ function isRemoteRegistryTargetSameAsLocalHost(localHub, target) {
5841
5919
  && localHostInstanceId.toLowerCase() === String(target.hostInstanceId).toLowerCase();
5842
5920
  }
5843
5921
 
5922
+ function isRemoteRegistryTargetEndpointLocal(localHub, target) {
5923
+ const localEndpoints = normalizeRemoteManagerEndpointList(
5924
+ localHub?.agentEndpointCandidates,
5925
+ localHub?.agentEndpoint,
5926
+ localHub?.hostTargetEndpointCandidates,
5927
+ localHub?.hostTargetEndpoint);
5928
+ const targetEndpoints = normalizeRemoteManagerEndpointList(
5929
+ target?.endpointCandidates,
5930
+ target?.endpoint);
5931
+ if (localEndpoints.length === 0 || targetEndpoints.length === 0) {
5932
+ return false;
5933
+ }
5934
+
5935
+ const localSet = new Set(localEndpoints.map(endpoint => endpoint.toLowerCase()));
5936
+ return targetEndpoints.some(endpoint => localSet.has(endpoint.toLowerCase()));
5937
+ }
5938
+
5844
5939
  async function fetchRemoteRegistryTarget(config, session) {
5845
5940
  const url = new URL('/rest/v1/remote_host_targets', config.url);
5846
5941
  url.searchParams.set('select', '*');
@@ -6609,6 +6704,54 @@ async function runRemoteRegistryFollowerOnce(trigger = 'timer') {
6609
6704
  await ensureRemoteRegistryRealtimeSubscription(config, session, trigger);
6610
6705
  const target = await fetchRemoteRegistryTarget(config, session);
6611
6706
 
6707
+ if (localHub?.hostTargetActive !== true
6708
+ && target?.active === true
6709
+ && !isRemoteRegistryTargetExpired(target)
6710
+ && isRemoteRegistryTargetEndpointLocal(localHub, target)) {
6711
+ const revived = remoteHub.setHostTarget({
6712
+ enabled: true,
6713
+ nodeId: target.nodeId,
6714
+ leaseId: target.leaseId,
6715
+ leaseMs: REMOTE_HOST_TARGET_LEASE_MS
6716
+ });
6717
+ if (revived?.ok === true) {
6718
+ if (isRemoteAgentProcessRunning()) {
6719
+ await stopRemoteAgentConnection('local-host-target-revived');
6720
+ }
6721
+ const revivedHub = remoteHub.getStatus({ includeSecrets: true });
6722
+ const registry = await publishLocalRemoteHostTargetToRegistry(revivedHub, { takeover: true });
6723
+ updateRemoteRegistryFollowerState({
6724
+ status: 'skipped',
6725
+ reason: 'local-monitor-host-revived',
6726
+ authenticated: true,
6727
+ lastAttemptAt: attemptedAt,
6728
+ lastSuccessAt: attemptedAt,
6729
+ targetEndpoint: registry.endpoint || target.endpoint,
6730
+ targetEndpointCandidates: registry.endpointCandidates?.length ? registry.endpointCandidates : target.endpointCandidates,
6731
+ targetLeaseId: revivedHub.hostTargetLeaseId,
6732
+ targetNodeId: revivedHub.hostTargetNodeId,
6733
+ lastError: registry.ok || isRemoteHostTargetRenewSoftSkipReason(registry.reason) ? '' : (registry.reason || 'host-target-republish-failed')
6734
+ });
6735
+ await reportRemoteRegistryFollowerSync({
6736
+ ok: registry.ok !== false,
6737
+ skipped: true,
6738
+ reason: registry.ok ? 'local-monitor-host-revived' : `local-monitor-host-revived-${registry.reason || 'registry-publish-failed'}`,
6739
+ trigger,
6740
+ authenticated: true,
6741
+ localHostTargetActive: true,
6742
+ targetActive: true,
6743
+ targetEndpoint: registry.endpoint || target.endpoint,
6744
+ targetEndpointCandidates: registry.endpointCandidates?.length ? registry.endpointCandidates : target.endpointCandidates,
6745
+ targetLeaseId: revivedHub.hostTargetLeaseId,
6746
+ targetNodeId: revivedHub.hostTargetNodeId,
6747
+ targetExpiresAt: revivedHub.hostTargetExpiresAt
6748
+ });
6749
+ scheduleRemoteHostTargetRenewWake('local-host-revived');
6750
+ scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_POLL_MS, 'local-host-revived');
6751
+ return serializeRemoteRegistryFollowerState();
6752
+ }
6753
+ }
6754
+
6612
6755
  if (localHub?.hostTargetActive === true) {
6613
6756
  resetRemoteRegistryInactiveTargetGrace();
6614
6757
  if (target?.active === true
@@ -2774,10 +2774,10 @@ body.mindcanvas-is-dense .css3d-resolution-wrapper.node-type-templatelauncher.se
2774
2774
  pointer-events: auto;
2775
2775
  touch-action: none;
2776
2776
  opacity: 0;
2777
- border: 1px solid rgba(37, 99, 235, 0.38);
2778
- background: rgba(255, 255, 255, 0.82);
2779
- box-shadow: 0 2px 7px rgba(37, 99, 235, 0.12);
2780
- transition: opacity 120ms ease, background 120ms ease, border-color 120ms ease;
2777
+ border: 1px solid transparent;
2778
+ background: transparent;
2779
+ box-shadow: none;
2780
+ transition: none;
2781
2781
  }
2782
2782
 
2783
2783
  .css3d-resolution-wrapper.node-type-templatelauncher > .css3d-resize-hit-layer .remote-fleet-resize-handle {
@@ -2786,34 +2786,34 @@ body.mindcanvas-is-dense .css3d-resolution-wrapper.node-type-templatelauncher.se
2786
2786
  pointer-events: auto;
2787
2787
  touch-action: none;
2788
2788
  opacity: 0;
2789
- border: 1px solid rgba(37, 99, 235, 0.18);
2790
- background: rgba(239, 246, 255, 0.24);
2789
+ border: 1px solid transparent;
2790
+ background: transparent;
2791
2791
  box-shadow: none;
2792
- transition: opacity 120ms ease, background 120ms ease, border-color 120ms ease;
2792
+ transition: none;
2793
2793
  }
2794
2794
 
2795
2795
  .css3d-resolution-wrapper.selected .map-node-template-card.map-node-remote-fleet .remote-fleet-resize-handle,
2796
2796
  .map-node-template-card.map-node-remote-fleet:hover .remote-fleet-resize-handle,
2797
2797
  .map-node-template-card.map-node-remote-fleet:focus-within .remote-fleet-resize-handle {
2798
- opacity: 0.78;
2798
+ opacity: 0;
2799
2799
  }
2800
2800
 
2801
2801
  .css3d-resolution-wrapper.node-type-templatelauncher.selected > .css3d-resize-hit-layer .remote-fleet-resize-handle,
2802
2802
  .css3d-resolution-wrapper.node-type-templatelauncher:hover > .css3d-resize-hit-layer .remote-fleet-resize-handle,
2803
2803
  .css3d-resolution-wrapper.node-type-templatelauncher:focus-within > .css3d-resize-hit-layer .remote-fleet-resize-handle {
2804
- opacity: 0.18;
2804
+ opacity: 0;
2805
2805
  }
2806
2806
 
2807
2807
  .map-node-template-card.map-node-remote-fleet .remote-fleet-resize-handle:hover {
2808
- opacity: 1;
2809
- border-color: rgba(37, 99, 235, 0.72);
2810
- background: rgba(239, 246, 255, 0.96);
2808
+ opacity: 0;
2809
+ border-color: transparent;
2810
+ background: transparent;
2811
2811
  }
2812
2812
 
2813
2813
  .css3d-resolution-wrapper.node-type-templatelauncher > .css3d-resize-hit-layer .remote-fleet-resize-handle:hover {
2814
- opacity: 0.42;
2815
- border-color: rgba(37, 99, 235, 0.48);
2816
- background: rgba(239, 246, 255, 0.46);
2814
+ opacity: 0;
2815
+ border-color: transparent;
2816
+ background: transparent;
2817
2817
  }
2818
2818
 
2819
2819
  .map-node-template-card.map-node-remote-fleet .remote-fleet-resize-handle.nw,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-wO/y6PhLVnM+OKb9a+DZH6St+JWnCdBxHFafo2120Dc=",
4
+ "hash": "sha256-e0n1RLLtx0J+Ffwaa005iSuhZrI+q9OH/azLs6RFJdE=",
5
5
  "fingerprinting": {
6
6
  "Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
7
7
  "Markdig.d1j7v41cl1.dll": "Markdig.dll",
@@ -132,7 +132,7 @@
132
132
  "MindExecution.Plugins.PlanMaster.xt4y5gj8yw.dll": "MindExecution.Plugins.PlanMaster.dll",
133
133
  "MindExecution.Plugins.YouTube.kz0lju94md.dll": "MindExecution.Plugins.YouTube.dll",
134
134
  "MindExecution.Shared.7rwzquw93z.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.hsle161c7m.dll": "MindExecution.Web.dll",
135
+ "MindExecution.Web.7on7otq3do.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",
@@ -284,7 +284,7 @@
284
284
  "MindExecution.Plugins.Concept.3mpew6v5hz.dll": "sha256-K6GVU9mkcEQatq6shNKDO9xtahvaLBOhrIOzYda8lAI=",
285
285
  "MindExecution.Plugins.PlanMaster.xt4y5gj8yw.dll": "sha256-Cx47VicQk4brENQ4Y/9SMJ81sawy9AkvnrxuDnqoAYI=",
286
286
  "MindExecution.Shared.7rwzquw93z.dll": "sha256-bZ2rx8V2aQu7Ff4nl/mgbEOg3P/3YNjMmFpIXWcu3cQ=",
287
- "MindExecution.Web.hsle161c7m.dll": "sha256-0e786DmgqKdqucNj3NloG//KBor6Wqoeci7UHtgdyaA="
287
+ "MindExecution.Web.7on7otq3do.dll": "sha256-l3+AdCxkmuRdpmgy+hg5tQBsNft43p/QZ1jFKlJAz2A="
288
288
  },
289
289
  "lazyAssembly": {
290
290
  "MindExecution.Plugins.Admin.29mytzdaun.dll": "sha256-2mHPbTPcHCi1xPuk3dNMVWxn42xZFUZ3QFhm3xIV/6E=",
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "gYWza81R",
2
+ "version": "G1uEbq5C",
3
3
  "assets": [
4
4
  {
5
5
  "hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
@@ -42,7 +42,7 @@
42
42
  "url": "_content/MindExecution.Shared/css/app.css"
43
43
  },
44
44
  {
45
- "hash": "sha256-vy6wZFJwYl0hLFMMhAKU4de+TPg7CJ91jMwnXl8GBqk=",
45
+ "hash": "sha256-OYGU0cL5+x1NEZrll29i766waIn/hsJ4See6ua47sVo=",
46
46
  "url": "_content/MindExecution.Shared/css/mind-map-overrides.css"
47
47
  },
48
48
  {
@@ -446,8 +446,8 @@
446
446
  "url": "_framework/MindExecution.Shared.7rwzquw93z.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-0e786DmgqKdqucNj3NloG//KBor6Wqoeci7UHtgdyaA=",
450
- "url": "_framework/MindExecution.Web.hsle161c7m.dll"
449
+ "hash": "sha256-l3+AdCxkmuRdpmgy+hg5tQBsNft43p/QZ1jFKlJAz2A=",
450
+ "url": "_framework/MindExecution.Web.7on7otq3do.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-qmh1kbyeNhh7PgQWPBUrFh5bC+YayDSQBAYA92Yo4VA=",
773
+ "hash": "sha256-/VqlSl0soIWMyeyUaHS8719wDwRJJLAk6Wh9TJo4PVU=",
774
774
  "url": "_framework/blazor.boot.json"
775
775
  },
776
776
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: gYWza81R */
1
+ /* Manifest version: G1uEbq5C */
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