DeepSeek Harness 插件

idoall/dsh-lan-guard

Star 数 ★ 4 下载量(近 30 天) 3,606 分类 远程与移动端 收录于 2026-09-25 npm dsh-lan-guard

把官方 DSH Web UI 安全地开放到局域网:不改动 DSH 自身的回环绑定,另开一个带密码门禁、自签 HTTPS 与可逐个吊销的设备配对的反向代理端口,并提供手机扫码入口。

安装

# npm 包(预构建)

dsh plugin --profile web add dsh-lan-guard

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:idoall/dsh-lan-guard

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

DSH LAN Guard 是 DeepSeek Harness 社区插件。它只注册官方设置页里的一个区块(settings.section),不替换任何官方布局,也不修改 DSH 源码或 DSH 自身的监听绑定。

DSH 的 Web 界面只监听 127.0.0.1,而官方明确拒绝绑定 0.0.0.0。本插件不碰那个绑定,而是在另一个端口上再开一个带门禁的监听,把官方界面原样代理出去:密码门禁、默认自签 HTTPS、手机扫码入口,以及可逐个批准与拉黑的设备配对。装完重启一次即可用——不需要手写任何配置。

能做什么

  • 门禁反向代理:完整转发 HTTP 与 WebSocket(含官方界面的 /api/remote.mux 长连接),改写 Host/Origin,剥离逐跳头,上游不可达时返回 502。DSH 自身的绑定与配置一动不动。
  • 默认对外可用,但对外可达 ≠ 可以进入:默认监听 0.0.0.0,所以安装后只需重启一次就能用;门禁默认开启,未设访问密码时拒绝所有设备,且默认自签 HTTPS——不存在明文传输。想只在本机使用,在设置页切到「仅本机」即可。
  • 双密码门禁:PBKDF2-SHA256(600,000 次迭代)。访问密码给手机等访客设备登录用,管理密码用于解锁本设置页的管理台(未设置时退回访问密码);另有 dsh_ 免密链接、持久访客会话、按 IP 锁定与 CSRF 校验。
  • 默认自签 HTTPS,CA 身份跨重启不变:自动生成 DSH LAN Guard CA,按所选网卡地址签发叶证书。换 IP 只重签叶证书,所以每台设备只需信任一次。
  • 本机永不锁定:直连 127.0.0.1 享有物理免锁特权(能用这台电脑的人本就能改这些设置);远程访问按 adminPolicy 处理——只读(默认)、需密码解锁、或不锁。
  • 设备配对与身份拉黑:手机首次通过门禁时命名,获得 HttpOnly 设备身份 cookie,出现在 已授权设备 里(名称 / 创建时间 / 最近使用 / 来源 IP),可逐个吊销或拉黑该身份。拉黑持续作用于已记录身份,不等于封禁物理设备:换浏览器或清除 cookie 后,仍可用共享访问密码重新配对。需要审核重新配对时,请开启新设备批准。
  • 设置挂在官方设置页内:「局域网访问」分区含四个 tab——扫码访问、安全认证、已授权设备、连接与证书。排版与配色全部使用官方设计 token,不替换任何官方布局。
  • 远程也能添加工作区(智能分流):DSH 的目录选择器在启动时判定一次,本机回环绑定 + 有显示器会判成「原生」——手机点「添加工作区」实际是在电脑屏幕上弹文件夹对话框。本插件在浏览器侧以更低优先级遮蔽官方选择流程:本机浏览器照旧走系统原生对话框,远程设备改用页面内目录浏览器(面包屑、快捷入口、目录列表)。选中后仍由 DSH 官方工作区流程登记,插件只读目录、不写任何东西。
  • 端口与监听范围可配:默认 3081(DSH 端口 + 1),被占用时自动往后顺延(最多试 10 个);设置页可改端口并带可用性检查,也可在「局域网(默认)/ 仅本机」之间切换(两者都需重启 dsh 生效)。
  • 可选 mDNS:默认关闭;开启后广播 _dsh-lan-guard._tcp。
  • 升级检测:设置页显示「当前版本 → npm 上最新版本」并给出可复制的升级命令;插件不会自己安装或重启任何东西。

快速开始

环境要求:

  • 带 Web profile 的 DeepSeek Harness
  • Node.js 20 或更新
  • 已验证的 DeepSeek Harness:0.2.2-rc.1(接口审查、新版依赖类型检查与自动回归;另在运行 rc.1 的实例上只读核对代理入口、页面注入与管理接口。本次未重新进行 rc.1 浏览器交互、完整 LAN/WebSocket 与手机实机验收;alpha.3 的浏览器测量属于上一版验证)。
  • 2026-11-04 核对 npm 渠道:next 为 0.2.2-rc.1,alpha 为 0.2.2-alpha.3,latest 为 0.2.1-rc.5。本次验证 0.2.2-rc.1;0.2.1-rc.5 不在已验证清单内。

从 npm 安装:

