@mindexec/cli 0.2.155 → 0.2.157

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.155",
3
+ "version": "0.2.157",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -21,15 +21,15 @@
21
21
  "scripts": {
22
22
  "start": "node launch-bridge.cjs",
23
23
  "dev": "node launch-bridge.cjs --watch",
24
- "test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
24
+ "test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
25
25
  "test:auth": "node scripts/auth-session-smoke.mjs",
26
26
  "test:remote": "node scripts/remote-hub-smoke.mjs",
27
27
  "test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
28
28
  "test:remote:render": "node scripts/remote-fleet-render-smoke.mjs",
29
- "test:remote:http": "node scripts/remote-http-smoke.mjs",
30
- "test:remote:frame-ws": "node scripts/remote-frame-ws-smoke.mjs",
31
- "test:remote:input-ws": "node scripts/remote-input-ws-smoke.mjs",
32
- "test:remote:agent-ws": "node scripts/remote-agent-ws-smoke.mjs",
29
+ "test:remote:http": "node scripts/remote-http-smoke.mjs",
30
+ "test:remote:frame-ws": "node scripts/remote-frame-ws-smoke.mjs",
31
+ "test:remote:input-ws": "node scripts/remote-input-ws-smoke.mjs",
32
+ "test:remote:agent-ws": "node scripts/remote-agent-ws-smoke.mjs",
33
33
  "test:remote:managed": "node scripts/remote-agent-managed-smoke.mjs",
34
34
  "test:remote:registry": "node scripts/remote-registry-follower-smoke.mjs",
35
35
  "test:remote:package": "node scripts/remote-agent-package-smoke.mjs",
@@ -66,6 +66,7 @@ function spawnBridge({ bridgePort, workspacePath, authDataRoot }) {
66
66
  WORKSPACE_PATH: workspacePath,
67
67
  MINDEXEC_AUTH_DATA_ROOT: authDataRoot,
68
68
  MINDEXEC_REMOTE_HUB: '0',
69
+ MINDEXEC_REMOTE_REGISTRY_FOLLOWER: '0',
69
70
  NO_COLOR: '1'
70
71
  }
71
72
  });
@@ -464,6 +464,25 @@ async function main() {
464
464
  && request.pathname === '/rest/v1/rpc/set_remote_host_target').length;
465
465
  return publishCount >= 2 ? publishCount : null;
466
466
  }, 8000, `host-target auto renew publish\n${renewHost.details()}`);
467
+ const beforeResumePublishCount = fakeSupabase.requests.filter(request =>
468
+ request.method === 'POST'
469
+ && request.pathname === '/rest/v1/rpc/set_remote_host_target').length;
470
+ const renewResume = await fetchJson(`${renewHost.baseUrl}/api/remote/system/resume`, {
471
+ method: 'POST',
472
+ token: BRIDGE_TOKEN,
473
+ body: JSON.stringify({
474
+ driftMs: 65000
475
+ })
476
+ });
477
+ assert.equal(renewResume.ok, true, JSON.stringify(renewResume.payload));
478
+ assert.equal(renewResume.payload?.ok, true, JSON.stringify(renewResume.payload));
479
+ assert.equal(renewResume.payload?.remoteSystemResume?.localHost, true, JSON.stringify(renewResume.payload?.remoteSystemResume));
480
+ await waitFor(() => {
481
+ const publishCount = fakeSupabase.requests.filter(request =>
482
+ request.method === 'POST'
483
+ && request.pathname === '/rest/v1/rpc/set_remote_host_target').length;
484
+ return publishCount > beforeResumePublishCount ? publishCount : null;
485
+ }, 8000, `host-target resume republish\n${renewHost.details()}`);
467
486
  const renewStatus = await fetchJson(`${renewHost.baseUrl}/api/status`);
468
487
  assert.equal(renewStatus.payload?.remoteHostTargetRenew?.status, 'active', JSON.stringify(renewStatus.payload?.remoteHostTargetRenew));
469
488
  assert.equal(renewStatus.payload?.remoteHostTargetRenew?.endpoint, renewPublicEndpoint, JSON.stringify(renewStatus.payload?.remoteHostTargetRenew));
