@srgay/cursor-extension 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -178,7 +178,7 @@ window.__cursorMcpFollowup.uninstall() // 卸载面板
178
178
 
179
179
  - `scanStart` / `scanCount`:扫描起始端口与数量(默认从 `8765` 起、共 `5` 个,即 `8765–8769`)。
180
180
  - `probeTimeoutMs`:单端口探测超时(默认 `2500` ms;含一次 `run_command` 读取窗口工作区的往返)。
181
- - `reconnectMs`:自动重连间隔(默认 `3000` ms)。
181
+ - `reconnectMs`:WebSocket 存活探测间隔(默认 `8000` ms;不做周期性重连)。
182
182
 
183
183
  ### 排障
184
184
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srgay/cursor-extension",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "本机 Cursor workbench 增强:MAX Mode 守护、MCP Follow-up 面板、输入法回车修复(支持随 Cursor 启动持久加载,或 CDP 临时注入)。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  styleId: "cursor-mcp-followup-style",
10
10
  panelId: "cursor-mcp-followup-panel",
11
11
  mountScanMs: 600,
12
- reconnectMs: 3000,
12
+ reconnectMs: 8000,
13
13
  scanStart: 8765,
14
14
  scanCount: 5,
15
15
  probeTimeoutMs: 2500,
@@ -391,6 +391,18 @@
391
391
  refreshOptionLabels();
392
392
  }
393
393
 
394
+ // 确保隐藏 select(数据载体)里存在某端口的 option。自定义浮层用 foundPorts 渲染,
395
+ // 但隐藏 select 只在全量扫描时重填;按需扫描新发现的端口必须补进来,否则给原生
396
+ // <select> 设一个不存在的 value 会被静默置空 → 连空端口 offline。
397
+ function ensurePortOption(value) {
398
+ if (!els.portSelect) return;
399
+ const v = String(value);
400
+ const opts = Array.from(els.portSelect.options);
401
+ if (opts.some((o) => o.value === v)) return;
402
+ const custom = opts.find((o) => o.value === config.customValue);
403
+ els.portSelect.insertBefore(h("option", { value: v }), custom || null);
404
+ }
405
+
394
406
  function makeScanIcon() {
395
407
  const svg = svgEl("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true" });
396
408
  svg.appendChild(svgEl("circle", { cx: "11", cy: "11", r: "6", stroke: "currentColor", "stroke-width": "2" }));
@@ -828,7 +840,11 @@
828
840
 
829
841
  // 点选某端口:写回隐藏 select 的 value,关菜单,刷新按钮,再走既有连接逻辑(onPortChange)。
830
842
  function selectPort(port) {
831
- els.portSelect.value = String(port);
843
+ const v = String(port);
844
+ // 关键:浮层从 foundPorts 渲染,可能领先于隐藏 select 的 options;先补齐再赋值,
845
+ // 否则原生 <select> 对不存在的 value 会静默置空,导致连空端口 → offline。
846
+ ensurePortOption(v);
847
+ els.portSelect.value = v;
832
848
  closePortMenu();
833
849
  updatePortButton();
834
850
  onPortChange();
@@ -1575,8 +1591,9 @@
1575
1591
  : "扫描完成:" + from + "–" + to + " 未发现活跃 MCP 端口。";
1576
1592
 
1577
1593
  if (refreshOnly) {
1578
- // 自定义下拉:扫描结果已写入 state;菜单仍展开则用最新结果渲染一次浮层(渲染后保持稳定,
1579
- // 不会在用户点击瞬间重建 DOM,故点选即时生效、无打断、无闪)。
1594
+ // 自定义下拉:先把本次扫到的端口补进隐藏 select(数据载体),保证点选时 value 设得上;
1595
+ // 菜单仍展开则用最新结果渲染浮层(渲染后保持稳定,点选即时生效、无打断、无闪)。
1596
+ found.forEach((p) => ensurePortOption(String(p)));
1580
1597
  if (state.expanded) renderPortMenu();
1581
1598
  return;
1582
1599
  }
@@ -1731,18 +1748,16 @@
1731
1748
  }
1732
1749
  }
1733
1750
 
1734
- // 服务端是「最后连接优先」模型:每个连接绑定其建立时的 session,且只向最后连接推送。
1735
- // 因此面板必须持续保持为「最后连接」,否则会收不到新会话、提交也会作用到已失效的旧会话。
1736
- // 定期重连以抢占活跃连接并同步当前会话;仅在用户正在输入时不打断(输入期间一般无其他端抢占)。
1751
+ // 定期探测当前 WebSocket 是否仍存活。这里不主动重连,避免周期性关闭/新建连接扰动
1752
+ // Cursor renderer 和 MCP 服务端的「最后连接」状态;重连只在启动、手动切端口、聚焦输入框或发送前触发。
1737
1753
  function refreshTick() {
1738
1754
  if (state.scanning) return;
1739
1755
  if (els.portSelect.value === config.customValue) return;
1740
1756
  if (els.customInput === document.activeElement) return;
1741
1757
  if (els.prompt === document.activeElement && els.prompt.value.trim()) return;
1742
1758
 
1743
- // 不做后台扫描(扫描仅发生在启动时与下拉展开期间)。这里只保持连接:
1744
- // 已连且健康 → 发 heartbeat 维持「最后连接」;否则重连选中端口(守卫会拦掉别窗口端口;
1745
- // 端口已关时 onerror 会清归属并置 offline,等待用户展开下拉重新选择本窗口端口)。
1759
+ // 不做后台扫描(扫描仅发生在启动时与下拉展开期间),也不做周期性重连。
1760
+ // OPEN → 发 heartbeat 探活;CONNECTING → 等待;其它状态 → 标记离线,等待用户操作触发连接。
1746
1761
  if (
1747
1762
  state.socket &&
1748
1763
  state.socket.readyState === WebSocket.OPEN &&
@@ -1751,12 +1766,22 @@
1751
1766
  try {
1752
1767
  state.socket.send(JSON.stringify({ type: "heartbeat", timestamp: Date.now() }));
1753
1768
  } catch (error) {
1754
- /* 发送失败则下个周期走重连 */
1769
+ setOptionLabel(els.portSelect.value, "offline");
1770
+ setVisualState("offline", els.portSelect.value + " WebSocket heartbeat 发送失败。");
1755
1771
  }
1756
1772
  return;
1757
1773
  }
1758
1774
 
1759
- connectSelectedPort(true);
1775
+ if (
1776
+ state.socket &&
1777
+ state.socket.readyState === WebSocket.CONNECTING &&
1778
+ state.socketPort === els.portSelect.value
1779
+ ) {
1780
+ return;
1781
+ }
1782
+
1783
+ setOptionLabel(els.portSelect.value, "offline");
1784
+ setVisualState("offline", els.portSelect.value + " WebSocket 未连接或已断开。");
1760
1785
  }
1761
1786
 
1762
1787
  function autoResize() {