dsh plugin --profile web add dsh-lan-guard@latest

从 GitHub 安装:

dsh plugin --profile web add "github:idoall/dsh-lan-guard"

从本地克隆安装:

git clone https://github.com/idoall/dsh-lan-guard.git
cd dsh-lan-guard
pnpm install && pnpm run build
dsh plugin --profile web add "link:$(pwd)"

然后重启一次 DSH,打开 设置 → 局域网访问。重启后插件已经在对外监听(默认 0.0.0.0:3081,自签 HTTPS + 门禁),你只需要:

  1. 在 安全认证 里设置访问密码(至少 8 位)。没设之前,门禁拒绝所有设备。
  2. 在 连接与证书 确认监听范围(默认「局域网」),并选择对外公布的网卡——网卡决定二维码/访问地址用哪个 IP、自签证书签哪些地址。
  3. 在 扫码访问 扫码,手机上信任一次 DSH LAN Guard CA、输入访问密码、给设备命名。之后手机运行的就是官方 DSH 界面。

远程设备默认是只读的(adminPolicy: local_only):能用 DSH,但不能改插件设置。想让手机也能管理,在桌面把策略改掉。

设置

设置项集中在 设置 → 局域网访问 的四个 tab 里。非敏感开关(enabled、listenPort、listenHost、networkInterface、settingsUnlock、answerHeartbeat、socketWatchdog、mobileCompat、mobileScrollFix、mobileTurnRail、auth.mode、auth.adminPolicy、auth.adminProtection、auth.allowLoopback、auth.requirePairing、auth.requireApproval)可直接改;listenPort 与 listenHost 需重启 dsh 生效;settingsUnlock / socketWatchdog / mobileCompat / mobileScrollFix / mobileTurnRail 刷新页面即可生效,answerHeartbeat 立即对已打开的连接生效;dataDir 与 tls.* 属启动期字段,需在 profile patch 里改。

设置项 默认 作用
监听范围 局域网 0.0.0.0 决定局域网能否访问这个端口。切到「仅本机 127.0.0.1」更保守,需重启 dsh。两种选择都保留门禁与自签 HTTPS。
代理端口 3081 DSH 端口 + 1;被占用时自动往后顺延(最多试 10 个),带可用性检查。需重启 dsh。
对外公布的网卡 自动选择 决定二维码/访问地址用哪个 IP、自签证书签哪些地址;虚拟网卡会被降权标注。
验证模式 扫码免密 + 密码 也可选「仅密码」或「仅安全 Token」。切换会吊销所有已有访客会话。
访问密码 未设置 手机等访客设备的登录密码。未设置时门禁拒绝所有设备。
管理密码 未设置 解锁本设置页的管理台;未设置时退回访问密码。
本机回环访问免密 开 127.0.0.1 直连跳过门禁(物理免锁)。
远程设备管理权限 仅本机 决定局域网设备能否管理:「仅本机」只读;「密码解锁」需先解锁;「不锁定」不额外要求。远程浏览目录并添加工作区需要后两者之一。
管理操作需要先解锁 开 关闭后,符合策略的远程会话可直接修改设置(local_only 除外,它永远只允许本机)。
局域网设备可用官方设置页 开 DSH 官方设置面默认只对回环页面开放,局域网设备打开「设置 → 模型」会提示 settings are unavailable in this browser。开启后,通过门禁的设备(含手机)刷新页面即可使用官方设置页;这是界面解锁,不是新增权限——设置接口本来就只由门禁把关,读取密钥仍由 DSH 脱敏。刷新页面生效,无需重启。
代理代答心跳 开 DSH 每 2 秒给每条 WebSocket 发心跳,连续两次没被回应就断开(实测 6 秒)。手机锁屏/切走时页面被系统挂起、回不了心跳,于是只走 WebSocket 的会话记录就会「载入不全、甚至断开」。开启后由代理替手机回心跳,手机自己回的那次是重复包,无副作用。立即生效(含已打开的连接)。
断线看门狗 开 页面补丁:卡在「连接中」超过 8 秒的 WebSocket 会被关掉;从后台回来 10 秒后仍一条都没连上时自动重载一次(每标签最多连续 3 次,冷却 20 秒起)。只对真的连不上的页面生效。刷新页面生效。
移动端兼容垫片 开 页面补丁:补 AbortSignal.any/AbortSignal.timeout/Promise.withResolvers/Iterator 与移动端 meta。缺这些 API 时 DSH 客户端在会话流里抛错,界面只会一直显示「载入历史…」且没有报错。现代浏览器上这些分支不生效。刷新页面生效。
手机滚动矫正(窄屏) 开 手机端 DSH 外壳在窄屏下把对话列裁在 overflow:hidden 的层里(实测 844/1688),整页也不可滚 → 内容可见但滑不动。开启后只在「窄屏 + 移动端 + 整页不可滚 + 找到被裁剪溢出的层」四条同时成立时,把那几层改成可触摸滚动;正常页面不碰。页面加 ?lgdiag=1 可看布局诊断。刷新页面生效。
手机端轮次导航 关 把 PC 右侧那根轮次浮轨搬到手机:DSH 在手机上其实照样渲染它(可跳转到某一轮、未加载的会先翻历史再跳、当前轮次跟随),只是被它自己的窄屏容器查询藏了起来(@container (max-width: 900px))。开启后用一段只在 ≤1023px 生效的样式把它放出来(right:4px、宽 28px,正好占满对话区右侧留白、不压正文),并给手机补一套手势:轻点 = 跳转、按住 ≈0.3 秒 = 弹出可读的轮次卡片(含该轮提问与回复摘要 + ◀▶ 逐轮微调 + 一键跳转)、按住成功后上下滑动 = 连续预览(手指滑到哪一格就换成那一轮的摘要,停在轨道上下边缘会自动继续卷动)、0.3 秒内快速拖动 = 普通滚动轨道。已知短板:每格 28×10px、格间距在官方 JS 里锁死 10px,所以"点得准"靠卡片与 ◀▶ 而不是放大刻度。上游若改了隐藏规则,脚本会在 3 秒内自检失败并整体摘掉样式(页面回到官方原样)。刷新页面生效;?lgdiag=1 底部会多一屏轮次导航诊断。
新设备需要命名确认 开 新设备首次通过门禁时要自己命名一次,之后才出现在设备列表里。
新设备需要管理员批准 关 开启后,命名完还要你在设备列表点「批准」才能进入。
TLS 自签 HTTPS 关闭会明文传输门禁密码;非回环 + 关闭 TLS 必须显式设置 tls.allowInsecureLan: true,否则拒绝启动。

