前言¶
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,不要在中间重载插件,否则配置会被覆盖回旧值。
第一次使用¶
- 手机连上和 PC 同一个 WiFi / 热点(PC 开热点就是
192.168.137.1) - 手机打开
http://<PC-IP>:17890/m - 会被跳到配对页,输入 PC 面板上显示的配对码
- 配对成功后进入完整界面;cookie 有效期由
cookieMaxAgeDays控制(默认 180 天)
找不到 IP?在 PC 上跑:
netstat -ano | findstr :17890
或直接看插件面板,它会列出所有可用 URL。
公网隧道(可选)¶
不开隧道时,手机必须和 PC 在同一个 WiFi / 热点。开了隧道,手机可以用任意网络访问。用的是 Cloudflare Quick Tunnel:匿名、免账号、免服务器、免公网 IP。
怎么装 cloudflared¶
插件本身不带 cloudflared(55MB,不适合进仓库)。三种方式任选:
- 放便携版:把
cloudflared.exe丢进本插件的tools/目录,插件会自动找到。 - 指定路径:配置
tunnel.binaryPath: "C:/path/to/cloudflared.exe"。 - 装进 PATH:
winget install Cloudflare.cloudflared,插件会自动从 PATH 找。
下载地址(官方 Release):
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe
怎么开¶
- 先把「手机同步」开起来(隧道要指向监听端口)
- 设置页 →「手机同步」→「公网隧道」→ 开启隧道
- 等 10-60 秒(Cloudflare 分配域名),面板会显示公网地址
- 用手机打开那个地址 → 配对 → 进入
注意:这个开关是手动的,刻意不跟 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 的可用性;本插件会在空闲超时后主动关闭,减少暴露窗口。
已知限制¶
- 隧道只有 Quick Tunnel:域名每次重启都变(不做持久域名,那需要 Cloudflare 账号与 named tunnel)。
frpc仅在配置里预留形状,未实现。 - 手机上的「能力」降级:
- 会话、消息、工作区分组:可用
- 发消息、选模型、切换工作区:可用
- 插件设置、账号、审批:可用
- 文件浏览 / 读工作区文件:可用(走 RPC 回 PC 读)
- Agent 浏览器(webview):不可用(需要 Electron 主进程的webviewlease API)
- 终端:取决于实现(若走 Host RPC 则可用;若依赖本地 pty 则不可用)
- 选目录 / 打开文件对话框:弹在 PC 上(由 PC 的 Electron 弹出) - 布局不专门适配手机:DSH Web UI 是给宽屏设计的,手机上侧栏会挤。
目前”能用”,但没有专门的移动端 CSS。 webview类插件在手机上不可用:架构限制,不是配置问题。
排障¶
| 现象 | 原因 / 处理 |
|---|---|
| 连不上、端口没监听 | enabled 还是 false。改了配置要重启 DSH,不要用插件重载 |
| 页面白板但 UI 在 | 某个 WebSocket 流被拒。看 ~/.dsh/remote-mobile/debug.jsonl 里的 ws-frame-denied |
| 会话全在「未分组」 | 同上,通常是 remoteAccess/follow 被拦 |
| 资源 404 | 地址少了尾斜杠,确认是 /m/ 不是 /m |
| 被弹回配对页 | 设备 cookie 过期或被撤销,重新配对 |
| 点「Agent 浏览器」报错 | 预期行为,见上文能力表 |
适用场景¶
适合需要在手机上查看 DSH 界面,但不想或不便使用远程桌面的用户。典型场景:
- 在公司或家庭网络内,用手机快速查看会话、切换工作区、审批操作。
- 不想折腾远程桌面或 VNC,只希望「多一个屏幕」。
注意:插件以当前 DSH 进程权限运行,安装前应检查源码与许可证。在公网暴露期间请勿把配对码发到会被转发的渠道。