@@ -523,6 +542,24 @@ async function main() {
523
542
  const firstStatus = await fetchJson(`${client.baseUrl}/api/status`);
524
543
  assert.equal(firstStatus.payload?.remoteRegistryRealtime?.subscribed, true, JSON.stringify(firstStatus.payload?.remoteRegistryRealtime));
525
544
  assert.ok(fakeSupabase.realtimeClients.size >= 1, 'expected LocalBridge realtime watcher to connect');
545
+ const firstPid = Number(firstAgent.pid || 0);
546
+ assert.ok(firstPid > 0, JSON.stringify(firstAgent));
547
+
548
+ const clientResume = await fetchJson(`${client.baseUrl}/api/remote/system/resume`, {
549
+ method: 'POST',
550
+ token: BRIDGE_TOKEN,
551
+ body: JSON.stringify({
552
+ driftMs: 65000
553
+ })
554
+ });
555
+ assert.equal(clientResume.ok, true, JSON.stringify(clientResume.payload));
556
+ assert.equal(clientResume.payload?.ok, true, JSON.stringify(clientResume.payload));
557
+ assert.equal(clientResume.payload?.remoteSystemResume?.registryAgentRestarted, true, JSON.stringify(clientResume.payload?.remoteSystemResume));
558
+ const resumedAgent = await waitForManagedAgentRestart(client, hostAEndpoint, firstPid, 'host-a after resume', 12000);
559
+ assert.equal(resumedAgent.usingNpx, false, JSON.stringify(resumedAgent));
560
+ assert.match(String(resumedAgent.launcher || ''), /mindexec-remote-fast/i);
561
+ assert.equal(resumedAgent.leaseId, 'lease-a');
562
+ await waitForConnectedDevice(hostA, 'host-a after resume');
526
563
 
527
564
  currentTarget = createRegistryTarget({
528
565
  endpoint: hostBEndpoint,
package/server.js CHANGED
@@ -3374,6 +3374,10 @@ const REMOTE_HOST_TARGET_RENEW_MS = Math.max(
3374
3374
  Math.floor(REMOTE_HOST_TARGET_LEASE_MS / 2),
3375
3375
  Number(process.env.MINDEXEC_REMOTE_HOST_TARGET_RENEW_MS || 25000) || 25000));
3376
3376
  const REMOTE_HOST_TARGET_RENEW_LOG_REPEAT_MS = 60000;
3377
+ const REMOTE_SYSTEM_RESUME_CHECK_MS = Math.max(1000, Number(process.env.MINDEXEC_REMOTE_RESUME_CHECK_MS || 5000) || 5000);
3378
+ const REMOTE_SYSTEM_RESUME_DRIFT_MS = Math.max(
3379
+ REMOTE_SYSTEM_RESUME_CHECK_MS + 5000,
3380
+ Number(process.env.MINDEXEC_REMOTE_RESUME_DRIFT_MS || 20000) || 20000);
3377
3381
  let remoteAgentState = createRemoteAgentIdleState();
3378
3382
  let remoteAgentSyncReportState = null;
3379
3383
  let remoteAgentSyncReportLogKey = '';
@@ -3396,10 +3400,27 @@ let remoteRegistryRealtimeHeartbeatTimer = null;
3396
3400
  let remoteRegistryRealtimeReconnectTimer = null;
3397
3401
  let remoteRegistryRealtimeReconnectContext = null;
3398
3402
  let remoteRegistryRealtimeLastWakeAt = 0;
3403
+ let remoteSupabaseRuntimeConfigCache = null;
3399
3404
  let remoteHostTargetRenewTimer = null;
3400
3405
  let remoteHostTargetRenewInFlight = false;
3401
3406
  let remoteHostTargetRenewLogKey = '';
3402
3407
  let remoteHostTargetRenewLogAt = 0;
3408
+ let remoteSystemResumeTimer = null;
3409
+ let remoteSystemResumeExpectedAt = 0;
3410
+ let remoteSystemResumeInFlight = false;
3411
+ let remoteSystemResumeState = {
3412
+ enabled: true,
3413
+ status: 'idle',
3414
+ reason: '',
3415
+ trigger: '',
3416
+ lastDetectedAt: '',
3417
+ lastCompletedAt: '',
3418
+ lastError: '',
3419
+ driftMs: 0,
3420
+ localHost: false,
3421
+ registryAgentRestarted: false,
3422
+ disconnectedDevices: 0
3423
+ };
3403
3424
  let remoteRegistryFollowerState = {
3404
3425
  enabled: REMOTE_REGISTRY_FOLLOWER_ENABLED,
3405
3426
  status: REMOTE_REGISTRY_FOLLOWER_ENABLED ? 'idle' : 'disabled',
@@ -4762,6 +4783,23 @@ function updateRemoteHostTargetRenewState(patch = {}) {
4762
4783
  emitBridgeEvent('RemoteHostTargetRenewUpdated', serializeRemoteHostTargetRenewState());
4763
4784
  }
4764
4785
 
4786
+ function serializeRemoteSystemResumeState() {
4787
+ return {
4788
+ ...remoteSystemResumeState,
4789
+ checkMs: REMOTE_SYSTEM_RESUME_CHECK_MS,
4790
+ driftThresholdMs: REMOTE_SYSTEM_RESUME_DRIFT_MS
4791
+ };
4792
+ }
4793
+
4794
+ function updateRemoteSystemResumeState(patch = {}) {
4795
+ remoteSystemResumeState = {
4796
+ ...remoteSystemResumeState,
4797
+ ...patch,
4798
+ enabled: true
4799
+ };
4800
+ emitBridgeEvent('RemoteSystemResumeUpdated', serializeRemoteSystemResumeState());
4801
+ }
4802
+
4765
4803
  function serializeRemoteRegistryRealtimeState() {
4766
4804
  return {
4767
4805
  ...remoteRegistryRealtimeState,
@@ -5189,12 +5227,19 @@ function readSupabaseRuntimeConfig() {
5189
5227
  || process.env.MINDEXEC_SUPABASE_KEY
5190
5228
  || '').trim();
5191
5229
  if (envUrl && envKey) {
5192
- return { url: envUrl.replace(/\/+$/, ''), key: envKey };
5230
+ remoteSupabaseRuntimeConfigCache = {
5231
+ url: envUrl.replace(/\/+$/, ''),
5232
+ key: envKey,
5233
+ source: 'env'
5234
+ };
5235
+ return { ...remoteSupabaseRuntimeConfigCache };
5193
5236
  }
5194
5237
 
5195
5238
  const candidates = [
5196
5239
  path.join(WEB_APP_ROOT || '', 'appsettings.json'),
5197
5240
  path.join(DEFAULT_WEB_APP_ROOT, 'appsettings.json'),
5241
+ path.join(BRIDGE_ROOT, 'wwwroot', 'appsettings.json'),
5242
+ path.join(process.cwd(), 'wwwroot', 'appsettings.json'),
5198
5243
  path.resolve(BRIDGE_ROOT, '..', 'MindExecution.Web', 'wwwroot', 'appsettings.json')
5199
5244
  ].filter(Boolean);
5200
5245
 
@@ -5204,13 +5249,25 @@ function readSupabaseRuntimeConfig() {
5204
5249
  const url = String(payload?.Supabase?.Url || '').trim().replace(/\/+$/, '');
5205
5250
  const key = String(payload?.Supabase?.Key || payload?.Supabase?.AnonKey || payload?.Supabase?.PublishableKey || '').trim();
5206
5251
  if (url && key) {
5207
- return { url, key };
5252
+ remoteSupabaseRuntimeConfigCache = {
5253
+ url,
5254
+ key,
5255
+ source: candidate
5256
+ };
5257
+ return { ...remoteSupabaseRuntimeConfigCache };
5208
5258
  }
5209
5259
  } catch {
5210
5260
  // Try the next appsettings candidate.
5211
5261
  }
5212
5262
  }
5213
5263
 
5264
+ if (remoteSupabaseRuntimeConfigCache?.url && remoteSupabaseRuntimeConfigCache?.key) {
5265
+ return {
5266
+ ...remoteSupabaseRuntimeConfigCache,
5267
+ cached: true
5268
+ };
5269
+ }
5270
+
5214
5271
  return { url: '', key: '' };
5215
5272
  }
5216
5273
 
@@ -5603,6 +5660,145 @@ function stopRemoteHostTargetRenew(reason = 'stopped') {
5603
5660
  });
5604
5661
  }
5605
5662
 
5663
+ function disconnectRemoteHubDevicesForResume(reason = 'system-resume') {
5664
+ const devices = remoteHub.listDevices();
5665
+ let disconnected = 0;
5666
+ for (const device of devices) {
5667
+ if (device?.connected !== true || !device?.deviceId) {
5668
+ continue;
5669
+ }
5670
+
5671
+ if (remoteHub.disconnectDevice(device.deviceId, reason)) {
5672
+ disconnected += 1;
5673
+ }
5674
+ }
5675
+
5676
+ return disconnected;
5677
+ }
5678
+
5679
+ async function reconcileRemoteAfterSystemResume(trigger = 'timer-drift', driftMs = 0) {
5680
+ if (remoteSystemResumeInFlight || isShuttingDown) {
5681
+ return;
5682
+ }
5683
+
5684
+ remoteSystemResumeInFlight = true;
5685
+ try {
5686
+ const localHub = remoteHub.getStatus({ includeSecrets: true });
5687
+ const isLocalHost = localHub?.hostTargetActive === true && !!localHub.hostTargetNodeId;
5688
+ const wasRegistryAgentRunning = isRemoteAgentRegistryOwned();
5689
+ const disconnectedDevices = isLocalHost
5690
+ ? disconnectRemoteHubDevicesForResume('system-resume')
5691
+ : 0;
5692
+
5693
+ updateRemoteSystemResumeState({
5694
+ status: 'detected',
5695
+ reason: 'system-resume',
5696
+ trigger,
5697
+ lastDetectedAt: new Date().toISOString(),
5698
+ lastError: '',
5699
+ driftMs: Math.round(Number(driftMs) || 0),
5700
+ localHost: isLocalHost,
5701
+ registryAgentRestarted: wasRegistryAgentRunning,
5702
+ disconnectedDevices
5703
+ });
5704
+
5705
+ logEvent(
5706
+ 'remote',
5707
+ `system resume detected ${formatKeyValue('trigger', trigger)} ${formatKeyValue('driftMs', Math.round(Number(driftMs) || 0))} ${formatKeyValue('host', isLocalHost ? 'yes' : 'no')} ${formatKeyValue('agent', wasRegistryAgentRunning ? 'registry' : '-')}`,
5708
+ 'remote');
5709
+
5710
+ closeRemoteRegistryRealtime('system-resume');
5711
+
5712
+ if (wasRegistryAgentRunning) {
5713
+ await stopRemoteAgentConnection('system-resume-reconnect');
5714
+ }
5715
+
5716
+ if (isLocalHost) {
5717
+ updateRemoteHostTargetRenewState({
5718
+ status: 'waking',
5719
+ reason: 'system-resume',
5720
+ nodeId: localHub.hostTargetNodeId,
5721
+ leaseId: localHub.hostTargetLeaseId,
5722
+ hostInstanceId: localHub.hostTargetHostInstanceId || localHub.hostInstanceId,
5723
+ endpoint: localHub.hostTargetEndpoint,
5724
+ endpointCandidates: localHub.hostTargetEndpointCandidates || [],
5725
+ expiresAt: localHub.hostTargetExpiresAt,
5726
+ lastError: ''
5727
+ });
5728
+ await runRemoteHostTargetRenewOnce('system-resume', { takeover: false });
5729
+ }
5730
+
5731
+ updateRemoteRegistryFollowerState({
5732
+ status: 'waking',
5733
+ reason: 'system-resume',
5734
+ lastError: '',
5735
+ lastAttemptAt: new Date().toISOString()
5736
+ });
5737
+ await wakeRemoteRegistryFollower('system-resume');
5738
+
5739
+ updateRemoteSystemResumeState({
5740
+ status: 'reconciled',
5741
+ reason: 'system-resume',
5742
+ lastCompletedAt: new Date().toISOString(),
5743
+ lastError: ''
5744
+ });
5745
+
5746
+ emitBridgeEvent('RemoteSystemResumeReconciled', {
5747
+ trigger,
5748
+ driftMs: Math.round(Number(driftMs) || 0),
5749
+ localHost: isLocalHost,
5750
+ registryAgentRestarted: wasRegistryAgentRunning,
5751
+ disconnectedDevices
5752
+ });
5753
+ } catch (error) {
5754
+ logWarn('remote', `system resume reconcile failed: ${error?.message || error}`);
5755
+ updateRemoteSystemResumeState({
5756
+ status: 'error',
5757
+ reason: 'system-resume-failed',
5758
+ lastError: error?.message || String(error || ''),
5759
+ lastCompletedAt: new Date().toISOString()
5760
+ });
5761
+ updateRemoteRegistryFollowerState({
5762
+ status: 'error',
5763
+ reason: 'system-resume-failed',
5764
+ lastError: error?.message || String(error || ''),
5765
+ lastAttemptAt: new Date().toISOString()
5766
+ });
5767
+ scheduleRemoteRegistryFollower(REMOTE_REGISTRY_FOLLOWER_FAST_RETRY_MS, 'system-resume-error');
5768
+ } finally {
5769
+ remoteSystemResumeInFlight = false;
5770
+ }
5771
+ }
5772
+
5773
+ function startRemoteSystemResumeMonitor() {
5774
+ if (remoteSystemResumeTimer || isShuttingDown) {
5775
+ return;
5776
+ }
5777
+
5778
+ remoteSystemResumeExpectedAt = Date.now() + REMOTE_SYSTEM_RESUME_CHECK_MS;
5779
+ remoteSystemResumeTimer = setInterval(() => {
5780
+ const now = Date.now();
5781
+ const driftMs = now - remoteSystemResumeExpectedAt;
5782
+ remoteSystemResumeExpectedAt = now + REMOTE_SYSTEM_RESUME_CHECK_MS;
5783
+ if (driftMs < REMOTE_SYSTEM_RESUME_DRIFT_MS) {
5784
+ return;
5785
+ }
5786
+
5787
+ reconcileRemoteAfterSystemResume('timer-drift', driftMs).catch(error => {
5788
+ logWarn('remote', `system resume handler failed: ${error?.message || error}`);
5789
+ });
5790
+ }, REMOTE_SYSTEM_RESUME_CHECK_MS);
5791
+ remoteSystemResumeTimer?.unref?.();
5792
+ }
5793
+
5794
+ function stopRemoteSystemResumeMonitor() {
5795
+ if (remoteSystemResumeTimer) {
5796
+ clearInterval(remoteSystemResumeTimer);
5797
+ remoteSystemResumeTimer = null;
5798
+ }
5799
+ remoteSystemResumeExpectedAt = 0;
5800
+ }
5801
+
5606
5802
  async function runRemoteHostTargetRenewOnce(trigger = 'timer', options = {}) {
5607
5803
  if (!REMOTE_HOST_TARGET_AUTO_RENEW_ENABLED || remoteHostTargetRenewInFlight) {
5608
5804
  return serializeRemoteHostTargetRenewState();
@@ -10509,6 +10705,7 @@ app.get('/api/status', async (req, res) => {
10509
10705
  remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
10510
10706
  remoteRegistryRealtime: serializeRemoteRegistryRealtimeState(),
10511
10707
  remoteHostTargetRenew: serializeRemoteHostTargetRenewState(),
10708
+ remoteSystemResume: serializeRemoteSystemResumeState(),
10512
10709
  shellJobsPath: '/api/shell/jobs',
10513
10710
  companyCore: {
10514
10711
  baseUrl: companyCoreBaseUrl,
@@ -10551,6 +10748,26 @@ app.get('/api/remote/status', (req, res) => {
10551
10748
  res.json(remoteHub.getStatus({ includeSecrets: true }));
10552
10749
  });
10553
10750
 
10751
+ app.post('/api/remote/system/resume', async (req, res) => {
10752
+ res.setHeader('Cache-Control', 'no-store');
10753
+ try {
10754
+ await reconcileRemoteAfterSystemResume('manual-api', Number(req.body?.driftMs || 0));
10755
+ res.json({
10756
+ ok: true,
10757
+ remoteSystemResume: serializeRemoteSystemResumeState(),
10758
+ remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
10759
+ remoteHostTargetRenew: serializeRemoteHostTargetRenewState(),
10760
+ remoteAgent: serializeRemoteAgentState()
10761
+ });
10762
+ } catch (err) {
10763
+ res.status(500).json({
10764
+ ok: false,
10765
+ error: err?.message || String(err),
10766
+ remoteSystemResume: serializeRemoteSystemResumeState()
10767
+ });
10768
+ }
10769
+ });
10770
+
10554
10771
  app.get('/api/remote/devices', (req, res) => {
10555
10772
  res.setHeader('Cache-Control', 'no-store');
10556
10773
  const includeDataUrl = /^(1|true|yes|on|data|base64)$/i.test(String(req.query?.includeDataUrl ?? req.query?.frameData ?? ''));
@@ -12313,8 +12530,9 @@ async function startBridgeServer() {
12313
12530
  : `${tone('app', 'muted')} ${tone('not packaged', 'warn')}`,
12314
12531
  `${tone('listen', 'muted')} ${tone(`http://127.0.0.1:${PORT}`, 'path')}`,
12315
12532
  `${tone('stop', 'muted')} Ctrl+C`
12316
- ]);
12533
+ ]);
12317
12534
  startRemoteRegistryFollower();
12535
+ startRemoteSystemResumeMonitor();
12318
12536
  });
12319
12537
  }
12320
12538
 
@@ -12382,6 +12600,12 @@ async function shutdownBridge(signal) {
12382
12600
  // Ignore registry follower shutdown errors
12383
12601
  }
12384
12602
 
12603
+ try {
12604
+ stopRemoteSystemResumeMonitor();
12605
+ } catch {
12606
+ // Ignore system resume monitor shutdown errors
12607
+ }
12608
+
12385
12609
  try {
12386
12610
  stopRemoteHostTargetRenew('bridge-shutdown');
12387
12611
  } catch {
@@ -2282,9 +2282,26 @@ body.is-panning .mind-map-text-overlay-v2-card.is-passive .mind-map-text-overlay
2282
2282
  .css3d-resolution-wrapper.node-type-templatelauncher > .map-node-template-card {
2283
2283
  border-radius: 8px !important;
2284
2284
  background: #f8fafc;
2285
- box-shadow:
2286
- 0 18px 38px rgba(15, 23, 42, 0.16),
2287
- 0 0 0 1px rgba(15, 23, 42, 0.08);
2285
+ box-shadow: none !important;
2286
+ }
2287
+
2288
+ /* Template launcher and Multi Desktop Monitor own their visual chrome on the
2289
+ card itself. Keep the CSS3D wrapper neutral so motion CSS3D and the passive
2290
+ DOM overlay do not stack different selection glows. */
2291
+ .css3d-resolution-wrapper.node-type-templatelauncher.selected,
2292
+ body.is-panning .css3d-resolution-wrapper.node-type-templatelauncher.selected,
2293
+ body.is-zooming .css3d-resolution-wrapper.node-type-templatelauncher.selected,
2294
+ body.mindcanvas-is-moving .css3d-resolution-wrapper.node-type-templatelauncher.selected,
2295
+ body.mindcanvas-is-dense .css3d-resolution-wrapper.node-type-templatelauncher.selected {
2296
+ outline: none !important;
2297
+ -webkit-box-shadow: none !important;
2298
+ box-shadow: none !important;
2299
+ -webkit-filter: none !important;
2300
+ filter: none !important;
2301
+ }
2302
+
2303
+ .css3d-resolution-wrapper.node-type-templatelauncher.selected > .map-node-template-card {
2304
+ box-shadow: none !important;
2288
2305
  }
2289
2306
 
2290
2307
  .map-node-template-card,
@@ -5040,6 +5040,9 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5040
5040
  const hasLiveInteractiveDomOverlay =
5041
5041
  useTextOverlayV2 &&
5042
5042
  window.MindMapTextOverlayV2?.hasLiveInteractiveOverlay?.(this) === true;
5043
+ const hasPassiveDomOverlaySurface =
5044
+ shouldRenderPassiveDomTextOverlay ||
5045
+ hasLiveInteractiveDomOverlay;
5043
5046
  const overlayV2State = useTextOverlayV2 ? (this._textOverlayV2State || null) : null;
5044
5047
  const hasOverlayV2DirtyState = !!(
5045
5048
  overlayV2State && (
@@ -5088,11 +5091,11 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5088
5091
  this._overlayDebugRefreshAgeMs = timeSinceLastPassiveOverlayRefresh;
5089
5092
  this._overlayDebugRefreshDeferred = shouldDeferInteractivePassiveOverlayRefresh;
5090
5093
  const isPassiveOverlayIdleDelayActive =
5091
- shouldRenderPassiveDomTextOverlay &&
5094
+ hasPassiveDomOverlaySurface &&
5092
5095
  Number(this._passiveOverlayResumeAt || 0) > frameStart;
5093
5096
  const shouldSuppressPassiveOverlayDuringCameraMotion =
5094
5097
  useTextOverlayV2 &&
5095
- shouldRenderPassiveDomTextOverlay &&
5098
+ hasPassiveDomOverlaySurface &&
5096
5099
  (isPassiveOverlayIdleDelayActive ||
5097
5100
  this.isZooming === true ||
5098
5101
  this.isPanning === true ||
@@ -5157,7 +5160,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5157
5160
  !shouldUsePassiveOverlayCameraFastPath;
5158
5161
  const shouldUsePassiveOverlayMotionHint =
5159
5162
  useTextOverlayV2 &&
5160
- shouldRenderPassiveDomTextOverlay &&
5163
+ hasPassiveDomOverlaySurface &&
5161
5164
  !isPassiveOverlaySuppressed &&
5162
5165
  !hasOverlayFocus &&
5163
5166
  !hasOverlayV2DirtyState &&
@@ -797,24 +797,24 @@
797
797
 
798
798
  /* Preserve agent memo chrome during camera motion so middle-button panning
799
799
  does not visibly weaken their glow or console surfaces. */
800
- body.is-zooming .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher),
801
- body.is-panning .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher),
802
- body.mindcanvas-is-moving .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher),
803
- body.mindcanvas-is-dense .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) {
800
+ body.is-zooming .css3d-resolution-wrapper:not(.selection-style-agent),
801
+ body.is-panning .css3d-resolution-wrapper:not(.selection-style-agent),
802
+ body.mindcanvas-is-moving .css3d-resolution-wrapper:not(.selection-style-agent),
803
+ body.mindcanvas-is-dense .css3d-resolution-wrapper:not(.selection-style-agent) {
804
804
  transition: none !important;
805
805
  -webkit-transition: none !important;
806
806
  filter: none !important;
807
807
  -webkit-filter: none !important;
808
808
  }
809
809
 
810
- body.is-zooming .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) .css3d-dynamic-node,
811
- body.is-panning .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) .css3d-dynamic-node,
812
- body.mindcanvas-is-moving .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) .css3d-dynamic-node,
813
- body.mindcanvas-is-dense .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) .css3d-dynamic-node,
814
- body.is-zooming .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) [id^="css3d-node-"],
815
- body.is-panning .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) [id^="css3d-node-"],
816
- body.mindcanvas-is-moving .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) [id^="css3d-node-"],
817
- body.mindcanvas-is-dense .css3d-resolution-wrapper:not(.selection-style-agent):not(.node-type-templatelauncher) [id^="css3d-node-"] {
810
+ body.is-zooming .css3d-resolution-wrapper:not(.selection-style-agent) .css3d-dynamic-node,
811
+ body.is-panning .css3d-resolution-wrapper:not(.selection-style-agent) .css3d-dynamic-node,
812
+ body.mindcanvas-is-moving .css3d-resolution-wrapper:not(.selection-style-agent) .css3d-dynamic-node,
813
+ body.mindcanvas-is-dense .css3d-resolution-wrapper:not(.selection-style-agent) .css3d-dynamic-node,
814
+ body.is-zooming .css3d-resolution-wrapper:not(.selection-style-agent) [id^="css3d-node-"],
815
+ body.is-panning .css3d-resolution-wrapper:not(.selection-style-agent) [id^="css3d-node-"],
816
+ body.mindcanvas-is-moving .css3d-resolution-wrapper:not(.selection-style-agent) [id^="css3d-node-"],
817
+ body.mindcanvas-is-dense .css3d-resolution-wrapper:not(.selection-style-agent) [id^="css3d-node-"] {
818
818
  box-shadow: none !important;
819
819
  filter: none !important;
820
820
  -webkit-filter: none !important;
@@ -4013,7 +4013,9 @@
4013
4013
 
4014
4014
  function isLiveOverlayPortalNode(nodeModel) {
4015
4015
  const contentType = String(nodeModel?.contentType ?? nodeModel?.ContentType ?? '').trim().toLowerCase();
4016
- return contentType === CSV_TABLE_CONTENT_TYPE || isTemplateLauncherNode(nodeModel);
4016
+ return contentType === CSV_TABLE_CONTENT_TYPE
4017
+ || isTemplateLauncherNode(nodeModel)
4018
+ || isBusinessAutomationNode(nodeModel);
4017
4019
  }
4018
4020
 
4019
4021
  function isBusinessAutomationContextSourceNode(nodeModel) {
@@ -374,6 +374,17 @@
374
374
  return getNodeContentType(model) === 'templatelauncher';
375
375
  }
376
376
 
377
+ function isBusinessAutomationModel(model) {
378
+ return getNodeSemanticType(model) === BUSINESS_AUTOMATION_SEMANTIC_TYPE;
379
+ }
380
+
381
+ function isLivePortalCssLodModel(model) {
382
+ const contentType = getNodeContentType(model);
383
+ return isTemplateLauncherModel(model) ||
384
+ contentType === 'csv-table' ||
385
+ isBusinessAutomationModel(model);
386
+ }
387
+
377
388
  function isRemoteFleetNodeModel(model) {
378
389
  const semanticType = getNodeSemanticType(model);
379
390
  return semanticType === REMOTE_FLEET_MONITOR_SEMANTIC_TYPE ||
@@ -4238,7 +4249,7 @@
4238
4249
 
4239
4250
  const entry = nodeObjectsById.get(nodeId);
4240
4251
  if (!entry?.cssObject || !supportsCss3dNodeModel(entry.model)) continue;
4241
- if (isTemplateLauncherModel(entry.model)) continue;
4252
+ if (isLivePortalCssLodModel(entry.model)) continue;
4242
4253
  if (shouldShowAgentConsoleInLodBand(lodBand) && hasAgentConsoleOpenForEntry(entry)) continue;
4243
4254
 
4244
4255
  nextWarmIds.add(nodeId);
@@ -4268,7 +4279,7 @@
4268
4279
  const nodeId = warmIds[i];
4269
4280
  if (keepAttachedIds?.has(nodeId)) continue;
4270
4281
  const entry = nodeObjectsById.get(nodeId);
4271
- if (isTemplateLauncherModel(entry?.model)) continue;
4282
+ if (isLivePortalCssLodModel(entry?.model)) continue;
4272
4283
  this._detachCss3dObject(entry);
4273
4284
  }
4274
4285
  }
@@ -4401,7 +4412,7 @@
4401
4412
  if (!nodeObjectsById) return;
4402
4413
 
4403
4414
  for (const entry of nodeObjectsById.values()) {
4404
- if (!entry || !isTemplateLauncherModel(entry.model)) continue;
4415
+ if (!entry || !isLivePortalCssLodModel(entry.model)) continue;
4405
4416
 
4406
4417
  const nodeId = String(entry.model?.id || entry.model?.Id || entry.glObject?.userData?.nodeId || '').trim();
4407
4418
  if (isCss3dRendererEnabled(module)) {
@@ -4886,7 +4897,7 @@
4886
4897
  let count = 0;
4887
4898
  for (const nodeId of Array.from(this._lodTemplateCssNodeIds)) {
4888
4899
  const entry = nodeObjectsById.get(nodeId) || null;
4889
- if (!entry || !isTemplateLauncherModel(entry.model)) {
4900
+ if (!entry || !isLivePortalCssLodModel(entry.model)) {
4890
4901
  this._lodTemplateCssNodeIds.delete(nodeId);
4891
4902
  continue;
4892
4903
  }
@@ -16,6 +16,7 @@
16
16
  const LIVE_TEMPLATE_OVERLAY_MODE = 'live-template';
17
17
  const CSV_TABLE_CONTENT_TYPE = 'csv-table';
18
18
  const REMOTE_FLEET_SEMANTIC_TYPE = 'RemoteFleetMonitor';
19
+ const BUSINESS_AUTOMATION_SEMANTIC_TYPE = 'BusinessAutomationNode';
19
20
  const log = DEBUG ? console.log.bind(console, '[MindMapTextOverlayV2]') : () => { };
20
21
  let _projCornerTopLeft = null;
21
22
  let _projCornerTopRight = null;
@@ -87,7 +88,8 @@
87
88
  const semanticType = getNodeSemanticType(entry);
88
89
  return type === 'templatelauncher'
89
90
  || type === CSV_TABLE_CONTENT_TYPE
90
- || semanticType === REMOTE_FLEET_SEMANTIC_TYPE;
91
+ || semanticType === REMOTE_FLEET_SEMANTIC_TYPE
92
+ || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE;
91
93
  }
92
94
 
93
95
  function isLiveInteractiveOverlayMode(mode) {
@@ -99,7 +101,7 @@
99
101
  const semanticType = getNodeSemanticType(entry);
100
102
  return semanticType === 'MindCanvasAgent'
101
103
  || semanticType === 'AgentCommand'
102
- || semanticType === 'BusinessAutomationNode';
104
+ || semanticType === BUSINESS_AUTOMATION_SEMANTIC_TYPE;
103
105
  }
104
106
 
105
107
  function isDomOverlayDebugEnabled(module) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-H62hFd8/JAFraA8jNVN3s3GNyvSJa5o8b6zHgtuC/es=",
4
+ "hash": "sha256-oN9NB5XxbZ2xSDkf/iKkLdth/StnRySGcRVgEHblI6U=",
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.0yfii1nh3p.dll": "MindExecution.Plugins.PlanMaster.dll",
133
133
  "MindExecution.Plugins.YouTube.y4vxx807fo.dll": "MindExecution.Plugins.YouTube.dll",
134
134
  "MindExecution.Shared.50ptsgk9eg.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.hztii75ar1.dll": "MindExecution.Web.dll",
135
+ "MindExecution.Web.hucq2w2ism.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",
@@ -283,7 +283,7 @@
283
283
  "MindExecution.Plugins.Concept.4ncya2e794.dll": "sha256-gKad12tQh1p7kInUiSIECobEZiD4VIuxmmskGE3ambo=",
284
284
  "MindExecution.Plugins.PlanMaster.0yfii1nh3p.dll": "sha256-OMcIYPKBnnJCW6QRN8nGykgCloUl1FRnFx6lma85ZGs=",
285
285
  "MindExecution.Shared.50ptsgk9eg.dll": "sha256-1X+o7c7jCm4MVorCrq52beiyMe+C6qFw6EyIGfZGQdo=",
286
- "MindExecution.Web.hztii75ar1.dll": "sha256-s/mh0EOyp5uxPxzn6b4blKD/zAAA5oKCjFDe0tCgc9g="
286
+ "MindExecution.Web.hucq2w2ism.dll": "sha256-iSV90XAYUoYq7Rfm7gf4tzJBsIy2zbfVfxadAFS8pSY="
287
287
  },
288
288
  "lazyAssembly": {
289
289
  "MindExecution.Plugins.Admin.bk23zzy6wc.dll": "sha256-uz7Nz0OZ8BBESp2Nr9YocyzV0dUrn7szPuD4/m/RWL8=",
@@ -7,8 +7,8 @@
7
7
  <title>MindExec | Run your ideas as AI task graphs</title>
8
8
  <meta name="description" content="MindExec is an AI execution canvas for solo builders, researchers, developers, and creators. Start with free browser tools, then move serious work into saved MindCanvas projects." />
9
9
  <base href="/" />
10
- <link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260617-remote-control-node-v587" />
11
- <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260617-remote-control-node-v587" />
10
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260617-auth-mirror-v590" />
11
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260617-auth-mirror-v590" />
12
12
  <!-- ?쇄뼹??Font Awesome (local) ?쇄뼹??-->
13
13
  <link rel="stylesheet" href="_content/MindExecution.Shared/lib/font-awesome/css/all.min.css" />
14
14
  <!-- ?꿎뼯??-->
@@ -579,7 +579,7 @@
579
579
  }
580
580
 
581
581
  const base = '_content/MindExecution.Shared/js/';
582
- const scriptVersion = '20260617-remote-control-node-v587';
582
+ const scriptVersion = '20260617-auth-mirror-v590';
583
583
  const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
584
584
  console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
585
585
  const criticalScripts = [
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "EpMH+qVw",
2
+ "version": "GgmfxCXF",
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-Z/qOemFxT1byK3tSyusCzWleiFLxcMRdsEc/0DoSPbk=",
45
+ "hash": "sha256-FxtluumoQV2QXTCFtSR+/dc0Xp/CFDcF1cXkaeikt4s=",
46
46
  "url": "_content/MindExecution.Shared/css/mind-map-overrides.css"
47
47
  },
48
48
  {
@@ -78,7 +78,7 @@
78
78
  "url": "_content/MindExecution.Shared/js/marked.min.js"
79
79
  },
80
80
  {
81
- "hash": "sha256-y/ehxOpFseX3lMFu+VGjbL4lTMmQ0kEmj5QIRldR2S4=",
81
+ "hash": "sha256-xTWG0IO6nYqtcvUgWluJHWfalJjI4MN86PHUMmtpECs=",
82
82
  "url": "_content/MindExecution.Shared/js/mind-map-core.js"
83
83
  },
84
84
  {
@@ -86,7 +86,7 @@
86
86
  "url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
87
87
  },
88
88
  {
89
- "hash": "sha256-7XM3q7xvvWVxDjSBniKorjQgNjyhOcAz2WNqg8OHOa0=",
89
+ "hash": "sha256-1ImfUjyr1aBodcgICy6KTDGinJHMb1jdRG2MYGPdYvE=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -114,7 +114,7 @@
114
114
  "url": "_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js"
115
115
  },
116
116
  {
117
- "hash": "sha256-/I+97B4sBuC4ON9Ytp2crB+yaSXnxgr1wq35P/GZ4O0=",
117
+ "hash": "sha256-Lo7v8nqlOhYydM8OzuMLHllA29+Q76y/PIV+QaB1uyo=",
118
118
  "url": "_content/MindExecution.Shared/js/mind-map-lod-renderer.js"
119
119
  },
120
120
  {
@@ -154,7 +154,7 @@
154
154
  "url": "_content/MindExecution.Shared/js/mind-map-text-lod-system.js"
155
155
  },
156
156
  {
157
- "hash": "sha256-qwNb+PcesE9rgfkjiJrUOWHz/J2vwDUb6wzJ3j5Zj90=",
157
+ "hash": "sha256-TYsOWDglKs9IpaKCvKCpmRhjYRx32npfGkCFbplRM3s=",
158
158
  "url": "_content/MindExecution.Shared/js/mind-map-text-overlay-v2.js"
159
159
  },
160
160
  {
@@ -446,8 +446,8 @@
446
446
  "url": "_framework/MindExecution.Shared.50ptsgk9eg.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-s/mh0EOyp5uxPxzn6b4blKD/zAAA5oKCjFDe0tCgc9g=",
450
- "url": "_framework/MindExecution.Web.hztii75ar1.dll"
449
+ "hash": "sha256-iSV90XAYUoYq7Rfm7gf4tzJBsIy2zbfVfxadAFS8pSY=",
450
+ "url": "_framework/MindExecution.Web.hucq2w2ism.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-pG5Qn7IywPsHJ02D/12EcGeCG65zRYsIE4kfzFBZv3I=",
773
+ "hash": "sha256-l0xPjAbrLfbdmB69gqtRn+JMPssryiM0fh1uDxZ/NNo=",
774
774
  "url": "_framework/blazor.boot.json"
775
775
  },
776
776
  {
@@ -834,7 +834,7 @@
834
834
  "url": "image-manifest.json"
835
835
  },
836
836
  {
837
- "hash": "sha256-RmtnourcT6RHyaHx4CcQLxRf5QOIMFMIfF4OZC0W07k=",
837
+ "hash": "sha256-RLe/OcetCLrQfgXCZMRe1KAmWd4NRvzz9LOOJ0dr7p0=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: EpMH+qVw */
1
+ /* Manifest version: GgmfxCXF */
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