插件从它的 Cordis 条目读取配置。所有字段都有可用默认值,装完即可用:

# ~/.dsh/profiles/web/cordis.patch.yml(可选:只写你想改的字段)
- id: dsh-lan-guard
  config:
    listenHost: 0.0.0.0              # 默认面向局域网;'127.0.0.1' = 仅本机,或写具体网卡 IP
    listenPort: 3081                 # DSH 端口 + 1;被占用时自动顺延
    networkInterface: en0            # 可选:只在一个网卡上公布(留空 = 自动)
    settingsUnlock: true             # 局域网设备可用官方设置页(默认开;关闭恢复 DSH 默认)
    answerHeartbeat: true            # 代理代答 WebSocket 心跳(默认开;手机挂起时不被宿主回收)
    socketWatchdog: true             # 页面断线看门狗(默认开)
    mobileCompat: true               # 移动端兼容垫片(默认开)
    mobileTurnRail: false            # 手机端显示官方轮次浮轨(默认关,刷新页面生效)
    dataDir: ~/.dsh/profiles/web/data/dsh-lan-guard   # 可选;缺省即用这个推导路径
    tls:
      mode: self-signed              # 'self-signed'(默认)| 'provided' | 'off'
      allowInsecureLan: false        # 局域网明文 HTTP 的显式风险确认
    mdns:
      enabled: false                 # 广播 _dsh-lan-guard._tcp
    auth:
      mode: token_and_password       # 'token_and_password' | 'password' | 'token'
      adminPolicy: local_only        # 'local_only'(默认)| 'password_unlock' | 'open'
      adminProtection: true          # 管理台需要管理密码
      allowLoopback: true            # 127.0.0.1 访客跳过门禁(物理免锁)
      requirePairing: true           # 新的远程设备必须先命名一次

dataDir 是唯一需要解释的一项:不写就用 <当前 profile>/data/dsh-lan-guard(例如 ~/.dsh/profiles/web/data/dsh-lan-guard),写了就优先用你写的(支持 ~ 前缀)。它只影响插件私有数据(密码哈希、设备令牌哈希、会话、自签 CA)的落点,不影响可用性。

兼容性

当前版本:插件 0.7.10,兼容 DeepSeek Harness 0.2.2-rc.1。下表每行是一个插件版本:它验证过哪些 DSH 版本,以及那一版做了什么。

