DeepSeek Harness 手机同步插件:把桌面端 Web UI 反代到手机

前言

DeepSeek Harness(DSH)在本地运行,但有时需要在手机上查看或操作。常见做法是远程桌面,但开销大、体验不连续。66jiujiu99 的 dsh-plugin-remote-mobile 插件提供另一种方案:不重新实现一套移动端 UI,而是把 DSH 桌面端的完整 Web UI 反向代理到手机,手机浏览器打开 http://<LAN-IP>:17890/m 就能看到 PC 上的界面——工作区分组、会话、插件设置、排队提示,全部都在。所有操作在 PC 执行,手机只是遥控器和显示器。

插件是什么

这是 DSH 的一个插件,维护者为 66jiujiu99,归类为「联网工具」。它是一个反向代理层:监听 0.0.0.0:17890,把请求中的 /m 前缀剥离,转发到 DSH 自己的 Web 端口(127.0.0.1:<随机>),并加了一道配对门。手机只拿到一个设备 cookie(dshm_d),DSH 自己的 dsh-auth-* 凭据只在插件内存里,不会下发到手机。

核心能力

  • 整站反代:手机看到的是 PC 上完整的 Web UI,包括工作区分组、会话、插件设置等。
  • 设备配对:首次访问会被跳到配对页,输入 PC 面板上显示的配对码(默认 60 秒有效、5 次尝试)。
  • 只读策略与审批路由:插件对端点做了过滤,某些敏感路由(terminal/、credentials/、plugin/ 等)对所有设备都拒绝。
  • 公网隧道(可选):通过 Cloudflare Quick Tunnel 让手机用任意网络访问,需要单独安装 cloudflared。
  • 零第三方依赖:只用 node 内置模块(http/net/crypto/fs/os/stream),ESM,无构建。

安装与启用

在 DSH 中执行:

plugin_manager install_bundle target=C:\path\to\dsh-plugin-remote-mobile

安装完成后,在配置里设置 enabled: true(或在设置页的「手机同步」里打开开关)。

示例配置(cordis.yml 中本插件那一行的 config):

enabled: true
bindAddress: 0.0.0.0
port: 17890
allowedNetworks: []      # 空 = 只允许私有网段(192.168/10/172.16-31)+ 链路本地 + loopback
deviceOnlineWindowMs: 90000
deviceOfflineTtlMs: 43200000

注意:插件重载(set_plugin enabled=false/true)会用运行时状态重写 ~/.dsh/profiles/<profile>/cordis.yml。所以手工改完配置后必须重启 DSH,不要在中间重载插件,否则配置会被覆盖回旧值。

第一次使用

  1. 手机连上和 PC 同一个 WiFi / 热点(PC 开热点就是 192.168.137.1)
  2. 手机打开 http://<PC-IP>:17890/m
  3. 会被跳到配对页,输入 PC 面板上显示的配对码
  4. 配对成功后进入完整界面;cookie 有效期由 cookieMaxAgeDays 控制(默认 180 天)

找不到 IP?在 PC 上跑:

netstat -ano | findstr :17890

或直接看插件面板,它会列出所有可用 URL。

公网隧道(可选)

不开隧道时,手机必须和 PC 在同一个 WiFi / 热点。开了隧道,手机可以用任意网络访问。用的是 Cloudflare Quick Tunnel:匿名、免账号、免服务器、免公网 IP。

怎么装 cloudflared

插件本身不带 cloudflared(55MB,不适合进仓库)。三种方式任选:

  1. 放便携版:把 cloudflared.exe 丢进本插件的 tools/ 目录,插件会自动找到。
  2. 指定路径:配置 tunnel.binaryPath: "C:/path/to/cloudflared.exe"。
  3. 装进 PATH:winget install Cloudflare.cloudflared,插件会自动从 PATH 找。

下载地址(官方 Release):

https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe

怎么开

  1. 先把「手机同步」开起来(隧道要指向监听端口)
  2. 设置页 →「手机同步」→「公网隧道」→ 开启隧道
  3. 等 10-60 秒(Cloudflare 分配域名),面板会显示公网地址
  4. 用手机打开那个地址 → 配对 → 进入

注意:这个开关是手动的,刻意不跟 enabled 联动:开了监听不等于要开公网。

四条行为约定

  • 域名每次都变:Quick Tunnel 不做持久域名。重连或重启后就是新地址
  • 空闲自动断开:tunnel.idleTimeoutSec(默认 1800 秒)内没有请求就自动关。设 0 = 永不主动关
  • 断开后自动重连:tunnel.reconnect(默认开)。指数退避重试,reconnectBudgetSec 内一直试
  • 父死子亡:DSH 崩溃或被任务管理器结束,隧道会跟着消失(靠管道所有权,不依赖清理代码)

「链接多久失效」怎么配

Quick Tunnel 的域名由 Cloudflare 分配,cloudflared 进程一退出域名就作废。所以”有效期”是两层,都能配:

