@mrrisega/dsh-remote 0.7.0-beta.7 → 0.7.0
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/clients/dsh-remote/dsh-bridge.mjs +17 -17
- package/clients/dsh-remote/dsh-events.mjs +6 -6
- package/clients/dsh-remote/e2ee-client.mjs +7 -7
- package/clients/dsh-remote/e2ee-shim-script.js +9 -9
- package/clients/dsh-remote/mobile-adapter.mjs +21 -21
- package/clients/dsh-remote/test/bridge-gzip.test.mjs +1 -1
- package/clients/dsh-remote/test/device-registration-report.test.mjs +1 -1
- package/clients/dsh-remote/test/dsh-events.test.mjs +10 -10
- package/clients/dsh-remote/test/e2ee-bridge.test.mjs +4 -4
- package/clients/dsh-remote/test/e2ee-client.test.mjs +7 -7
- package/clients/dsh-remote/test/e2ee-shim.test.mjs +1 -1
- package/clients/dsh-remote/test/e2ee-wecrypto-parity.test.mjs +1 -1
- package/clients/dsh-remote/test/harness-cookie-authority.test.mjs +4 -4
- package/clients/dsh-remote/test/machine-fingerprint.test.mjs +2 -2
- package/clients/dsh-remote/test/mobile-adapter-guards.test.mjs +6 -6
- package/clients/dsh-remote/test/mobile-adapter-image.test.mjs +1 -1
- package/clients/dsh-remote/test/mobile-adapter-runtime.test.mjs +5 -5
- package/clients/dsh-remote/test/upstream-discovery.test.mjs +1 -1
- package/clients/dsh-remote/test/wechat-channel.test.mjs +5 -5
- package/clients/dsh-remote/test/wechat-runtime.test.mjs +26 -26
- package/clients/dsh-remote/test/wechat-tier-source.test.mjs +2 -2
- package/clients/dsh-remote/wechat-channel.mjs +9 -9
- package/clients/dsh-remote/wechat-runtime.mjs +38 -38
- package/docs/telemetry.md +3 -3
- package/dsh-setup.mjs +63 -26
- package/package.json +2 -2
- package/packages/dsh-remote-web/lib/client.js +56 -56
- package/packages/dsh-remote-web/lib/fix-prompt.js +1 -1
- package/packages/dsh-remote-web/lib/index.js +43 -43
- package/packages/dsh-remote-web/lib/tarball-update.js +1 -1
- package/packages/dsh-remote-web/package.json +1 -1
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/dsh-bridge.mjs +17 -17
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/dsh-events.mjs +6 -6
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/e2ee-client.mjs +7 -7
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/e2ee-shim-script.js +9 -9
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/mobile-adapter.mjs +21 -21
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/wechat-channel.mjs +9 -9
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/wechat-runtime.mjs +38 -38
- package/packages/dsh-remote-web/runtime/clients/dsh-web/native.html +5 -5
- package/packages/dsh-remote-web/runtime/dsh-setup.mjs +63 -26
- package/packages/dsh-remote-web/test/account-switch-device.test.mjs +2 -2
- package/packages/dsh-remote-web/test/activation-single-point.test.mjs +1 -1
- package/packages/dsh-remote-web/test/announcements-ui.test.mjs +1 -1
- package/packages/dsh-remote-web/test/connect-loop.test.mjs +1 -1
- package/packages/dsh-remote-web/test/connect-ui.test.mjs +2 -2
- package/packages/dsh-remote-web/test/desktop-compat.test.mjs +4 -4
- package/packages/dsh-remote-web/test/diag-report.test.mjs +1 -1
- package/packages/dsh-remote-web/test/fix-prompt.test.mjs +2 -2
- package/packages/dsh-remote-web/test/harness-restart.test.mjs +1 -1
- package/packages/dsh-remote-web/test/install-profile-targets.test.mjs +6 -4
- package/packages/dsh-remote-web/test/installer-cli.test.mjs +1 -1
- package/packages/dsh-remote-web/test/launchd-domain.test.mjs +2 -2
- package/packages/dsh-remote-web/test/patch-activation.test.mjs +2 -2
- package/packages/dsh-remote-web/test/picker-pin.test.mjs +1 -1
- package/packages/dsh-remote-web/test/plugin-manifest.test.mjs +2 -2
- package/packages/dsh-remote-web/test/quota-absent.test.mjs +4 -4
- package/packages/dsh-remote-web/test/remote-access-ui.test.mjs +2 -2
- package/packages/dsh-remote-web/test/settings-entry-placement.test.mjs +6 -6
- package/packages/dsh-remote-web/test/settings-entry.test.mjs +2 -2
- package/packages/dsh-remote-web/test/settings-open-path.test.mjs +4 -4
- package/packages/dsh-remote-web/test/setup-output.test.mjs +32 -9
- package/packages/dsh-remote-web/test/tarball-update.test.mjs +1 -1
- package/packages/dsh-remote-web/test/telemetry.test.mjs +4 -4
- package/packages/dsh-remote-web/test/uninstall-safety.test.mjs +2 -2
- package/packages/dsh-remote-web/test/watcher-upstream-port.test.mjs +1 -1
- package/packages/dsh-remote-web/test/wechat-bot-ui.test.mjs +7 -7
- package/packages/dsh-remote-web/test/windows-compat.test.mjs +5 -5
- package/scripts/test-net-guard.cjs +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 运行时语义(docs/e2ee-protocol.md §4/§5/§6/§9;字节约定对齐 clients/dsh-remote/
|
|
5
5
|
* e2ee-client.mjs「规范化决定 1–6」与 native.html Phase-3 手机端):
|
|
6
6
|
* 0. document-start:读一次性交接单(sessionStorage dsh-e2ee-handover)→ 立即删除,
|
|
7
|
-
*
|
|
7
|
+
* 用内存重建「已与桥端探测互认」的会话(仅 SHK+saltH+sessId;MK/密码不下放)。
|
|
8
8
|
* 1. 有会话 → patch window.fetch / window.WebSocket:命中 §4.5 策略表(/api 排除
|
|
9
9
|
* SSE、/sidebar、/git、/pet;channel 形态 /remote/{api|sidebar|git|pet}/* 与
|
|
10
10
|
* device 形态 /remote/<dev>/… 归一后同样判定)→ HTTP 封 v2 信封发送、响应信封
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
return out;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
/** 一个解锁会话(
|
|
135
|
+
/** 一个解锁会话(由交接单重建;与桥端已探测互认的会话同 sessId)。密钥仅内存。 */
|
|
136
136
|
function SeSession(o) {
|
|
137
137
|
if (!o || typeof o.sessId !== "string" || !SE_SESS_RE.test(o.sessId)) throw seErr("bad_session", "e2ee: sessId 非法");
|
|
138
138
|
if (!o.shk || o.shk.length !== SE_KEY_BYTES || !o.saltH || o.saltH.length !== SE_KEY_BYTES) throw seErr("bad_session", "e2ee: SHK/saltH 非法");
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
this.saltH = o.saltH;
|
|
142
142
|
this.profile = typeof o.profile === "string" ? o.profile : "";
|
|
143
143
|
this.epoch = Number(o.epoch) || 0;
|
|
144
|
-
this.verified = true; // 交接单来源 =
|
|
144
|
+
this.verified = true; // 交接单来源 = 已通过探测的解锁会话
|
|
145
145
|
}
|
|
146
146
|
/** 子密钥(node keyFor / wc keyBytesFor 同构)。 */
|
|
147
147
|
SeSession.prototype.keyBytesFor = function (opt) {
|
|
@@ -292,7 +292,7 @@
|
|
|
292
292
|
/**
|
|
293
293
|
* 把**根路径**的受保护请求改写成"设备作用域"形态:`/api/x` → `/remote/<deviceId>/api/x`。
|
|
294
294
|
*
|
|
295
|
-
* 为什么必须(2026-09-26
|
|
295
|
+
* 为什么必须(2026-09-26 线上故障,nginx access/error 日志为证):
|
|
296
296
|
* 官方客户端把 mux WS 的地址固定按 **`location.origin`** 拼成 `/api/remote.mux`
|
|
297
297
|
* (@deepseek-ai/dsh-api-gateway/lib/client.js:544 的 remoteStreamUrl())—— 它**永远**不带设备前缀,
|
|
298
298
|
* 于是中继只能靠 **`dsh_device` cookie** 来判"这是哪台设备"(relay-router/src/index.mjs:626-627
|
|
@@ -424,7 +424,7 @@
|
|
|
424
424
|
if (typeof console !== "undefined" && console.warn) console.warn("[dsh-e2ee-shim]", message);
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
/* ---- 会话失效自愈(2026-09-26
|
|
427
|
+
/* ---- 会话失效自愈(2026-09-26 线上故障:unknown_session 502 + 刷新也好不了) -------------
|
|
428
428
|
链路:手机镜像页 → 加密信封 → 中继透明转发 → 桥端 e2ee-client 查会话表 → 查不到就回
|
|
429
429
|
**明文** 502 + `x-dsh-e2ee-error: unknown_session`(桥端会话表是进程内存 + 24h TTL + CAP=32 LRU,
|
|
430
430
|
桥重启/被挤掉之后就没了)。而手机侧靠 **localStorage 持久交接单**长期复用同一个 sessId
|
|
@@ -604,7 +604,7 @@
|
|
|
604
604
|
inner.onopen = function (ev) { self._state = 1; self._opened = true; seNoteWsOpened(); if (typeof self._onopen === "function") self._onopen(ev || { type: "open", target: self }); };
|
|
605
605
|
inner.onerror = function (ev) {
|
|
606
606
|
/* 握手上就失败(error 早于任何 open)→ 让用户知道"加密数据通道没建立"。
|
|
607
|
-
这是 2026-09-26
|
|
607
|
+
这是 2026-09-26 故障里最缺的一条信号:mux 被 404 掐掉时页面**一声不响**,
|
|
608
608
|
用户只看到所有数据都停住。 */
|
|
609
609
|
if (!self._opened) seNoteWsHandshakeFail(self._meta && self._meta.wsLabel, "error");
|
|
610
610
|
if (typeof self._onerror === "function") self._onerror(ev || { type: "error", target: self });
|
|
@@ -715,7 +715,7 @@
|
|
|
715
715
|
var base = ctx.origin || (typeof window !== "undefined" ? window.location.href : undefined);
|
|
716
716
|
var uo = new URL(url, base);
|
|
717
717
|
if (seSameWebContext(uo, ctx.origin)) {
|
|
718
|
-
/* ★ 设备作用域改写(2026-09-26
|
|
718
|
+
/* ★ 设备作用域改写(2026-09-26 故障):官方客户端把 mux WS 固定拼成**根路径**
|
|
719
719
|
`/api/remote.mux`(dsh-api-gateway/lib/client.js:544 用 location.origin 拼),
|
|
720
720
|
于是中继只能靠 `dsh_device` cookie 判设备;cookie 一过期/缺失就是 404(upgrade 被
|
|
721
721
|
rejectUpgrade 掐掉,nginx 只看到 009)→ E2EE 会话永远建不起来 → 之后每个受保护请求
|
|
@@ -730,7 +730,7 @@
|
|
|
730
730
|
/* ★ 关键:改写结果要**留住**。原来不改写分支 return 的是原始 url,等于把
|
|
731
731
|
`/remote/<dev>/api/remote.mux` 又丢回根路径 —— 那正是 404 的来源。 */
|
|
732
732
|
passUrl = so.href;
|
|
733
|
-
/* ⚠️ 必须同时要求"有会话":URL 改写在无会话时也要做(这是 2026-09-26
|
|
733
|
+
/* ⚠️ 必须同时要求"有会话":URL 改写在无会话时也要做(这是 2026-09-26 故障的正题),
|
|
734
734
|
但**加密**没有会话就做不了 —— 少了这个判断,未解锁的页面上任何
|
|
735
735
|
`new WebSocket("/api/…")` 都会在 ctx.sess.sessId 上抛 TypeError,
|
|
736
736
|
把整个页面打挂(比原来"不装补丁"还糟)。 */
|
|
@@ -948,7 +948,7 @@
|
|
|
948
948
|
try { lsSess = seSessionOfHandover(lsRaw); } catch (e) { lsSess = null; }
|
|
949
949
|
if (lsSess) {
|
|
950
950
|
_install(lsSess);
|
|
951
|
-
/* ⚠️ 0.6.18 起这句文案不再无条件说"加密好好的"(2026-09-26
|
|
951
|
+
/* ⚠️ 0.6.18 起这句文案不再无条件说"加密好好的"(2026-09-26 故障的教训):
|
|
952
952
|
从 localStorage 恢复的会话**没法在本机验证**桥端是否还认它(桥端会话表是进程内存 +
|
|
953
953
|
24h TTL + CAP=32 LRU,桥重启/被挤掉之后这张交接单就是死票)。真实校验只能等
|
|
954
954
|
第一个受保护请求 —— 那一步由 seNoteE2eeError 兜底:一旦桥端回 unknown_session,
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
* - JS 用结构探测(frame → 子列 → 打 dsh-ma-* 稳定类)承载布局,不以 hashed 类为唯一钩子;
|
|
53
53
|
* - 命中不到 frame 时静默跳过,页面保持官方默认,绝不报错/破坏。
|
|
54
54
|
*
|
|
55
|
-
* ⚠️ 官方 0.1.5-rc.2 起第三列改名(2026-09-19
|
|
55
|
+
* ⚠️ 官方 0.1.5-rc.2 起第三列改名(2026-09-19 线上故障,Windows 用户先撞到):
|
|
56
56
|
* 第三列 `pI_x6G_detailsCol` → `pI_x6G_rightbarCol`、`data-details-collapsed` →
|
|
57
57
|
* `data-rightbar-collapsed`,且折叠时不再渲染拖拽把手、`overlayLayer`(data-shell-overlay,
|
|
58
58
|
* inset:0 全屏)始终留在 frame 末尾。旧实现"详情抽屉兜底 = 取 frame 最后一个子元素"于是把
|
|
@@ -153,10 +153,10 @@ const STYLE = `
|
|
|
153
153
|
══════════════════════════════════════════════════════════════════════════════
|
|
154
154
|
🚫 这里**故意什么都不写** —— 第三列(官方右栏)的显示/隐藏完全交还给官方。
|
|
155
155
|
══════════════════════════════════════════════════════════════════════════════
|
|
156
|
-
历史(2026-09-27
|
|
156
|
+
历史(2026-09-27 根治,用户拍板 B 方案):
|
|
157
157
|
0.6.13 起我们把这列改造成自绘浮层 —— position:fixed; right:0; width:min(92vw,400px);
|
|
158
158
|
transform:translateX(103%),再用 html.dsh-ma-details-open … { transform:none } 滑进来。
|
|
159
|
-
因为"自绘浮层会白框压屏"(2026-09-19
|
|
159
|
+
因为"自绘浮层会白框压屏"(2026-09-19 故障),又加了一道很严的闸门(必须"用户 8 秒内
|
|
160
160
|
在正文里点过"才允许滑入);而 better-sidebar 这类插件只翻**它自己**的 store、官方
|
|
161
161
|
cols.rightbar 永远是 0 → 闸门永远不开 → 于是又在悬浮菜单里补了颗「打开右侧栏」按钮。
|
|
162
162
|
|
|
@@ -188,7 +188,7 @@ const STYLE = `
|
|
|
188
188
|
opacity: 0; pointer-events: none;
|
|
189
189
|
transition: opacity .22s ease;
|
|
190
190
|
}
|
|
191
|
-
/* ⚠️ 拦截点击**只认 dsh-ma-scrim-on 这一个闸**(2026-09-19
|
|
191
|
+
/* ⚠️ 拦截点击**只认 dsh-ma-scrim-on 这一个闸**(2026-09-19 回顾):
|
|
192
192
|
旧版还并列了一个「html.dsh-ma-details-open div.dsh-ma-scrim」规则,于是"详情展开判定出错"
|
|
193
193
|
会直接变成"整屏被遮罩拦截"——官方 0.1.5 把第三列改名 rightbar 后就是这个死局。
|
|
194
194
|
现在判定收口在 JS 里(必须有一个**真的在视口内、且不是全屏的**抽屉),
|
|
@@ -524,7 +524,7 @@ const SCRIPT = `(() => {
|
|
|
524
524
|
"use strict";
|
|
525
525
|
|
|
526
526
|
/* ===== 【0.6.16 已删除】首屏加载提示层(0.6.14 引入,0.6.15 仍在)=====
|
|
527
|
-
|
|
527
|
+
为什么删掉(用户实测,2026-09-25):「界面都已经加载完了,十几秒就把插件的接口加载好了,
|
|
528
528
|
但这个遮罩一直不消失」—— 截图里官方 UI 就在下面透出来,秒表走到 187 秒。
|
|
529
529
|
|
|
530
530
|
根因是**设计错误**,不是某个选择器写错:
|
|
@@ -535,7 +535,7 @@ const SCRIPT = `(() => {
|
|
|
535
535
|
官方改版、会话尚未挂载输入框、或前缀变化时两组信号全不命中 → 层就永远留着。
|
|
536
536
|
· 当时我给自己写的安慰是「pointer-events:none,万一移除失效也绝不挡住点击」——
|
|
537
537
|
可对用户来说,**盖住视线和进不去没有区别**:刷新也一样,每次都要等 4 分钟兜底。
|
|
538
|
-
|
|
538
|
+
结论(用户要求):**直接删掉这个功能**。首屏反馈交给官方自己的转圈;
|
|
539
539
|
我们不再在用户页面上放任何会覆盖整屏的东西 —— 这类层一旦判断失误就是"永远进不去",
|
|
540
540
|
代价远大于它带来的提示价值(何况限速放开后首屏本来就快了)。
|
|
541
541
|
|
|
@@ -579,7 +579,7 @@ const SCRIPT = `(() => {
|
|
|
579
579
|
|
|
580
580
|
/* ===== 「加载疑似没完成」看门狗(2026-09-25,替代被整块删除的首屏提示层)=====
|
|
581
581
|
那个提示层是全屏覆盖 + 启发式退出 —— 判断错一次就把**已经加载完**的界面永久盖住
|
|
582
|
-
|
|
582
|
+
(用户实测:秒表走到 187 秒,刷新也一样)。所以这里换一条**安全的失败方向**:
|
|
583
583
|
|
|
584
584
|
· 解析期**不创建任何 DOM**(只有几个变量 + 一个定时器)—— 页面还没过主机门,
|
|
585
585
|
更不该有任何覆盖层;
|
|
@@ -614,13 +614,13 @@ const SCRIPT = `(() => {
|
|
|
614
614
|
return Date.now();
|
|
615
615
|
}
|
|
616
616
|
/**
|
|
617
|
-
*
|
|
617
|
+
* 镜像页就绪耗时(毫秒)——**约定见 docs/TELEMETRY-CONTRACT.md / 迭代冻结文档 §2.3**:
|
|
618
618
|
* · 起点 = **导航开始**(performance.now() 的零点 = performance.timeOrigin ≈ 导航开始),
|
|
619
619
|
* 所以一条值同时覆盖 DNS+TLS+HTML TTFB + 解析 + 界面就绪;
|
|
620
620
|
* · **不能**用 Date.now() - maLoadWatch.startedAt:那个起点是"注入脚本解析时刻",会**漏掉
|
|
621
621
|
* HTML TTFB**(HAR 实测那 79 秒里 TTFB 就有 8.7 秒),报出来的数会系统性小于用户体感;
|
|
622
|
-
* 而且 bfcache 恢复(pageshow persisted)不会重置 startedAt
|
|
623
|
-
* · 没有 performance 时退化回 startedAt
|
|
622
|
+
* 而且 bfcache 恢复(pageshow persisted)不会重置 startedAt,那约定还能报出几十分钟。
|
|
623
|
+
* · 没有 performance 时退化回 startedAt 约定(老浏览器兜底,宁可粗糙也不能漏报)。
|
|
624
624
|
*/
|
|
625
625
|
function maLoadElapsedMs() {
|
|
626
626
|
try {
|
|
@@ -1030,7 +1030,7 @@ const SCRIPT = `(() => {
|
|
|
1030
1030
|
let fabDone = false;
|
|
1031
1031
|
|
|
1032
1032
|
/* ===== 📶 网络延迟采样器(2026-10-10 二修,与"某一代菜单 DOM"彻底解耦)=================
|
|
1033
|
-
一修(0.7.0-beta.3)改掉了"亚毫秒样本被丢弃"
|
|
1033
|
+
一修(0.7.0-beta.3)改掉了"亚毫秒样本被丢弃",但用户实测**仍然一直「测速中」**,
|
|
1034
1034
|
而同一时段镜像页打了 69 次 /api/agentPresets/list 这类**完全合格**的请求 ——
|
|
1035
1035
|
说明真根因不是样本被过滤,而是**采样根本没接上**(或渲染写进了已被替换的旧节点)。
|
|
1036
1036
|
|
|
@@ -1300,15 +1300,15 @@ const SCRIPT = `(() => {
|
|
|
1300
1300
|
(dsh-bridge.mjs:763 → :770),两者都 splice 到 </head> 前 → adapter 的 fetch 包装在
|
|
1301
1301
|
**内层** → 它量到的是"去掉加解密后的真实网络往返"(这正是用户想要的"网速")。
|
|
1302
1302
|
|
|
1303
|
-
|
|
1303
|
+
统计约定:最近 RTT_KEEP(8) 次**成功**样本取中位数。
|
|
1304
1304
|
· 用中位数而不是均值/EWMA:偶发的一次 GC/系统调度卡顿会把均值永久带偏,
|
|
1305
1305
|
中位数对单次离群点免疫,同时比"最小值"更贴近体感(最小值永远偏乐观);
|
|
1306
1306
|
· 失败(reject)/超时(>20s)/亚毫秒(同一帧内的缓存命中)都不进样本:
|
|
1307
1307
|
前者不是网络耗时,后者会算出 0ms 这种"谎言";
|
|
1308
1308
|
· **无样本时显示「测速中…」而不是 0ms** —— 0ms 会让人以为网络飞快。
|
|
1309
|
-
分档(2026-09-27
|
|
1309
|
+
分档(2026-09-27 用户约定调整):≤500ms 顺畅 / ≤1000ms 正常 / ≤2000ms 偏慢 / >2000ms 很慢。
|
|
1310
1310
|
为什么放宽:这是个**文本窗口**的远程操作界面(看输出、敲字、点按钮),
|
|
1311
|
-
对往返延迟本来就宽容 ——
|
|
1311
|
+
对往返延迟本来就宽容 —— 用户原话「500ms 以内的延迟都不叫事,1000ms 使用起来也是正常流畅的」。
|
|
1312
1312
|
旧的 120/300 分档是照"网页首屏"的体感定的,会把大量正常可用的移动网络误报成"较慢",
|
|
1313
1313
|
反而让用户以为哪里坏了。 */
|
|
1314
1314
|
const RTT_KEEP = 8, RTT_MAX_MS = 20000;
|
|
@@ -1323,7 +1323,7 @@ const SCRIPT = `(() => {
|
|
|
1323
1323
|
const mid = s.length >> 1;
|
|
1324
1324
|
return s.length % 2 ? s[mid] : Math.round((s[mid - 1] + s[mid]) / 2);
|
|
1325
1325
|
};
|
|
1326
|
-
/* 分档(2026-09-27
|
|
1326
|
+
/* 分档(2026-09-27 按用户约定放宽):文本窗口的远程操作,500ms 内都算顺畅、1000ms 内正常流畅。
|
|
1327
1327
|
ok=顺畅 / mid=正常 / slow=偏慢 / bad=很慢 —— 颜色只用于区分,**不把"正常"渲染成告警色**。 */
|
|
1328
1328
|
const rttGrade = (ms) => (ms <= 500 ? "ok" : ms <= 1000 ? "mid" : ms <= 2000 ? "slow" : "bad");
|
|
1329
1329
|
const rttLabel = (g) => (g === "ok" ? "顺畅" : g === "mid" ? "正常" : g === "slow" ? "偏慢" : "很慢");
|
|
@@ -1357,7 +1357,7 @@ const SCRIPT = `(() => {
|
|
|
1357
1357
|
if (ico) ico.textContent = shown === null ? "📶" : (g === "bad" ? "📵" : "📶");
|
|
1358
1358
|
if (txt) {
|
|
1359
1359
|
/* 空态**说清原因**(2026-10-10):以前只有一句「等一次网络请求后显示」,
|
|
1360
|
-
于是"采样器压根没接上"和"接上了但还没有请求"
|
|
1360
|
+
于是"采样器压根没接上"和"接上了但还没有请求"长得一模一样,用户两次反馈
|
|
1361
1361
|
我们都只能猜。现在一眼可分。 */
|
|
1362
1362
|
txt.textContent = shown !== null ? (shown + " ms · " + rttLabel(g))
|
|
1363
1363
|
: (maNetPatch.ok ? "测速中…(已接上,等一次请求)" : "测速中…(采样器未接上)");
|
|
@@ -1585,7 +1585,7 @@ const SCRIPT = `(() => {
|
|
|
1585
1585
|
frame.classList.add("dsh-ma-frame");
|
|
1586
1586
|
|
|
1587
1587
|
/* 给 frame 子列打稳定标记(dsh-ma-*),规避官方类名前缀随构建变化。
|
|
1588
|
-
⚠️ 必须先把**不是列**的节点剔掉(2026-09-19
|
|
1588
|
+
⚠️ 必须先把**不是列**的节点剔掉(2026-09-19 故障根因):
|
|
1589
1589
|
· overlayLayer(data-shell-overlay / *overlayLayer*)= 官方 shell.overlay 宿主,inset:0 全屏,
|
|
1590
1590
|
在 0.1.5-rc.2 里**始终**是 frame 的最后一个子元素;
|
|
1591
1591
|
· 拖拽把手(*handle* / [data-side])= 8px 宽绝对定位条(折叠时官方根本不渲染)。
|
|
@@ -1622,7 +1622,7 @@ const SCRIPT = `(() => {
|
|
|
1622
1622
|
① 已经判定为第三列(rightbar/details 类名)的列,**绝不是**抽屉列;
|
|
1623
1623
|
② 类名含 sidebar 且不含 rightbar/details(0.1.2/0.1.5 的 pI_x6G_sidebarCol 都满足);
|
|
1624
1624
|
③ 退到结构信号:列里有官方侧栏组件根 .hHd-Xa_root,或有**整串**匹配的官方侧栏开合按钮
|
|
1625
|
-
(精确匹配,绝不再用 *="sidebar" 这种包含匹配 ——
|
|
1625
|
+
(精确匹配,绝不再用 *="sidebar" 这种包含匹配 —— 那正是故障来源);
|
|
1626
1626
|
④ 同时给 CSS 加了 :not(.dsh-ma-details) 兜底:万一将来又标重,详情列的样式也必须赢。 */
|
|
1627
1627
|
const SIDEBAR_RE = /sidebar/i;
|
|
1628
1628
|
const NOT_SIDEBAR_RE = /rightbar|right-bar|details/i;
|
|
@@ -1742,7 +1742,7 @@ const SCRIPT = `(() => {
|
|
|
1742
1742
|
只要官方属性语义与我们理解的不一致,光看属性就可能误判;而遮罩一旦拦截整屏,
|
|
1743
1743
|
用户看到的就是"整屏阴影 + 点哪都没反应"。以实测几何为准(isDrawer),误判也不会挡住用户。 */
|
|
1744
1744
|
/* 抽屉必须"像抽屉":不是全屏层(overlay 层/主内容区都被排除在外)、不是拖拽把手、
|
|
1745
|
-
**与视口真正相交**。任一不满足 → 它就不该让遮罩拦截点击(0.1.5
|
|
1745
|
+
**与视口真正相交**。任一不满足 → 它就不该让遮罩拦截点击(0.1.5 故障的结构性防线)。
|
|
1746
1746
|
⚠️ 2026-09-19 手机实测第二波:旧实现只查 r.right > 8,这对"向右滑出屏外"的第三列是
|
|
1747
1747
|
**恒真**的 —— 抽屉滑出时右边坐标是很大的正数(实测 left=400,right=759,width=358),
|
|
1748
1748
|
于是"完全看不见、一个像素都没露"的第三列被判成"展开的详情列":
|
|
@@ -1809,7 +1809,7 @@ const SCRIPT = `(() => {
|
|
|
1809
1809
|
let officialExpandedSeen = false;
|
|
1810
1810
|
let lastOfficialCollapsed = null;
|
|
1811
1811
|
let scrimTimer = 0, scrimWatchdog = 0;
|
|
1812
|
-
/* ❌ 这里曾有「详情列(第三列)
|
|
1812
|
+
/* ❌ 这里曾有「详情列(第三列)在手机端的显示条件」整段判据(白框故障后打的四层闸门)。
|
|
1813
1813
|
2026-09-27 根治时删除:第三列的显隐**完全交还官方** —— 宽屏按 grid 列、窄屏全屏浮层。
|
|
1814
1814
|
我们不再有"要不要显示它"的决定权,也就不需要任何判据。 */
|
|
1815
1815
|
|
|
@@ -2067,7 +2067,7 @@ const SCRIPT = `(() => {
|
|
|
2067
2067
|
setTimeout(run, 0);
|
|
2068
2068
|
};
|
|
2069
2069
|
/* ===== 手机图片上传兼容(0.6.15)=====
|
|
2070
|
-
|
|
2070
|
+
用户反馈:「手机端无法识别图片」——现象是选了照片发出去,模型看不到图 / 当成普通文件。
|
|
2071
2071
|
|
|
2072
2072
|
根因(官方 dsh web 客户端逻辑,dsh-client-ui-conversation 的 createDrafts):
|
|
2073
2073
|
· 只有 file.type ∈ {image/png, image/jpeg, image/webp, image/gif} 才走**图片**通道
|
|
@@ -83,7 +83,7 @@ export function resolveClientVersion(raw) {
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* 数值协议常量 —— **逐个抄自归档参考实现**,不是猜的:
|
|
86
|
-
*
|
|
86
|
+
* OpenClaw 微信插件(第三方实现,仅作字段命名参考)
|
|
87
87
|
* · MessageType (types.ts:64-68) NONE:0 / USER:1 / BOT:2
|
|
88
88
|
* · MessageItemType (types.ts:70-79) NONE:0 TEXT:1 IMAGE:2 VOICE:3 FILE:4 VIDEO:5
|
|
89
89
|
* TOOL_CALL_START:11 TOOL_CALL_RESULT:12
|
|
@@ -702,7 +702,7 @@ export class IlinkClient {
|
|
|
702
702
|
// **入站消息**里(官方实现见 messaging/send.js 的 buildTextMessageReq:`msg.context_token`;
|
|
703
703
|
// 腾讯自家插件同样是「从入站取出 → 按 (账号, 用户) 缓存 → 发送时回带」)。
|
|
704
704
|
// 🔴 我们以前**从不发它** → 真机症状就是日志里的 `ret=-2 errmsg=prepare failed`:
|
|
705
|
-
//
|
|
705
|
+
// 消息发不出去(用户 2026-09-23:「没收到微信通道」)。
|
|
706
706
|
// ⚠️ 只在"有令牌且属于同一个对端"时带上,绝不拿 A 的上下文发给 B。
|
|
707
707
|
const peer = String(to);
|
|
708
708
|
// 本产品一个 bot 只绑定一个微信用户,所以只要有令牌就回带;`contextTokenPeer` 仅作诊断。
|
|
@@ -715,7 +715,7 @@ export class IlinkClient {
|
|
|
715
715
|
message_type: MessageType.BOT,
|
|
716
716
|
message_state: MessageState.FINISH,
|
|
717
717
|
item_list: text ? [{ type: MessageItemType.TEXT, text_item: { text: String(text) } }] : [],
|
|
718
|
-
// 没有就**不出现这个键**(不是空串)—— 与官方 `contextToken ?? undefined`
|
|
718
|
+
// 没有就**不出现这个键**(不是空串)—— 与官方 `contextToken ?? undefined` 同约定
|
|
719
719
|
...(ctxToken ? { context_token: ctxToken } : {})
|
|
720
720
|
};
|
|
721
721
|
const { json } = await this.request({
|
|
@@ -1774,7 +1774,7 @@ export function markUnbound(relayDir, reason = "", onWarn) {
|
|
|
1774
1774
|
export const DEFAULT_REPLY_TTL_MINUTES = 5;
|
|
1775
1775
|
|
|
1776
1776
|
/**
|
|
1777
|
-
* 完成通知里「结论」的展示上限(字)
|
|
1777
|
+
* 完成通知里「结论」的展示上限(字)。用户约定(2026-09-22):
|
|
1778
1778
|
* 「任务完成通知里有些关键结论被大量截断了,加长一些,更完整地展示最终结论 ——
|
|
1779
1779
|
* 这可能是用户关注的内容,所以都给他们展示出来」。
|
|
1780
1780
|
* 原值 400 对真实结论太短(实测一条正常的修复总结就有 190+ 字,稍详细就超)。
|
|
@@ -1898,7 +1898,7 @@ export function formatNotification(node = {}, opts = {}) {
|
|
|
1898
1898
|
for (const l of Array.isArray(node.lines) ? node.lines : []) lines.push(String(l));
|
|
1899
1899
|
// 措辞对齐"回顾今天做了什么" —— 日报不是待办清单
|
|
1900
1900
|
if (!Array.isArray(node.lines) || !node.lines.length) lines.push("今天这台电脑上没有跑任务。");
|
|
1901
|
-
// 品牌轻露出(
|
|
1901
|
+
// 品牌轻露出(用户:让用户知道我们在给他提供服务)。**克制一行**、放在最底,不喧宾夺主。
|
|
1902
1902
|
lines.push("", "—— DSH 远程控制 · 微信机器人通道");
|
|
1903
1903
|
// ⚠️ 这句是**功能**不是客套:微信 24h 推送窗口靠用户回消息续期,
|
|
1904
1904
|
// 简报的产品作用正是每天制造一次互动(§7)。必须出现「回复」字样,否则用户不会回。
|
|
@@ -1974,7 +1974,7 @@ function clipText(s, n) {
|
|
|
1974
1974
|
* 为什么必须转:微信**不渲染 Markdown**(最强证据是腾讯自己的插件在出站路径跑
|
|
1975
1975
|
* `StreamingMarkdownFilter` 主动剥离"不支持的 markdown 语法")。所以模型写的表格会以
|
|
1976
1976
|
* `| a | b |` 的**字面竖线**出现在气泡里、`**加粗**` 会显示成星号。
|
|
1977
|
-
*
|
|
1977
|
+
* 用户原话:「现在的表格形式看起来很奇怪,整个格式在微信上没有做过任何兼容,体验很差很差」
|
|
1978
1978
|
* —— 根因就在这里,不是"排版没调好"。
|
|
1979
1979
|
*
|
|
1980
1980
|
* 规则(只改"怎么显示",不改语义):
|
|
@@ -2365,10 +2365,10 @@ export function isDestructiveTool(toolName, detail) {
|
|
|
2365
2365
|
if (DESTRUCTIVE_TOOL_NAME_RE.test(name)) return true;
|
|
2366
2366
|
|
|
2367
2367
|
const text = flattenToolDetail(detail);
|
|
2368
|
-
// ⚠️ 2026-09-22
|
|
2368
|
+
// ⚠️ 2026-09-22 按用户决定**删掉**了原来的规则③:「能跑命令的工具 + 完全看不到内容 → 算破坏性」。
|
|
2369
2369
|
// 原意是防"盲批",但 DSH 的审批节点本来就只下发 toolName、常常没有命令原文,
|
|
2370
2370
|
// 那条规则会把**绝大多数正常 Bash 审批**都降级成"只能回电脑确认",过严且伤体验。
|
|
2371
|
-
//
|
|
2371
|
+
// 用户明确:安全边界松一点没问题,**DSH 自身有权限控制**。
|
|
2372
2372
|
// 所以现在只看**内容**:看得出是 rm -rf / 强推 / DROP TABLE / 批量覆盖这类才拦;
|
|
2373
2373
|
// 看不到内容时按普通审批处理(保留一步回执)。SHELL_TOOL_NAME_RE 仍保留,
|
|
2374
2374
|
// 供将来若要恢复"盲批保护"时使用,并由测试固化其语义。
|
|
@@ -2834,7 +2834,7 @@ export class WeChatChannel {
|
|
|
2834
2834
|
// 冷却原本只按"撞到 -14"计时,但**换绑会拿到全新的 bot_token** ——
|
|
2835
2835
|
// 旧 token 的会话超时对新 token 毫无意义。不清的后果:用户解绑→重绑拿到新 token,
|
|
2836
2836
|
// 却仍被旧 token 的 1 小时冷却挡着,表现成"刚绑上就一小时内不能聊天",
|
|
2837
|
-
// 而且面板只说"通知会延迟",用户完全不知道为什么。(
|
|
2837
|
+
// 而且面板只说"通知会延迟",用户完全不知道为什么。(用户实测撞到)
|
|
2838
2838
|
this.cooldown.clear();
|
|
2839
2839
|
return this.uiAccount();
|
|
2840
2840
|
}
|