插件 已验证的 DeepSeek Harness 这个版本是什么
0.7.10 (latest) 0.2.2-rc.1;此前验证版本见下方清单 rc.1 兼容版本,开发依赖同步 Cordis 4.1.0 / Schemastery 3.19.0;运行时源码不变。
0.7.9 (published) 0.2.2-alpha.3;此前验证版本见下方清单 修复 alpha.3 手机侧栏抽屉裁剪远程目录弹层的问题;同步兼容声明与开发依赖;校正两版 README 中过时的安全边界与手机浮轨说明。
0.7.8 (published) 0.2.2-alpha.2、0.2.1-rc.4、0.2.1-rc.3、0.2.1-rc.2、0.2.1-alpha.5、0.2.1-alpha.4、0.2.1-alpha.3、0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2、0.1.7-rc.1 跟上 DSH 0.2.2-alpha.2:新版界面把轮次导航的标签译成 8 种语言,手机浮轨改成不依赖文案的定位(并为将来新增语言留了结构化兜底);其余是兼容声明与文档。
0.7.7 (published) 0.2.1-rc.4、0.2.1-rc.3、0.2.1-rc.2、0.2.1-alpha.5、0.2.1-alpha.4、0.2.1-alpha.3、0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2、0.1.7-rc.1 跟上 DSH 0.2.1-rc.4:上游这次只改版本号,宿主接口与类型与 rc.3 逐字节一致;插件运行时代码不变。
0.7.6 (published) 0.2.1-rc.3、0.2.1-rc.2、0.2.1-alpha.5、0.2.1-alpha.4、0.2.1-alpha.3、0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2、0.1.7-rc.1 跟上 DSH 0.2.1-rc.3,运行时代码不变。仅完成接口、类型与自动检查;手机/浏览器验收待完成。
0.7.5 (published) 0.2.1-rc.2、0.2.1-alpha.5、0.2.1-alpha.4、0.2.1-alpha.3、0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 跟上 DSH 0.2.1-rc.2:只更新兼容声明,代码没动。
0.7.4 0.2.1-alpha.5、0.2.1-alpha.4、0.2.1-alpha.3、0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 跟上 DSH 0.2.1-alpha.5:只更新兼容声明,代码没动。
0.7.3 0.2.1-alpha.4、0.2.1-alpha.3、0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 跟上 DSH 0.2.1-alpha.4:只更新兼容声明,代码没动。
0.7.2 0.2.1-alpha.3、0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 跟上 DSH 0.2.1-alpha.3:只更新兼容声明,代码没动。
0.7.1 0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 文档版:README 和历史发布说明改成人话,功能与 0.7.0 完全相同。
0.7.0 0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 手机端轮次浮轨终于好用:卡片不再跑偏,预览跟着手指走,按住刻度上下滑动就能看每一轮在说什么(默认关闭)。顺带修掉重启后手机白屏的问题。
0.6.0 0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 手机上「看得见刻度、看不到内容」的问题:改成按住刻度约 0.3 秒弹出卡片,显示那一轮的提问和回复摘要。
0.5.0 0.2.1-alpha.1、0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 跟上新版 DSH(只改兼容声明);新增手机端轮次浮轨,默认关闭。
0.4.6 0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 跟上 DSH 0.2.0-rc.2:只更新兼容声明,并确认旧的手机滚动补偿已经不需要了。
0.4.5 0.2.0-rc.1、0.1.7-rc.2 修长会话里正文晚一步加载时被判成「已经正常」、结果仍然滑不动的问题。
0.4.4 0.2.0-rc.1、0.1.7-rc.2 修 iOS 上打开会话不在最新、手指几乎拖不动的问题。
0.4.3 0.2.0-rc.1、0.1.7-rc.2、0.1.7-rc.1 换个修法:改移动端高度链,不再给内容层打补丁,并撤掉自建的「回到底部」按钮。
0.4.2 0.1.7-rc.2 找回官方自带的「回到底部」按钮,并修掉它和全屏遮罩打架、侧边栏发灰的问题。
0.4.1 0.1.7-rc.2 第一版手机滚动修复:只在真的滑不动的页面上动手,正常页面不碰;新增 ?lgdiag=1 诊断。
0.4.0 0.1.7-rc.2、0.1.7-rc.1 手机长连接不再轻易掉线(代理替页面回心跳),并加入断线看门狗和连接体检。
0.3.6 0.1.7-rc.2、0.1.7-rc.1 局域网下也能添加工作区,并修远程设备管理和几处界面问题。
0.3.5 0.1.7-rc.2、0.1.7-rc.1 用 http 访问 TLS 端口会跳到 https 并留日志;删掉设备后旧 cookie 不再把浏览器锁死。
0.3.4 0.1.7-rc.2、0.1.7-rc.1 局域网设备也能用官方设置页(默认开启,只是解锁界面,不新增权限)。
0.3.3 0.1.7-rc.2、0.1.7-rc.1 首次访问的两道关提前说明;失效链接页可以重新登录;拉黑说明与实现对齐。
0.3.2 0.1.7-rc.2、0.1.7-rc.1 装完即用:默认对外监听 + dataDir 自动推导;设置页液体玻璃与官方尺寸;访问地址改为单行滚动
0.3.1 0.1.7-rc.2、0.1.7-rc.1 针对 0.1.7-rc.2 的验证版:代码零改动,只更新兼容元数据
0.3.0 0.1.7-rc.1 设备批准与永久拉黑;修掉打开分享的 ?auth= 链接时的空白页
0.2.0 0.1.7-rc.1 升级检测;移除右下角状态胶囊;间距修复
0.1.1 0.1.7-rc.1 文档版:中英双语用户 README
0.1.0 0.1.7-rc.1 首个版本:门禁反向代理、自签 HTTPS、设备配对、设置页、扫码访问
  • 声明范围 >=0.1.7-rc.1 <0.3.0(dsh.engines.dsh);已验证版本:0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.3、0.2.1-alpha.4、0.2.1-alpha.5、0.2.1-rc.2、0.2.1-rc.3、0.2.1-rc.4、0.2.2-alpha.2、0.2.2-alpha.3、0.2.2-rc.1(共 14 个版本,0.2.2-rc.1 验证范围见上文)。未列入的 DSH 版本属未验证,请自行验证后再使用。上界在 0.4.3 从 <0.2.0 放宽:profile 加载会拒绝范围不含运行版本的 bundle,而裸的 <0.2.0 恰好排除 0.2.0 正式版。
  • 0.2.2 新能力评估:webserver/index-inject 的 per-Peer 注入、resource 路由与 readResource 都是 0.2.2 新增;本插件没有采用 —— 前者只影响 fallback 渲染,后两者拿不到客户端地址,而门禁和「仅本机」策略依赖远端地址。真正用上的是多语言:手机浮轨现在识别 DSH 全部 8 种界面语言,并对将来新增语言保留结构化兜底。
  • 本插件用到的宿主/客户端接口:webServer.register / webServer.tapIndex(indexTaps)、connection.requestRejection、connection.authenticatedUrl、追加型 settings.section seat、@deepseek-ai/schemastery,以及 profileContext(用于推导默认数据目录)。
  • 破坏性默认值变更(0.3.2 起):listenHost 默认由 127.0.0.1 改为 0.0.0.0,装完重启一次即可用;0.3.1 及更早默认仅回环。门禁与自签 TLS 的默认值未变(未设密码仍拒绝所有设备)。详见 CHANGELOG。
  • 0.4.0 的验证状态:三处改动都做了实测,不只跑用例。
    1. 代理代答心跳:先用 Node 客户端对真实 DSH 测得「停止回 Pong 后 6.0 秒被切断,close 1006(无 Close 帧)」;再加上代答后同一条链路 20 秒仍存活(收到 10 次 Ping)。仓库里有一条端到端用例复现同一对行为(上游按 40ms 心跳、漏 2 次即 destroy):关掉代答必被回收、打开代答连接存活。
    2. 页面补丁:注入的脚本在测试里被执行(假环境 + 假时钟),断言「卡在 CONNECTING 8 秒被关掉」「有一条 OPEN 就不重载」「从未建过 socket 不重载」「后台回来无 OPEN 才重载一次、并按冷却与上限退避」「隐藏状态不重载」。
    3. 浏览器实测(BrowserSkill,Chrome 154,经临时回环转发走完整插件链路):/api/remote.mux 握手 3ms、session/follow 首帧 2.1MB 单帧 43–110ms、12 秒 310 帧无中断;同一页面把主线程冻结 12 秒时,无代答的连接收到 close 1006。 测试套件 349 项全绿(0.3.6 为 330)。真实 DSH 上的装机验证在发布后进行(本机运行的是已安装的 0.3.6,需重装 + 重启 dsh 才生效)。

