Voice
Dsh Phone Bridge
在手机微信里接着电脑上正在聊的 DeepSeek Harness (DSH) 会话:共用同一段上下文,不是新建机器人。经 OpenClaw 连微信/元宝。Drive your existing DSH desktop session from WeChat: same thread, not a new bot.
Install
openclaw plugins install openclaw-dsh-bridge
README
# dsh-phone-bridge
**在手机微信里,接着你电脑上正在聊的那个 DeepSeek Harness(DSH)会话。**
手机上的方案大多是在给 DSH 加一个新入口:机器人自己开一个会话,你在手机上聊的和电脑侧边栏里那些是两个东西,上下文不通。
这个项目做的是另一件事。**微信里 `/list` 列出来的就是你桌面上那些会话**,选一个,你说的话落进那个会话本身,回到电脑上打开接的是同一段上下文。手机到电脑那层通道走的是 OpenClaw。
具体一点。你在电脑上让 DSH 查个东西,聊到一半要出门。别的方案会给你一个全新机器人,前面聊的全部丢失,你得重新交代一遍。这个方案你在地铁上 `/list`、`/use 3`,接着问,它记得前面所有内容;回家打开电脑,还是那条线,历史都在。
**这是它最大的优势。** 别的方案给你一个新机器人,这个让你接着自己那条线说。
[English](README.en.md) | 中文 · [GitHub](https://github.com/KaiyeZeng/dsh-phone-bridge) · [Gitee 镜像](https://gitee.com/zeng-kaiye103/dsh-phone-bridge)
---
## 和别的方案的区别
| | 常见方案 | 本项目 |
|---|---|---|
| 会话归属 | 机器人自己新建 | **桌面已有的真实会话** |
| 手机上能选会话吗 | 不能 | **能**(列出 / 切换 / 搜索) |
| 和电脑的上下文 | 两套 | **同一段** |
| 会话管理 | 只有「新会话」 | 列表 / 切换 / 搜索 / 重命名 / 删除 / 回收站 |
| 权限门 | 各自实现 | **白名单**(只允许指定的聊天账号) |
## 为什么中间多了一层 OpenClaw
这是本项目最大的使用门槛,值得说清楚它从哪来、以及能不能省掉。
**DSH 的 HTTP 端口只监听 `127.0.0.1`**,手机即使连在同一个 WiFi 下也够不到它(实测本机局域网地址是 `172.21.61.86`,而端口只绑回环)。要用手机操作电脑上的 DSH,需要两样东西:**一个跑在 DSH 进程里的半边**(只有它拿得到那个正在聊的会话),加**一条手机到电脑的通路**。
OpenClaw 是这条通路的实现,不是唯一实现。选它是因为它已经把微信、元宝这些渠道接好了,复用比自己写省事得多。
**不想装 OpenClaw 也有一条路**:自己实现渠道协议。腾讯的 iLink Bot 协议是官方的、扫码即可登录,写一个 Node 脚本直接跟它通话就行(`gtaifu/dsh-wechat-bridge` 就是这么做的)。代价是协议得自己跟着腾讯的变更维护,而且那种做法的通路走的是 `dsh` 的 headless 子进程——**驱动的是新建的一次性会话,不是你桌面上那个**。要不要为省一层而换掉「接已有会话」这一点,取决于你更需要哪个。
不自己写协议的话,剩下的选择是明确的两选一:要么把 DSH 的端口暴露到局域网,攻击面直接变大(而「所有路由只绑回环」正是这个方案的安全优点之一);要么加一层本机够得到、手机也够得到的中转。本项目选后者。
换句话说,这一层不是多余的包装,**它是手机能到达电脑的那条路**。
## 组成
两个插件,一个仓库,**都要装**:
```
手机聊天软件 ──► OpenClaw ──► openclaw-plugin ──HTTP──► dsh-plugin ──► 桌面会话
│ │
└──────────── 提问 / 审批 ─────────────────┘
```
| 目录 | 装在哪 | 职责 |
|---|---|---|
| [`openclaw-plugin/`](openclaw-plugin/) | OpenClaw | 拦截聊天消息,翻译成 HTTP 调用;把挂起的提问和审批带回聊天窗口 |
| [`dsh-plugin/`](dsh-plugin/) | DSH 桌面版 | 在回环地址上暴露一组路由,操作真实的 `sessionController` |
**为什么必须有 DSH 里那一半**:`dsh --profile headless` 会拒绝任何带 agent preset 的会话(源码里写死的 `if (preset !== void 0) throw`),所以从进程外面驱动桌面会话这条路走不通。跑在桌面进程内部,`sessionController` 就在手边。
## 功能
**手机侧**
- `/list [页]` 列出桌面会话
- `/use <序号>` 切换到某个会话
- `/where` 看当前接的是哪个
- `/find <词>` 按标题找;`/find-any <词>` 连正文一起找
- `/new` 新建会话
- `/name <标题>` 重命名
- `/status` 看当前会话状态、工作目录、活跃时间
- `/recent [条数]` 看你最近几条发给这个会话的话,默认 3 条
- `/kill` 停下正在跑的任务(排队的消息保留)
- `/model [序号]` 查看可用模型,带序号就是切换
- `/health` 自检链路各层是否接通
- `/del <序号>` 删除(移进回收站,可恢复)
- `/trash` 看回收站;`/restore <序号>` 放回原位置;`/purge` 彻底清除
- `/pending` 看挂起的提问或审批
- `/help` 全部命令
> `/stop` 用不了,它是 OpenClaw 自己的中断指令,在插件之前就被截走,而且只掐断 OpenClaw 的回复,不会停 DSH 里的任务。要停 DSH 的任务用 `/kill`。
**双向**
- 直接发消息 = 发给当前会话
- 有挂起的提问或审批时,普通消息当作答复
- 想强行当聊天发,用 `//` 开头
- 指令打错时会给建议(`/lst` → 「是想用 /list 吗」),而不是把这条错指令原样丢给 DSH
- 被白名单拦下时,会**把你的身份 ID 回给你**,你直接复制进 `allowedSenders` 就行,不用去翻电脑上的日志
- **上一个任务还在跑时,再发消息会立刻收到提示**(「已经跑了多少秒,这条我没发出去」),而不是把消息塞进队列让你干等
## 安装
分两边装,**各一行命令**。
**DSH 侧:**
```
dsh plugin --profile <你的 profile> add dsh-phone-bridge
```
`<你的 profile>` 通常是 `desktop`。装完**重启 DSH**——client 半边在启动时扫描,配置热重载对它无效。
**升级要手动改范围**:`dsh plugin add` 不会跨小版本升级,因为 0.x 版本的 `^` 只允许同一个次版本(`^0.2.4` 等于 `>=0.2.4 <0.3.0`),它会回你一句 `Already up to date`。步骤见 [docs/installation.md](docs/installation.md) 的「以后怎么升级」。
这个包**自带 bundle 声明**(`dsh.bundle.patch` 指向包内的 `cordis.patch.yml`),所以 `dsh plugin add` 会把 host 半边的条目注册进 profile,client 半边会跟着自动挂上,**不需要手工编辑任何配置文件**。
**OpenClaw 侧:**
```
openclaw plugins install openclaw-dsh-bridge --force --accept-capabilities
```
两个参数都得带,缺一个装不上。装完在 `openclaw.json` 里放行并配置(见下一节)。完整步骤见 [docs/installation.md](docs/installation.md)。
装之前确认 `~/.openclaw/extensions/` 里没有这个插件的第二份副本——旧备份目录也会被当成插件加载,两份同时跑会把同一条消息转发两次。
**本地开发时**也可以不走 npm:DSH 侧用 `file://` 把 `dsh-plugin/` 挂进 profile,OpenClaw 侧把 `openclaw-plugin/` 复制进扩展目录,改完代码不用重装。两边都别同时用安装版和本地版。
## 配置
**OpenClaw 侧**(`openclaw.json` 里这个插件的配置段):
| 字段 | 默认 | 说明 |
|---|---|---|
| `enabled` | `true` | 总开关 |
| `allowedSenders` | `[]` | **允许的聊天账号 ID**。空 = 不限制(危险) |
| `allowGroups` | `false` | 是否响应群聊 |
| `bridgeUrl` | `http://127.0.0.1:19387/phone-bridge` | DSH 侧地址 |
| `notifyUrl` | `http://127.0.0.1:19387/dsh-notify` | 提问/审批接口 |
| `pageSize` | `15` | `/list` 每页条数 |
| `turnTimeoutMs` | `300000` | 单轮超时 |
**DSH 侧**(profile 的 `cordis.patch.yml` 里那一行的 `config`):
| 字段 | 默认 | 说明 |
|---|---|---|
| `routePath` | `/phone-bridge` | 路由前缀 |
| `timeoutMs` | `300000` | 单轮默认超时 |
| `pollIntervalMs` | `1000` | 轮询间隔 |
| `maxBodyBytes` | `1048576` | 请求体上限 |
| `trashDir` | 空 | 回收站位置;空 = `<dsh 家目录>/deleted-sessions` |
**这个插件没有任何 npm 依赖**,只用 Node 内置模块。这是刻意的:用 `file://` 从磁盘直挂时没有 `node_modules`,一旦 import 了第三方包(比如 `@deepseek-ai/schemastery`),加载会失败,**而且失败时整条手机链路一起失效**。所以配置不走 schema 校验,而是直接取 `apply()` 的第二个参数,留空就用上面的默认值。
## 安全
**这个插件能把你的电脑交给聊天窗口那一端。** 请务必:
1. **一定要设 `allowedSenders`**,只填你自己的账号 ID。留空等于任何能给机器人发消息的人都能驱动你的会话。
2. **路由只监听回环地址**。不要把它映射到公网。
3. 聊天账号的凭据、`openclaw.json` 里的 token,都不属于本仓库,自己保管。
## 已知限制
- **依赖 OpenClaw**。不想装 OpenClaw 的话这个方案用不了。
- **微信渠道拿不到发送者 ID 的情况**:某些渠道的 `event.senderId` 是空的,身份要从上下文里取。插件会尝试多个字段,一个都取不到时**拒绝请求**(宁可不响应)。
- **走的是 DSH 内部接口**(插槽、`sessionController`、`~/.dsh` 下的目录布局)。DSH 升级可能失效,见下面的兼容性说明。
- **`/kill` 是协作式取消**。它会中断正在进行的对话轮次,但如果当前跑的是一条不响应中断的长命令(比如一次性的 `sleep`),要等它自己结束才真正停下。回执是立刻返回的,**收到回执不等于任务已经停了**。
- **任务运行期间发的消息不会被投递**。上一个任务没跑完时,你下一条消息会被拦下并回一句提示,而不是排进队列。这样做是为了不让你对着一个没有反应的窗口干等,代价是那条消息要重发。想排队就用 `/kill` 停掉当前任务再发。
- **`/stop` 用不了**,别试。它是 OpenClaw 自己的中断指令,在插件之前就被截走,而且只掐断 OpenClaw 的回复,不会停 DSH 里的任务。同理 `/halt`、`/abort`、`/interrupt`、`/exit`、`/停止`、`/暂停` 也都被它占用。
- **npm 刚发布的版本要几分钟才可查**。这期间 `npm view` 可能报 404,不是发布失败。
## 兼容性
### 实测环境
下面这些是**实际跑通的组合,不是「理论上支持」**:
| | 版本 |
|---|---|
| DSH 桌面版 | `0.2.0-rc.2`(profile `desktop`) |
| OpenClaw | `2026.9.5` (ec9c1a1) |
| 操作系统 | Windows 11 家庭版(中文) |
| 已验证渠道 | 微信(`@tencent-weixin/openclaw-weixin` 2.4.8)、元宝(2.18.3) |
| 已验证 Node | 24.x |
**升级 DSH 之后第一件事**:跑 `verify.ps1`,或者直接看 `GET /phone-bridge/health`。它会列出插件用到的 11 个 `sessionController` 方法里有没有缺失的,并对比 DSH 版本和这个插件实测过的版本。缺了就是 DSH 改了内部接口,报错会直接点名是哪个方法,不用逐个路由试。
### DSH 升级之后怎么办
**这个插件不会随 DSH 版本自动适应**,DSH 改了内部接口就得改代码。这是有意的选择:做「猜候选方法名」的自适应性,猜错时会静默绑到名字相近但语义不同的方法上,那比一个红灯危险得多。
所以维护方式是「**自动发现 + 手动修**」,其中发现那一环是自动的。
**使用者升级 DSH 之后:**
1. 电脑上跑 `verify.ps1`,或者手机上发 `/health`
2. 两者都会直接告诉你是接口变了,还是别处的问题;`/health` 还会说 DSH 版本和实测版本是否一致
3. 如果报「缺某个方法」,那就是 DSH 改了内部接口,去看仓库有没有新版本,有就按安装文档更新
**仓库这边是自动的:** 每天定时跑一次 DSH 接口契约检查,对象是 npm 上 `@deepseek-ai/dsh-api-session-controller` 的 `next` 标签(它和桌面版是同一条线,实测 `next` = `0.2.0-rc.2`,与桌面版完全一致)。DSH 一旦改名或删掉插件用到的方法,CI 就红——在任何一个使用者升级之前。
**还有一种它检测不了的失效**:方法名没变、参数没变,但**语义变了**(比如 `cancel` 从「中断当前轮次」变成「清空队列」)。这种谁都检测不出来,只能靠升级后留意行为变化。这也是 `/health` 显示版本对比的全部意义。
### 失效时的排查顺序
两个半边都依赖 DSH 与 OpenClaw 的内部实现,版本升级可能失灵。按这个顺序查:
1. **DSH 侧路由没挂上** → 看 DSH 启动日志里有没有 `phone-bridge listening on /phone-bridge`
2. **`sessionController` 变了** → 先看 `/phone-bridge/health` 报缺哪个;用到的方法只有 `cancel`、`create`、`follow`、`inspect`、`list`、`modelCatalog`、`page`、`prompt`、`rename`、`search`、`selectModel` 这几个,调用全在 `dsh-plugin/index.js` 里
3. **`~/.dsh` 下的目录布局变了** → 会话在 `sessions/<cwd 编码>/<sessionId>/`,投影缓存在 `storages/session_projcache/sessions/`
4. **OpenClaw 的钩子变了** → 用 `api.on("before_dispatch", ...)`,不是 `api.registerHook`(后者对这个事件不生效)
## 维护状态
本项目由作者在业余时间维护。欢迎提 issue 和 PR,会尽量响应,但无法保证响应时间。功能建议、兼容性问题(DSH 或 OpenClaw 升级导致失效)都欢迎提出。
## 许可
MIT,见 [LICENSE](LICENSE)。
voice
Comments
Sign in to leave a comment