@srgay/cursor-extension 1.0.5 → 1.0.6

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": "@srgay/cursor-extension",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "本机 Cursor workbench 增强:MAX Mode 守护、MCP Follow-up 面板、输入法回车修复(支持随 Cursor 启动持久加载,或 CDP 临时注入)。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,9 @@
27
27
  pyCmd: "__MCP_PY__",
28
28
  // run_command 拉取提示词的兜底超时(毫秒)。
29
29
  promptLoadTimeoutMs: 4000,
30
+ // onclose 断开后「自动重扫一次」的最小间隔(毫秒)。仅用于事件驱动的防抖,
31
+ // 防止「连不上→断开→再扫」抖动成风暴;不是后台定时轮询。
32
+ autoRescanMinGapMs: 4000,
30
33
  };
31
34
 
32
35
  const state = {
@@ -69,6 +72,10 @@
69
72
  // 自定义端口下拉的文档级监听(点空白处 / Esc 关闭菜单),uninstall 时移除。
70
73
  onDocPointerDown: null,
71
74
  onDocKeydown: null,
75
+ // 上次扫描的时间戳,供 onclose 断开后的「防抖自动重扫」判断间隔(事件驱动,非定时)。
76
+ lastAutoScanAt: 0,
77
+ // 正在对某端口做「守卫拒绝前的归属复核」探测,避免对同一端口重复探测。
78
+ revalidating: null,
72
79
  };
73
80
 
74
81
  const SVG_NS = "http://www.w3.org/2000/svg";
@@ -1523,6 +1530,7 @@
1523
1530
  const refreshOnly = !!(opts && opts.refreshOnly);
1524
1531
  if (state.scanning) return;
1525
1532
  state.scanning = true;
1533
+ state.lastAutoScanAt = Date.now();
1526
1534
  if (els.scanBtn) {
1527
1535
  els.scanBtn.classList.add("scanning");
1528
1536
  els.scanBtn.disabled = true;
@@ -1647,12 +1655,41 @@
1647
1655
  }
1648
1656
  }
1649
1657
 
1658
+ // ③ 断开后「自动重扫一次」:事件驱动 + 防抖(非定时轮询)。借扫描重新探测各端口归属、
1659
+ // 刷新缓存并连回本窗口端口;防抖避免「连不上→断开→再扫」反复抖动成风暴。
1660
+ function autoRescan() {
1661
+ if (state.scanning) return;
1662
+ if (els.portSelect.value === config.customValue) return;
1663
+ if (Date.now() - state.lastAutoScanAt < config.autoRescanMinGapMs) return;
1664
+ scanPorts();
1665
+ }
1666
+
1667
+ // ④ 守卫判定「别的窗口」后,不全信可能过时的缓存:对该端口做一次性探测复核真实归属,
1668
+ // 刷新缓存;仅当探测确认「确属本窗口」时才重连,否则保持离线等待(不轮询)。
1669
+ function revalidatePortOwnership(port, ws) {
1670
+ const key = String(port);
1671
+ if (state.revalidating === key) return;
1672
+ state.revalidating = key;
1673
+ probePort(key, config.probeTimeoutMs).then((res) => {
1674
+ if (state.revalidating === key) state.revalidating = null;
1675
+ if (res.workspaceFolders) state.portWorkspaces[key] = res.workspaceFolders;
1676
+ else delete state.portWorkspaces[key];
1677
+ if (res.workspaceLabel) state.portLabels[key] = res.workspaceLabel;
1678
+ else delete state.portLabels[key];
1679
+ // 用户没切走端口、探测存活且复核后确属本窗口,才连接(连接层会再次走守卫兜底)。
1680
+ if (els.portSelect.value === key && res.alive && isPortMine(key, ws)) {
1681
+ connectSelectedPort(false);
1682
+ }
1683
+ });
1684
+ }
1685
+
1650
1686
  function connectSelectedPort(auto) {
1651
1687
  const port = els.portSelect.value;
1652
1688
  if (port === config.customValue) return;
1653
1689
 
1654
1690
  // 守卫:能确定当前窗口、且该端口已知属于别的窗口时不连接,
1655
- // 避免误连到其它窗口的实例或常驻 feedback 实例(等本窗口端口被识别后由 refreshTick 重扫接管)。
1691
+ // 避免误连到其它窗口的实例或常驻 feedback 实例(随后由 revalidatePortOwnership 探测复核,
1692
+ // 若该端口已换回本窗口则立刻重连接管)。
1656
1693
  const ws = getWorkspacePath();
1657
1694
  const known = !!(state.portWorkspaces[port] || state.portLabels[port]);
1658
1695
  if (ws && known && !isPortMine(port, ws)) {
@@ -1665,6 +1702,9 @@
1665
1702
  }
1666
1703
  setOptionLabel(port, "other window");
1667
1704
  setVisualState("offline", port + " 属于其它窗口,等待本窗口的 MCP 会话…");
1705
+ // ④ 缓存可能已过时(如该端口刚换回本窗口):异步探测复核,确属本窗口则立刻重连,
1706
+ // 避免「端口已是自己的、却被 stale 缓存永久判为别人的」而连不上。
1707
+ revalidatePortOwnership(port, ws);
1668
1708
  return;
1669
1709
  }
1670
1710
 
@@ -1734,6 +1774,12 @@
1734
1774
  if (seq !== state.connectSeq) return;
1735
1775
  state.socket = null;
1736
1776
  if (state.currentState === "processing") return;
1777
+ // ① 非 4004 的断开意味着端口可能已关闭/换主:清除该端口归属缓存,从根上消除 stale,
1778
+ // 避免下次连接被过时归属(守卫)误判。4004 是「连上了但无会话」,端口归属仍有效,不清。
1779
+ if (event.code !== 4004) {
1780
+ delete state.portWorkspaces[port];
1781
+ delete state.portLabels[port];
1782
+ }
1737
1783
  setOptionLabel(port, event.code === 4004 ? "no session" : "offline");
1738
1784
  setVisualState(
1739
1785
  "offline",
@@ -1741,6 +1787,8 @@
1741
1787
  ? port + " 已连接到 MCP,但当前没有 active session。"
1742
1788
  : port + " WebSocket 已断开。"
1743
1789
  );
1790
+ // ③ 断开后自动重扫一次(事件驱动 + 防抖):刷新各端口归属并连回本窗口端口;不聚焦也能自愈。
1791
+ if (event.code !== 4004) autoRescan();
1744
1792
  };
1745
1793
  } catch (error) {
1746
1794
  setOptionLabel(port, "offline");
@@ -1908,7 +1956,11 @@
1908
1956
  suppressAutoSubmit();
1909
1957
  });
1910
1958
  els.prompt.addEventListener("focus", () => {
1911
- if (els.portSelect.value !== config.customValue) connectSelectedPort(true);
1959
+ if (els.portSelect.value === config.customValue) return;
1960
+ // ② 离线时聚焦:缓存可能过时导致守卫挡住直连,改为重扫一次刷新各端口归属并连回本窗口端口
1961
+ // (事件驱动 + 防抖);非离线仍走原「重连抢回最后连接」逻辑。
1962
+ if (state.currentState === "offline") autoRescan();
1963
+ else connectSelectedPort(true);
1912
1964
  });
1913
1965
  // 跟踪输入法组字状态:组字期间(含上屏候选词的回车)不触发发送。
1914
1966
  els.prompt.addEventListener("compositionstart", () => {