手机长连接为什么会断(实测,2026-09-28)

会话记录(就是界面上的「载入历史…」那一段)只走 WebSocket:会话列表、目标条、统计数字都来自 HTTP,所以 WS 一断就会呈现「界面都在、对话区空白」的形状。三个事实决定了这个失效模式:

事实 实测/来源
宿主每 2 秒 Ping 一次,连续两次没被回应才回收 websocketHeartbeatIntervalMs: 2000、MAX_MISSED_HEARTBEATS: 2
停止回 Pong 后 6.0 秒被切断(close 1006,无 Close 帧),三处独立复现 直连 3081、经转发、浏览器冻结主线程
手机切走/锁屏时页面被系统挂起,回不了心跳 iOS 行为;WebKit 另有「后台恢复后 new WebSocket() 永久卡 CONNECTING 而 HTTP 正常」的已知问题

因此本版本提供三层兜底,默认全开、可逐项关闭:

  1. 代理代答心跳(answerHeartbeat):代理在中转时读取上游→浏览器方向的 WS 控制帧,遇到 Ping 立刻以客户端身份回一个掩码 Pong。RFC 6455 允许未经请求的 Pong,宿主只按「收到过 Pong」重置计数,所以手机自己回的重复包无害。这是唯一能阻止宿主回收挂起手机连接的一层。
  2. 断线看门狗(socketWatchdog):页面侧兜住 WebKit 的「卡 CONNECTING」——超时关掉卡住的 socket,从后台回来仍连不上时限流重载一次。
  3. 移动端兼容垫片(mobileCompat):补 AbortSignal.any/timeout、Promise.withResolvers、Iterator 与移动端 meta。DSH 客户端在会话流里直接调用 AbortSignal.any,而 Session.doOpen 会把非传输类异常原样抛出——缺 API 时的表现就是「永久载入历史…、连报错都没有」。

