@qzsy/dsh-bridge 3.5.2 → 3.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # 更新日志
2
2
 
3
+ ## [v3.5.4] - 2026-11-02
4
+
5
+ ### 🐛 修复
6
+
7
+ - **接住宿主自己弹出的推理菜单**:选完模型后宿主的推理菜单可能已经挂出(层级不一定是 `body` 直接子节点);现在不再依赖我们点「推理等级」,过渡期间直接查找并 sheet 化。
8
+ - **推理菜单向上找定位层**:从每个 `[role="menu"]` 向上找最近的 `fixed/absolute` 承载层,兼容嵌套 portal。
9
+ - **给宿主 300ms 自动衔接窗口**:先等宿主自己切到推理菜单;没出现才回退到「重开父菜单 → 点推理等级」。
10
+ - 这样选完模型会稳定进入推理 sheet;当前模型/不同模型两条路径都覆盖。
11
+
12
+ ### 🧪 验证
13
+
14
+ - 独立宿主模拟 4/4;待安装后验证真实 DSH。
15
+
16
+
17
+ ## [v3.5.3] - 2026-11-02
18
+
19
+ ### 🐛 修复
20
+
21
+ - **推理菜单延迟挂载也能接住**:宿主的推理菜单 portal 在过渡期可能是 0 尺寸/延迟一帧才可测量,原来只在 `MutationObserver` 触发时找一次会漏掉;现在点击「推理等级」后每 50ms 轮询、最多 1.2s,直到推理 sheet 出现。
22
+ - **推理 portal 识别放宽**:不再要求宽高 > 0,只要求 `display` 可见且 `position: fixed/absolute`,避免透明过渡期被误判为不存在。
23
+ - 清掉旧 sheet 快照和过渡遮罩的时机保持到推理 sheet 真正套用之后。
24
+
25
+ ### 🧪 验证
26
+
27
+ - 独立宿主模拟 4/4;待安装后验证真实 DSH 的完整链路。
28
+
29
+
3
30
  ## [v3.5.2] - 2026-11-02
4
31
 
5
32
  ### 🐛 修复
package/client/client.js CHANGED
@@ -677,23 +677,25 @@ function setupMobileDrawer() {
677
677
  };