tunnel:
  # ① 我们主动关:多久没请求就关。0 = 永不主动关。
  idleTimeoutSec: 0
  # ② 意外断开后自动重连(会换新域名)。不想要就设 false。
  reconnect: true
  reconnectBudgetSec: 86400   # 重连额度:一天内一直尝试
  reconnectMaxDelaySec: 60    # 退避上限

想尽量不失效:idleTimeoutSec: 0 + reconnect: true + 大 reconnectBudgetSec。

面板上也能改:设置 → 手机同步 →「公网隧道」→ 空闲断开下拉框。
改完立即生效,不用重启;选「永不主动关」等于 idleTimeoutSec: 0。
面板改的是运行时值,重启后会回到 cordis.patch.yml 里的配置。

必须知道的硬限制:DSH 一关(或被强杀),域名必然失效。这是 Quick Tunnel 的固有代价,不是本插件的限制——想拿固定域名得用 Cloudflare named tunnel(需要账号与自有域名),本插件不做。

配置项

tunnel:
  provider: quick              # none | quick(frpc 仍是预留形状,未实现)
  binaryPath: ''               # 空 = 自动找 PATH 与 tools/
  idleTimeoutSec: 1800         # 空闲多久自动关(0 = 永不主动关)
  reconnect: true              # 意外断开后自动重连(换新域名)
  reconnectBudgetSec: 3600     # 重连总时长上限
  reconnectMaxDelaySec: 60     # 指数退避上限
  startTimeoutSec: 90          # 等域名的最长时间

安全模型

防线 做法
来源网段 allowedNetworks 为空时只放行私有网段 + 链路本地 + loopback
DNS rebinding Host 必须是本机真实监听地址之一(LAN IP / hostname / localhost)
跨站请求 Origin 必须与 Host 同 authority;Sec-Fetch-Site: cross-site 一律拒
凭据隔离 上行抹掉手机的 Cookie/Authorization/Sec-Fetch-*/X-Forwarded-*;下行抹掉 Set-Cookie/HSTS
永久黑名单 terminal/、credentials/、plugin/、plugin-manager/、settings/ —— 任何设备任何路由都拒
只读设备 fail-closed 白名单:只允许读取类端点,未知端点直接 403
撤权 单设备撤销立即断开在途连接;kill 急停但保留设备表,可恢复

必须知道的残余风险:

  • 插件跑在宿主进程内、工作区沙箱之外,和你的用户权限相同。
  • 开了监听就等于同一网络里的人可以探测到这个端口(虽然有配对门)。
    在公共 WiFi 下建议关掉,或把 allowedNetworks 收紧到你家网段。
  • 开了隧道 = 全球可达。这时候「同一 WiFi」这道天然边界没有了,只剩配对码。
    配对码是 8 位(31 字符表,≈39.6 bit),并且按 IP 限流(10 次/分钟)+ 单码 5 次错误即锁 60 秒。
    在公网暴露期间请勿把配对码发到会被转发的渠道。
  • 隧道用的是匿名 Quick Tunnel,流量经 Cloudflare 边缘。不要通过它传你不想让第三方看见的内容。
  • Cloudflare 不保证 Quick Tunnel 的可用性;本插件会在空闲超时后主动关闭,减少暴露窗口。

已知限制

  1. 隧道只有 Quick Tunnel:域名每次重启都变(不做持久域名,那需要 Cloudflare 账号与 named tunnel)。
    frpc 仅在配置里预留形状,未实现。
  2. 手机上的「能力」降级:
    - 会话、消息、工作区分组:可用
    - 发消息、选模型、切换工作区:可用
    - 插件设置、账号、审批:可用
    - 文件浏览 / 读工作区文件:可用(走 RPC 回 PC 读)
    - Agent 浏览器(webview):不可用(需要 Electron 主进程的 webview lease API)
    - 终端:取决于实现(若走 Host RPC 则可用;若依赖本地 pty 则不可用)
    - 选目录 / 打开文件对话框:弹在 PC 上(由 PC 的 Electron 弹出)
  3. 布局不专门适配手机:DSH Web UI 是给宽屏设计的,手机上侧栏会挤。
    目前”能用”,但没有专门的移动端 CSS。
  4. webview 类插件在手机上不可用:架构限制,不是配置问题。

排障

现象 原因 / 处理
连不上、端口没监听 enabled 还是 false。改了配置要重启 DSH,不要用插件重载
页面白板但 UI 在 某个 WebSocket 流被拒。看 ~/.dsh/remote-mobile/debug.jsonl 里的 ws-frame-denied
会话全在「未分组」 同上,通常是 remoteAccess/follow 被拦
资源 404 地址少了尾斜杠,确认是 /m/ 不是 /m
被弹回配对页 设备 cookie 过期或被撤销,重新配对
点「Agent 浏览器」报错 预期行为,见上文能力表

适用场景

适合需要在手机上查看 DSH 界面,但不想或不便使用远程桌面的用户。典型场景:

  • 在公司或家庭网络内,用手机快速查看会话、切换工作区、审批操作。
  • 不想折腾远程桌面或 VNC,只希望「多一个屏幕」。

注意:插件以当前 DSH 进程权限运行,安装前应检查源码与许可证。在公网暴露期间请勿把配对码发到会被转发的渠道。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