「连接与证书 → 手机连接与自愈」里有一个连接体检:它在这个浏览器里检查缺哪些 API、页面拿到了哪些补丁,并对当前地址做一次真实 WebSocket 握手,同时显示代理侧的计数(在活连接 / 累计升级 / 被拒 / 代答心跳次数)与最近一条连接的时长、上下行字节、是否异常断开。

  • 0.3.6 的验证状态:改动包含宿主侧(新增目录列举路由与 cookie 中转)与客户端(目录浏览器、设置页控件、提示层与配色)。测试套件 330 项全绿(+57);三个提交在独立 worktree 里逐提交验证(278 / 304 / 330 各自通过);并做过反证(回退 cookie 中转、固定行不收缩、hover 染当背景,各自让对应用例失败);配色对比度按 DSH 真实 token 逐项计算,浅色/深色 10/10 通过。真实 DSH 上的装机验证在发布后进行。
  • 0.3.5 的验证状态:改动只在代理的连接层处理与门禁判定,未改动任何宿主/客户端接口;测试套件 273 项全绿(+10),并做过反证(回退修复后对应用例失败);真实 DSH 上的装机验证在发布后进行。
  • 0.3.4 的验证状态:改动只新增一个宿主侧开关、一行 index 注入与插件自己的设置页开关(webServer.tapIndex 是已声明的宿主接口);测试套件 263 项全绿;注入脚本已在真实 Chrome + 非回环地址实测三种状态;真实 DSH 上的装机验证在发布后进行。
  • 0.3.3 的验证状态:改动只在插件自己的页面、文案与门禁表单可用性,未改动任何宿主/客户端接口;测试套件 247 项全绿;真实 DSH 上的装机验证在发布后进行。

官方 UI 零改动复用,手机视口下自动适配:

手机上为什么「看不到全部消息」/ 滑不动(实测,2026-09-28)

症状:手机上打开会话,最新内容能看到一部分,但手指滑不动;底部固定的「目标条 / 快捷回复 / 输入框」压住正文,右侧滚动条停在中途。

根因(真机视口实测):DSH 官方外壳在窄屏下的根层 pI_x6G_frame 是 overflow:hidden,而内容高于它——实测 clientHeight=844 / scrollHeight=1688,同时整页也不可滚(pageScrolls=false)。内容被裁在容器里,所以"看得见、滑不动"。同一视口在桌面 Chrome 上正常,因此这是 iOS/窄屏的布局差异,与反向代理无关(DOM/CSS 全部来自官方 UI 与已装插件)。

修复(mobileScrollFix,默认开):注入脚本只在四条同时成立时才动手——窄屏(≤1023px)、移动端 UA、整页不可滚、且确实找到被裁剪且内容溢出的层;然后只把那几层改成可触摸滚动(overflow-y:auto、-webkit-overflow-scrolling:touch、touch-action:pan-y)。能正常滚动的页面一律不碰。

「回到底部」按钮:0.4.2 曾短暂提供插件自建按钮,但 0.4.3 已移除。当前版本复用 DSH 官方按钮,只在检查发现裁剪时修正移动端高度与滚动链,不再增加第二个回到底部按钮。

自检:在页面地址后加 ?lgdiag=1,顶部会出现一屏诊断(narrow / mobile / innerHeight / visualViewport / pageScrolls / clippingLayers / 是否已修)。这一屏就是本次定位所用的数据,以后复现同类问题不必连 Mac 调试。

手机上把 PC 右侧的轮次浮轨搬过来(mobileTurnRail,默认关,2026-10-06 新增)

它本来就在,只是被藏了。 PC 上会话右侧那根浮轨(灰刻度=已加载轮次、暗刻度=还没加载、黑长条=当前轮次;悬停出预览;点未加载的会先翻历史再跳)在手机上照样渲染、照样接线——ChatView 无条件挂载它,隐藏它的只有官方自己的一条容器查询: @container (max-width: 900px) { .frame { display: none } }(容器是对话区,390px 手机上只有约 327px 宽)。

做法:开启后用一段只在 ≤1023px 生效的样式把它放出来,并把它从"压着正文"挪到对话区右侧留白里(right:4px、宽 28px)。 为什么不是重写一个:轮次数据(ui-chat 的 navigation.items())是包内私有的,插件拿不到;而解开隐藏就能原样复用官方组件的跳转、翻历史、当前轮次跟随与预览。因此插件不新建正文滚动容器,但会添加下文所述的触摸预览卡片、卡片按钮与手势监听。

实测(0.2.1-alpha.1 / 390×844,把注入脚本原样打进页面):display:none → block、rect [357,355,24,52]、5 格、间距 10px、恰好 1 格 aria-current;对话列右缘 351、浮轨左缘 357 ⇒ 不压正文;Global panels / Session hierarchy 两个 nav 完全不受影响;0 报错。

