# deepseek-harness-desktop

> 为 dsh web 增加扫码配对式手机远程控制与自更新能力，扫码即在手机小屏独立界面操作工作区。

## Metadata

- Author: [@ningbainb](https://github.com/ningbainb)
- Repo: <https://github.com/ningbainb/deepseek-harness-desktop.git>
- GitHub: [ningbainb/deepseek-harness-desktop](https://github.com/ningbainb/deepseek-harness-desktop)
- Stars: 156
- Language: TypeScript
- License: [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)
- Homepage: <https://ningbainb.github.io/deepseek-harness-desktop/>
- Topics: `ai-agent`, `ai-coding-assistant`, `codex`, `deepseek`, `deepseek-harness`, `desktop-app`, `dsh`, `dsh-plugin`, `electron`, `electron-app`, `gui`, `open-source`, `plugin-system`, `plugins`, `remote-access`, `skills`, `ssh-client`, `windows`, `windows-desktop`
- Forks: 5
- Open Issues: 6
- Last push: 2026-08-20T05:29:52.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:ningbainb/deepseek-harness-desktop/packages/dsh-remote-web-ui
```

## Wiki

## 一句话定位
为 DeepSeek Harness (DSH) 的 Web GUI 增加扫码配对式手机远程控制 ——扫描桌面侧边栏手机图标生成的二维码，手机会进入一个独立、专为小屏设计的轻客户端界面，远程使用当前工作区；侧边栏的下载按钮可一键自更新 dsh-web-ui 全家桶。

## 核心能力
- 扫码配对：侧边栏底部手机图标打开面板，生成一次性、限时的二维码链接，手机扫码即绑定并落到 `/m` 独立移动端界面。
- 移动端独立界面：手机加载的是专为小屏设计的精简界面（工作区、会话、聊天、模型选择），不是把桌面 UI 塞进手机。
- 实时同步：桌面面板和手机之间通过 SSE 实时同步配对状态（等待/已连接/已断开），消息可达时手机端实时推送新消息。
- 局域网栅栏：默认开启后，所有非本机 `/api` 请求必须携带配对 Cookie；点「刷新二维码」旧链接立刻失效，点「停止」全设备所有令牌与会话被清空。
- 一键公网隧道：开启后插件自动拉起 Cloudflare quick tunnel（无需账号/域名），二维码自动变为公网链接；也可手动指定已搭好的 `publicBaseUrl`。
- 一键自更新：侧边栏下载按钮异步检查 npm 上 `@linxin666/dsh-*` 全家桶最新版本，发现新版本自动跑 `pnpm update`，并提示重启 dsh web 生效。

## 技术实现
- **语言**: TypeScript
- **关键依赖**: `@deepseek-ai/cordis`、`@deepseek-ai/dsh-host-webserver`、`qrcode.react`（无 canvas 的纯 SVG 二维码渲染）、`cloudflared`（随包分发的 Cloudflare 隧道二进制）、`schemastery`（DSH 标准配置 schema 校验）
- **架构模式**: 双半区 cordis 插件 —— host 半区（`src/index.ts`）持有配对令牌、设备会话、`/api/pair` 路由族、`/api/update` 端点和 `api/gate` 监听器；browser 半区（`src/client/`）渲染侧边栏入口、配对面板、设置卡片、自更新面板。每个半区都通过 cordis `apply()` 单独挂载，gate 监听器在 `/api` 进入 ApiProxy 之前拦截未配对请求。
- **入口文件**: `src/index.ts`（host 半区） + `src/client/index.ts`（browser 半区）

## 适用场景
经常离开工位但想随时用手机继续某个 DSH 工作区的人 ——比如在沙发上、床上、外出路上想接着写提示词、看日志、追流式输出，又不想在手机浏览器里塞桌面版 UI。安装后扫码即用，手机走的是精简小屏界面，过期可一键刷新、被入侵可一键撤销；附带的一键自更新让多插件用户免去手动 `pnpm update` 的麻烦。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | >= 0.1.0-rc.7 | 依赖 `api/gate` 水位、`sidebar.remote` 座位、host-apiproxy 的 LAN 修复；旧版本缺失这些 seam 会失去部分功能 |
| Node | >= 22.19.0 或 >= 24.0.0 | `package.json#engines` 声明 |
| 平台 | macOS / Windows / Linux | 跨平台；Windows 下更新走 `cmd.exe` 解析 npm 的 `.cmd` shim |
| 原生模块 | `cloudflared` | 一键公网隧道随包自动下载平台二进制，无需用户安装；未启用 autoTunnel 则不依赖 |

## 安装方式
```bash
dsh plugin --profile web add github:ningbainb/deepseek-harness-desktop/packages/dsh-remote-web-ui
```

## 配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| 启用移动端远程控制 | 开关 | 关闭后移除侧边栏入口并停用配对路由与局域网栅栏 | 开 |
| 配对令牌有效期（毫秒） | 数字 | 新生成的二维码链接在此时间后失效，最低 60000 | 600000（10 分钟） |
| 设备离线判定（毫秒） | 数字 | 配对设备超过该时长未上报心跳即视为离线 | 25000 |
| 已配对设备上限 | 数字 | 超过时淘汰最旧的设备会话，范围 1-64 | 4 |
| 设备 Cookie 名 | 文本 | 携带已配对设备标识的 Cookie 名称 | dsh_pair |
| 局域网访问要求配对 | 开关 | 开启：所有非本机的 /api 请求必须带有效配对 Cookie；关闭：LAN 栅栏放开，只保留令牌、状态、撤销 | 开 |
| 公网地址（可选） | 文本 | 内网穿透的公网地址，如 `https://xxx.trycloudflare.com`；填后二维码即变为公网链接 | 留空 |
| 自动公网隧道 | 开关 | 开启后插件自动启动 Cloudflare quick tunnel（无需安装任何工具），并自动更新公网地址与信任配置 | 关 |
| 移动端回车发送 | 开关 | 开启：手机输入框按 Enter 直接发送，Shift+Enter 换行；关闭：Enter 换行，仅点发送按钮发送 | 开 |

## 常见问题

**Q: 安装后启动看不到手机图标？**

A: 三种常见原因：① 启动时绑定 `127.0.0.1` 而没有配置 publicBaseUrl / 开启 autoTunnel，面板会显示「此功能需要…」。请用 `dsh web --host 0.0.0.0` 启动，或在设置里配置公网地址/打开自动隧道；② DSH 版本过旧（< 0.1.0-rc.7）缺少 `sidebar.remote` 座位 —— 升级宿主；③ DSH 设置 allowlist 没把本插件命名空间开放给配置页 —— 走到 `~/.dsh/settings.yaml` 直接配置 `remote-web-ui` 命名空间。

**Q: 关闭「局域网访问要求配对」后还会被断线吗？**

A: 不会。该开关只决定非本机的 `/api` 请求是否必须带配对 Cookie。关闭后令牌、状态、撤销、心跳这些行为都还在；只是 LAN 栅栏被绕过，任何人在局域网内都能直接访问 `/api`，手机配对像是多了一道双保险。

**Q: Quick Tunnel 启动后手机端新消息为何延迟几秒才到？**

A: 这是预期行为。`cloudflared` 的 quick tunnel 与 Tailscale Serve 默认不转发 Server-Sent Events（SSE），而 SSE 是手机端实时收消息的通道。本插件会自动回退到按短间隔轮询 `session.history`，消息仍能到达，只是不够即时；SSE 通道恢复后立刻恢复流式。要真正实时推送，请用 Cloudflare Named Tunnel（域名托管在 Cloudflare）或 TCP 端口转发（LAN 地址、Tailscale 虚拟接口地址、`ssh -L`、cloudflared TCP 隧道都可）。

**Q: 一键自更新能更新什么，不能更新什么？**

A: 面板只更新通过 npm 正常安装的 `@linxin666/dsh-web-ui` 全家桶（执行 `pnpm update`，失败时回退 `corepack pnpm` / `npx --yes pnpm`，Windows 上经 `cmd.exe` 解析 `.cmd` shim）。若插件是 `link:` 安装（开发模式），面板会检测到并只报告显示版本号，提示你在仓库里 `git pull`，不会执行更新（因为 link 模式没有 npm 上对应的版本）。

**Q: 如何撤销已配对的手机？**

A: 在桌面面板点「停止」即可。当前活动的配对令牌与所有绑定设备会话会被立即清空，下一次点「刷新二维码」可重新开始扫码。已连接的手机在下一次请求时会被 LAN 栅栏挡掉（403），实时流也会被切断。

**Q: 移动端聊天能否发图片或附件？**

A: 本插件描述范围内未涉及图片/附件/工具调用确认等富交互，移动端提供文本输入、模型选择、权限切换；mobile 端代码仅做 chat 历史/流式收发、模型/权限切换等 RPC 富能力，附件/工具调用确认走宿主原生会话，手机端只负责文本对话与切换设置。

**Q: 电脑多网卡 / Tailscale 虚拟接口时二维码指向哪个网段？**

A: 面板会列出机器所有非内部 IPv4 字面量供选择（含虚拟适配器），Tailscale 的 `100.x` 虚拟接口也会自动出现。手机与电脑在同一网络时选 LAN 段最稳，不在时选公网地址即可。

**Q: 必须用 Cloudflare 隧道才能从外网访问吗？**

A: 不是。任何把公网地址转回本机 `127.0.0.1:端口` 的隧道都行 —— 只需在插件设置填写 `publicBaseUrl`（如 `https://xxx.trycloudflare.com`），并在 `dsh web` 启动时加 `--trusted-host <对应公网主机>` 让宿主 ApiProxy 信任该主机；云网络未自动把该主机信任，公网 /api 请求会在到达配对层之前就被 403 拒绝。

## 上手难度
入门 — 装好插件、扫码、立即可用；要调整安全策略或走公网的话只需在设置卡片里开/关对应开关，不需要写代码。

## 已知问题与限制
- 撤销是逐请求生效的：点「停止」时还在途中的请求会跑完，下一次请求才会被 403；这是设计上的并发副作用，不是 bug。
- 设备会话在内存中：配对状态（token + devices）随 `dsh web` 进程重置，重启后所有手机需重新扫码。
- 没有逐设备管理 UI：面板只显示聚合状态（等待/已连接 N 台/离线），单一设备的撤销被推迟，需「停止」全部后由下次自动刷新。
- Quick-tunnel 主机名每次都变：`trycloudflare.com` URL 每次 `cloudflared` 启动都是随机新主机名，宿主 `--trusted-host` 与插件 `publicBaseUrl` 需一起更新；用 Named Tunnel（固定主机名）可避免这种抖动。
- 安装了此插件后默认开启 LAN 栅栏：经局域网 URL 打开的桌面浏览器必须像手机一样扫码配对，否则 `/api` 全部 403；想保留原有开放 LAN 行为，把「局域网访问要求配对」关掉即可。
- 自动公网隧道在中国大陆的可达性不保证：Cloudflare 自行判定，本地需自测。
- 当插件以 `link:` 形式（开发模式）安装时，一键自更新按钮只显示当前版本号与「请在仓库里 git pull」提示，不会真正发起更新。
- Dev HMR：`dsh web --dev` 按路径轮询每个 roster bundle，因此重建本包（其自己的 `tsdown --watch`）会热重载 client bundle；这与日常使用无关，仅开发期需要知道。

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [deepseek-harness-desktop](https://deepseek-plugin.org/plugins/ningbainb/deepseek-harness-desktop/packages/dsh-remote-web-ui)
Wiki generated by AI (model: `MiniMax-M3`)