678
678
  const findReasoningPortal = () => {
679
679
  if (!isMobileViewport()) return null;
680
- const candidates = [];
681
- for (const el of document.body.children) {
682
- if (!(el instanceof HTMLElement)) continue;
683
- if (el === modelBackdrop || el === modelClone || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
684
- if (el.hasAttribute("data-qzsy-model-sheet") || el.hasAttribute("data-qzsy-model-sheet-clone")) continue;
685
- if (!visiblePortal(el)) continue;
686
- const s = getComputedStyle(el);
687
- if (s.position !== "fixed" && s.position !== "absolute") continue;
688
- const menu = el.querySelector('[role="menu"]');
689
- if (!menu) continue;
680
+ const roots = /* @__PURE__ */ new Set();
681
+ for (const menu of document.querySelectorAll('[role="menu"]')) {
690
682
  const radios = menu.querySelectorAll('button[role="menuitemradio"], [role="menuitemradio"], input[type="radio"]').length;
691
683
  if (radios < 1) continue;
692
684
  const text = menu.innerText || "";
693
685
  if (/搜索模型|Search model|DeepSeek|claude|模型与推理等级/i.test(text)) continue;
694
- candidates.push(el);
686
+ let el = menu;
687
+ while (el && el !== document.body) {
688
+ if (el.hasAttribute("data-qzsy-model-sheet") || el.hasAttribute("data-qzsy-model-sheet-clone")) break;
689
+ const s = getComputedStyle(el);
690
+ if (s.display === "none") break;
691
+ if (s.position === "fixed" || s.position === "absolute") {
692
+ roots.add(el);
693
+ break;
694
+ }
695
+ el = el.parentElement;
696
+ }
695
697
  }
696
- return candidates[candidates.length - 1] || null;
698
+ return [...roots].pop() || null;
697
699
  };
698
700
  const hostBackdropPresent = () => {
699
701
  for (const el of document.body.children) {
@@ -930,6 +932,12 @@ function setupMobileDrawer() {
930
932
  document.body.appendChild(modelClone);
931
933
  window.setTimeout(() => {
932
934
  if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
935
+ const hostReason = findReasoningPortal();
936
+ if (hostReason) {
937
+ reasoningStep = true;
938
+ applyModelSheet(hostReason);
939
+ return;
940
+ }
933
941
  const findReasonCell = () => [...document.querySelectorAll('button[role="menuitem"]')].find((b) => /推理等级|Reasoning effort|Reasoning level|推理强度/i.test(b.innerText || ""));
934
942
  const clickReasonCell = (cell) => {
935
943
  if (!cell) return;
@@ -938,13 +946,24 @@ function setupMobileDrawer() {
938
946
  cell.click();
939
947
  } catch {
940
948
  }
941
- window.setTimeout(() => {
949
+ let reasonTries = 0;
950
+ const applyReasonPortal = () => {
942
951
  if (disposed || token !== modelAdvanceToken) return;
943
- if (!modelPortal) {
952
+ if (modelPortal && modelPortal.getAttribute("data-qzsy-model-step") === "reasoning") return;
953
+ const found = findReasoningPortal();
954
+ if (found) {
955
+ applyModelSheet(found);
956
+ return;
957
+ }
958
+ reasonTries += 1;
959
+ if (reasonTries > 24) {
944
960
  cancelAdvanceOverlay();
945
961
  clearModelSheet();
962
+ return;
946
963
  }
947
- }, 1200);
964
+ window.setTimeout(applyReasonPortal, 50);
965
+ };
966
+ window.setTimeout(applyReasonPortal, 30);
948
967
  };
949
968
  let attempts = 0;
950
969
  const pollReasonCell = () => {
@@ -987,7 +1006,7 @@ function setupMobileDrawer() {
987
1006
  return;
988
1007
  }
989
1008
  reopenParent();
990
- }, 140);
1009
+ }, 300);
991
1010
  };
992
1011
  const syncModelSheet = () => {
993
1012
  if (disposed) return;
@@ -996,12 +1015,11 @@ function setupMobileDrawer() {
996
1015
  return;
997
1016
  }
998
1017
  if (modelAdvancing) {
999
- if (reasoningStep) {
1000
- const next = findReasoningPortal();
1001
- if (next) {
1002
- applyModelSheet(next);
1003
- return;
1004
- }
1018
+ const next = findReasoningPortal();
1019
+ if (next) {
1020
+ reasoningStep = true;
1021
+ applyModelSheet(next);
1022
+ return;
1005
1023
  }
1006
1024
  if (modelPortal && !visiblePortal(modelPortal)) modelPortal = null;
1007
1025
  return;
package/client/mobile.js CHANGED
@@ -501,24 +501,26 @@ export function setupMobileDrawer() {
501
501
 
502
502
  const findReasoningPortal = () => {
503
503
  if (!isMobileViewport()) return null;
504
- const candidates = [];
505
- for (const el of document.body.children) {
506
- if (!(el instanceof HTMLElement)) continue;
507
- if (el === modelBackdrop || el === modelClone || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
508
- if (el.hasAttribute('data-qzsy-model-sheet') || el.hasAttribute('data-qzsy-model-sheet-clone')) continue;
509
- if (!visiblePortal(el)) continue;
510
- const s = getComputedStyle(el);
511
- if (s.position !== 'fixed' && s.position !== 'absolute') continue;
512
- const menu = el.querySelector('[role="menu"]');
513
- if (!menu) continue;
504
+ const roots = new Set();
505
+ for (const menu of document.querySelectorAll('[role="menu"]')) {
514
506
  const radios = menu.querySelectorAll('button[role="menuitemradio"], [role="menuitemradio"], input[type="radio"]').length;
515
507
  if (radios < 1) continue;
516
508
  const text = menu.innerText || '';
517
- // 推理等级菜单只含 Off / Low / High / Max 等档位,不含模型搜索与模型分组
518
509
  if (/搜索模型|Search model|DeepSeek|claude|模型与推理等级/i.test(text)) continue;
519
- candidates.push(el);
510
+ // 推理菜单可能不是 body 直接子节点,向上找最近的 fixed/absolute 承载层
511
+ let el = menu;
512
+ while (el && el !== document.body) {
513
+ if (el.hasAttribute('data-qzsy-model-sheet') || el.hasAttribute('data-qzsy-model-sheet-clone')) break;
514
+ const s = getComputedStyle(el);
515
+ if (s.display === 'none') break;
516
+ if (s.position === 'fixed' || s.position === 'absolute') {
517
+ roots.add(el);
518
+ break;
519
+ }
520
+ el = el.parentElement;
521
+ }
520
522
  }
521
- return candidates[candidates.length - 1] || null;
523
+ return [...roots].pop() || null;
522
524
  };
523
525
 
524
526
  const hostBackdropPresent = () => {
@@ -756,19 +758,36 @@ export function setupMobileDrawer() {
756
758
 
757
759
  window.setTimeout(() => {
758
760
  if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
761
+ // 先给宿主一点时间自己切到推理菜单;出现了就直接 sheet 化
762
+ const hostReason = findReasoningPortal();
763
+ if (hostReason) {
764
+ reasoningStep = true;
765
+ applyModelSheet(hostReason);
766
+ return;
767
+ }
759
768
  const findReasonCell = () => [...document.querySelectorAll('button[role="menuitem"]')].find((b) => /推理等级|Reasoning effort|Reasoning level|推理强度/i.test(b.innerText || ''));
760
769
  const clickReasonCell = (cell) => {
761
770
  if (!cell) return;
762
771
  reasoningStep = true;
763
772
  try { cell.click(); } catch { /* 忽略 */ }
764
- window.setTimeout(() => {
773
+ let reasonTries = 0;
774
+ const applyReasonPortal = () => {
765
775
  if (disposed || token !== modelAdvanceToken) return;
766
- if (!modelPortal) {
767
- // 推理菜单没出现:撤掉 overlay,避免卡在半路
776
+ if (modelPortal && modelPortal.getAttribute('data-qzsy-model-step') === 'reasoning') return;
777
+ const found = findReasoningPortal();
778
+ if (found) {
779
+ applyModelSheet(found);
780
+ return;
781
+ }
782
+ reasonTries += 1;
783
+ if (reasonTries > 24) {
768
784
  cancelAdvanceOverlay();
769
785
  clearModelSheet();
786
+ return;
770
787
  }
771
- }, 1200);
788
+ window.setTimeout(applyReasonPortal, 50);
789
+ };
790
+ window.setTimeout(applyReasonPortal, 30);
772
791
  };
773
792
  let attempts = 0;
774
793
  const pollReasonCell = () => {
@@ -810,7 +829,7 @@ export function setupMobileDrawer() {
810
829
  return;
811
830
  }
812
831
  reopenParent();
813
- }, 140);
832
+ }, 300);
814
833
  };
815
834
 
816
835
  const syncModelSheet = () => {
@@ -820,12 +839,12 @@ export function setupMobileDrawer() {
820
839
  return;
821
840
  }
822
841
  if (modelAdvancing) {
823
- if (reasoningStep) {
824
- const next = findReasoningPortal();
825
- if (next) {
826
- applyModelSheet(next);
827
- return;
828
- }
842
+ // 宿主可能在选中模型后自己就把推理菜单挂出来了;先直接接住
843
+ const next = findReasoningPortal();
844
+ if (next) {
845
+ reasoningStep = true;
846
+ applyModelSheet(next);
847
+ return;
829
848
  }
830
849
  if (modelPortal && !visiblePortal(modelPortal)) modelPortal = null;
831
850
  return;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@qzsy/dsh-bridge",
3
- "version": "3.5.2",
3
+ "version": "3.5.4",
4
4
  "description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
5
- "releaseNotes": "【v3.5.2】模型 → 思考级别连招稳定版\\n• 不同模型:直接复用宿主保留的父菜单,点「推理等级」进入 sheet\\n• 当前模型:先收起模型子菜单,再打开父菜单进入推理等级\\n• 保持旧 sheet 快照过渡,推理 sheet 出现后清理,不再卡在半路",
5
+ "releaseNotes": "【v3.5.4】模型 → 思考级别连招\\n• 接住宿主自己弹出的推理菜单,嵌套 portal 也能 sheet 化\\n• 给宿主 300ms 自动衔接窗口,没出现才回退点「推理等级」\\n• 当前模型 / 不同模型两条路径统一,推理选完才关闭",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
8
8
  "exports": {