手机上为什么必须补一层手势:官方那套提示是悬停交互(onPointerMove / onFocus 驱动预览气泡),触屏根本触发不了——轻点直接走 onClick 跳转,预览永远看不到。所以本插件不改官方逻辑,而是加一种手势:

手势 行为
轻点刻度 与原来一致:跳转到该轮(未加载的先翻历史)
按住刻度约 0.3 秒 弹出轮次卡片:该轮的用户提问 + 回复摘要(文本正是官方组件为悬停渲染的那份,靠派发 pointermove 取出,不调用 focus())
按住成功后上下滑动 连续扫过每一轮:手指滑到哪一格,卡片立刻换成那一轮的摘要——与 PC 上鼠标划过一致
手指停在轨道上/下边缘 按 16ms 步进自动卷动虚拟轨道,可以一直往下或往上扫
卡片上的 ◀ / ▶ 逐轮微调,边看边选——不必瞄准 10px 的格子
卡片上的「跳到第 N 轮」 走官方点击,一键跳转后卡片自动关闭
0.3 秒内快速拖动 普通滚动轨道(不会弹卡片)

卡片只在手机(pointer: coarse)与窄屏下存在;点空白处、✕ 或跳转都会关掉它。卡片挂在官方轨道内(不是 body 上的 fixed 浮层),这是 iPhone Chrome 上不再错位到左上角的关键。

已知短板(所以默认关):每格只有 28×10px,而格间距在官方 JS 里锁死为 10px——用 CSS 撑到 44px 会让相邻格互相覆盖、反而点错。所以精度的答案不是放大刻度,而是上面的卡片与 ◀▶。另外轨道无法再往中间挪:它左边紧贴正文列(实测 440px 视口下轨道 [401,411,28,52]、正文列右缘 401),再往内就会压字。

自检与回退:脚本会在安装后自己确认浮轨真的可见;若上游把隐藏规则改强(更高优先级),它会在约 3 秒内把注入的样式整体摘掉,页面回到与官方逐字节一致的状态,并在 __DSH_LAN_GUARD__.mobileTurnRail 记 "reverted"(连接体检里显示为 轮次导航 ✗ 已回退)。?lgdiag=1 会在屏幕底部多一屏 [lan-guard 轮次导航诊断]。

安全边界

  • DSH 自身的监听地址不被改动;本插件从不修改 DSH 配置、会话数据或官方 UI。
  • 门禁先于监听:监听器只在门禁对象构造完成之后才打开。默认对外监听之所以可接受,是因为 auth.enabled 默认为真、未设访问密码时门禁拒绝所有非回环设备、且 TLS 默认自签——三者必须同时成立。
  • 密钥(secrets.json、devices.json、会话)存放在 dataDir,权限 600;密码只存 PBKDF2-SHA256 哈希,设备令牌明文只返回一次,落盘只存 SHA-256 哈希,免密链接的 token 不写日志。
  • 代理给每个转发请求打上不可伪造的来源标记,宿主据此区分「本机操作者」与「经代理的访客」。
  • 回环直连按设计物理免锁——能使用这台电脑的人本就能改这些设置。
  • 官方设置页解锁(settingsUnlock)是界面层的兼容补丁,不是新的传输权限:经代理的请求本就会被改写成回环 host,DSH 的设置接口只由门禁把关(读取被 DSH 脱敏),该开关只是让官方页面不再显示「settings are unavailable in this browser」。想恢复 DSH 原生行为,把开关关掉即可。
  • 访问密码是共享的:吊销设备会立即让该设备的身份 cookie 失效,但换个浏览器用密码仍可重新配对。要「同一台机器永久拉黑」需要设备指纹或每设备独立令牌,本项目刻意不用指纹。
  • Cookie 中转只有一个例外:代理注入 DSH 的回环会话 cookie,且绝不把上游的 set-cookie 透传给访客;唯一被中转的是本插件自己的管理会话 cookie(dsh_lan_guard_admin,HttpOnly、SameSite=Strict、默认 30 分钟)。它由本插件签发、也只由本插件的路由校验,但校验代码跑在 DSH 的 web server 上——不中转它,远程设备就永远无法解锁管理台(password_unlock 形同虚设)。访客的门禁会话与设备身份 cookie 依旧不会到达 DSH。
  • 远程目录浏览器只读,且要管理台同等的权限:它只列举目录名(凭据目录 .ssh/.aws/.env 等与系统目录一律拒绝,符号链接解析到真实路径后二次校验,单层上限 1000 行、带限流),不创建、不修改任何文件;登记工作区仍由 DSH 官方流程完成。该接口复用管理台鉴权——本机操作者或已解锁的远程会话可用,默认 local_only 下远程设备得到 read_only_remote。
  • 只面向局域网:不做公网隧道、不做 IM Bot、不做端口转发。
  • 插件自己不安装、不重启、不推送任何东西:升级命令由你复制执行。

排障

手机提示证书不受信任。 CA 是自签的:每台设备安装/信任一次 DSH LAN Guard CA。信任前先比对 连接与证书 里显示的 SHA-256 指纹。

手机完全连不上。 确认手机在同一网络、地址与二维码一致,并检查是否有 VPN 或「专用代理/中继」类功能拦截流量;也确认 监听范围 没有被切成「仅本机」。

设置页显示「端口已对局域网开放,但尚未设置访问密码」。 这是预期的中间状态:端口可达,但门禁拒绝所有设备,不会泄露数据。去 安全认证 设一个访问密码即可。

「配置的端口 X 已被占用,已自动改用 Y」。 端口被别的程序占着,插件已自行顺延。可在 连接与证书 换端口(带可用性检查),或释放该端口。

局域网设备打开「设置 → 模型」提示 加载提供商目录失败: settings are unavailable in this browser。 这不是本插件的故障:DSH 按页面地址栏是否为回环决定官方设置面是否可用,局域网地址不是回环,于是设置面被永久降级。打开 连接与证书 → 局域网设备可用官方设置页(默认已开),然后在那个设备上刷新页面即可。若仍不行,确认开关确实保存成功、页面是刷新而非切换 tab,并确认 DSH 版本仍在兼容范围内。

「此设备已被移除访问权限」(403)。 该设备已在 已授权设备 中被吊销或拉黑。删除那条记录即可让它重新配对(被拉黑的需先「解除拉黑」)。

远程点「添加工作区」没反应(或对话框开在电脑上)。 这是 DSH 目录选择器的机制:它在启动时判定一次,本机回环绑定 + 有显示器 ⇒ 判成「原生」,对话框开在电脑屏幕上,远程设备看不到也点不到。本插件已把它接管为「本机走系统原生对话框 / 远程走页面内目录浏览器」两套交互。

若远程弹出「远程设备当前只读」,说明策略还是「仅本机」:去电脑的 设置 → 局域网访问 → 安全认证 → 远程设备管理权限 选「密码解锁」(或「不锁定」),然后在远程设备上刷新页面。

若弹出「需要先解锁管理控制台」,直接在弹层里输入管理密码(未设管理密码时用访问密码)点「解锁」,列表会立刻出现——不必再去找设置页的锁定卡片。

我忘了访问密码。 在运行 DSH 的电脑上直连 http://127.0.0.1:3080(本机直连物理免锁)重设。无头服务器则删除 dataDir 下的 secrets.json 后重设——在此之前门禁会拒绝所有设备。

改过密码后所有设备都要重新输密码。 这是有意的:更换访问密码或切换验证模式会吊销所有已有访客会话。

局域网明文 HTTP 被拒绝。 非回环 listenHost + tls.mode: 'off' 会被拒绝,除非显式设置 tls.allowInsecureLan: true——否则门禁密码将明文传输。

升级

设置页会显示「当前版本 → npm 上最新版本」,并给出可复制的升级命令:

dsh plugin --profile web add dsh-lan-guard@latest

插件不会自己安装任何东西,也不会重启 DSH——命令由你执行,执行后手动重启一次 dsh。检测只访问公开的 npm registry,结果缓存 6 小时;连不上时只在界面上提示,不影响门禁与代理。

卸载

dsh plugin --profile web remove dsh-lan-guard
rm -rf ~/.dsh/profiles/web/data/dsh-lan-guard   # 可选:删除密钥、设备记录与 CA

开发

pnpm install
pnpm test          # 单元 + 集成测试(含类型检查)
pnpm run build     # 打包 lib/index.js 与 lib/client.js
pnpm run verify    # 类型检查 + 测试 + 构建 + pack dry-run

客户端半边注册到官方追加型 seat settings.section,宿主半边通过包内 cordis.patch.yml 挂载。

文档索引

文档 用途
docs/dsh-version-adaptation.md DSH 升级后照着走:diff 哪些包、核对哪些接口、怎么落声明与发版
docs/mobile-regression.md 电脑上就能跑的窄屏几何回归:两个入口、四个量化判据(+1 条可选的轮次浮轨判据)、免 token 直连 3080 的方法
docs/mobile-acceptance.md 真机验收清单(触摸、锁屏/切后台、语音这些本地验不了的)
docs/mobile-debug-runbook.md 手机端出问题时的排障手册(含 Web Inspector 探针)
docs/upstream-dsh-0.2.0-rc.1-session-scroll.md 投递上游的回归报告底稿(rc.1 会话裁剪)

发版

发版由 tag 驱动。更新 package.json、把对应 CHANGELOG 段落移出 Unreleased、编写带中英双锚点的 release-notes/v<版本>.md 后,推送发版提交与 tag:

git tag v0.7.10
git push origin v0.7.10

发版工作流会校验 tag 与 package.json 版本一致、要求发布说明含中英双锚点,然后运行 pnpm run verify、打包插件、通过 npm trusted publishing(OIDC)发布,并创建附带 tarball 的 GitHub Release。

许可

MIT

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →

社区评论

评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。