# dsh-mobile-remote

[English](README.md) | 中文

**手机微信 = 你的 DeepSeek Harness 电脑遥控器。**

微信扫码绑定机器人后，在任何网络（4G/任何 WiFi）通过微信指挥你的 dsh agent：
发命令、发任务、收结果、`/ls` 看电脑文件夹目录、**双向收发文件**、**主动推送通知**、**多会话切换**。

- 全权限模式：agent 全部工具可用，不弹确认直接执行（permissionMode 可切 strict 逃生舱）
- 语音消息：不存不转写，回复「无法识别语音消息」
- 消息经腾讯 iLink 服务器中转（微信没有第三方 API），**非端到端加密**——见「风险与边界」

## 突出特点与优势

相比同类 dsh 微信桥接插件（`dsh-weixin`、`dsh-chatnode-wechat`、`dsh-im-bridge` 等）：

1. **协议最扎实**：wire 细节逐字对照腾讯官方 openclaw-weixin SDK v2.4.6（核对笔记与参考源码见 `docs/`）——出站 aes_key 编码、上传/消息两套媒体编号、CDN 上传下载流程全部与官方一致；入站 AES 密钥双编码态（base64 原字节 / base64 hex）全支持，坏密钥明确报错不降级
2. **双向文件收发**：入站图片/文件/视频自动下载解密落盘（稳定命名防崩溃重放 + 明文 md5 校验 + 100MB 上限）；出站 `/send` 与模型回复中的 `[[send-file:路径]]` 双入口，共用唯一路径白名单校验（symlink 穿透/越界/目录一律拒绝）
3. **主动通知闭环**：模型工具 `weixin_send`（文本+文件）+ 未绑定会话完成通知（规则化去重防双发）；会话令牌过期（-14）返回精确文案，健康面板自动降级提示重新扫码，运行中重新扫码**热轮换凭据**无需重启
4. **多会话遥控**：`/sessions` 列表 + `/switch` 编号/标题双语义切换，手机上在多个电脑任务间跳转；会话被其他窗口接管时旧窗口收到提示，杜绝输出串台
5. **可靠性工程（同类少见的重点投入）**：恰一次处理管线（队列+游标同次原子落盘）、outbox at-least-once 投递与崩溃恢复续传、全局处理限额器(8)+下载信号量(3)+背压节流、drain 重入互斥与游标引用身份校验、凭据双写冗余；**182 项自动化测试全绿** + 严格 typecheck + 宿主包零值导入门禁
6. **可观测性**：`/health` 健康端点（字段白名单、零内部标识泄露）、登录页三态状态条（运行中/缺凭据/未启动+原因）、`gateway.log` 文件日志（1MB 轮转、0600）
7. **安全边界清晰**：白名单是唯一使用边界、登录页仅 loopback 可访问、错误文案不泄露密钥与路径、`weixin_send` 文件受同一路径白名单约束

## 安装

```bash
# 1. 安装插件到 web profile
npx @deepseek-ai/dsh plugin --profile web add /path/to/dsh-mobile-remote

# 2. 启动 dsh web（或重启）
dsh web
```

## 扫码登录

1. `dsh web` 运行后，本机浏览器打开 `http://127.0.0.1:3080/mobile-remote-weixin/login`
2. 手机微信扫码，按提示确认（若手机要求输入数字验证码，页面会提示输入）
3. 登录成功后即可在微信里给机器人发消息

登录页顶部有网关状态条（绿=运行中 / 黄=缺凭据 / 红=未启动+原因）；
`GET /mobile-remote-weixin/health` 返回健康快照（仅本机可访问，无内部标识字段）。

## 命令

| 命令 | 作用 |
|---|---|
| `/status` | 会话状态 + 会话标识 + 工作区 |
| `/new` | 断开当前会话，下条消息开新会话 |
| `/stop` | 停止运行中任务 |
| `/reply <文本>` | 追问当前任务 |
| `/sessions` | 最近 10 个会话（当前绑定标 ⭐） |
| `/switch <编号或标题>` | 切换会话：纯数字先按 `/sessions` 编号，越界/无列表回退标题匹配 |
| `/切换聊天窗口：<标题>` | 恒按标题匹配切换（标题恰为纯数字时用它直达） |
| `/send <文件路径>` | 把工作区/收件目录内的文件发到微信 |
| `/ls [路径]` | 看电脑文件夹目录 |
| `/workspace` | 查看当前工作区 |
| `/help` | 再次显示命令清单 |

## 文件收发

- **收**：微信发来的图片/文件/视频自动下载解密，落盘到 `<工作区>/.wechat-inbox/<日期>/`
  （文件名 = 原名 + 消息哈希，稳定命名防崩溃重放重复落盘；大小上限 `maxMediaBytes`，默认 100MiB）。
  模型会看到 `[收到文件] <绝对路径>` 提示，可用 vision 等工具继续处理。
- **发**：`/send <路径>` 直接发；agent 回复中独占一行写 `[[send-file:路径]]` 也会把文件发到微信
  （该指令行不会发给用户）。路径必须落在工作区或收件目录内（同一校验函数，symlink 穿透/越界一律拒绝）。
- 语音消息维持拒识，不下载不落盘。

## 主动通知

- **weixin_send 工具**：微信会话的 agent 可用它把文本/文件推送到微信（绑定的聊天窗口；
  未绑定时回落到 `notifyChatId` 通知目标）。`enableWeixinSendTool`（默认开）可整体关闭。
- **完成通知**：配置 `notifyChatId` + `notifyOnTurnEnd: true` 后，**未绑定微信的**会话任务完成时
  会推送 `✅ 任务完成：会话「标题」` 到通知目标（绑定会话不重复通知；同 turn 已由工具推过则去重）。

## 配置（可选，全部有默认值）

环境变量：

| 变量 | 默认 | 说明 |
|---|---|---|
| `WEIXIN_BOT_TOKEN` | 无 | 登录 token（扫码登录后自动保存，无需手动设置） |
| `WEIXIN_ALLOWED_USERS` | 扫码者自动加入 | 允许使用 bot 的用户 id，逗号分隔 |
| `WEIXIN_ALLOWED_GROUPS` | 空 | 允许的群 id（群聊需 user+group 双命中） |
| `WEIXIN_BOT_API_BASE` | `https://ilinkai.weixin.qq.com` | iLink 网关地址 |
| `WEIXIN_CDN_BASE` | `https://novac2c.cdn.weixin.qq.com/c2c` | 媒体 CDN 地址 |
| `WEIXIN_MAX_MESSAGE_CHARS` | 3500 | 单条回复分块长度 |
| `WEIXIN_MAX_MEDIA_BYTES` | 100MiB | 媒体大小上限 |
| `WEIXIN_PERMISSION_MODE` | `full` | `full`=全权限；`strict`=工具白名单+审批（预留） |
| `WEIXIN_DSH_WORKSPACE` | 自动 | 微信新会话默认工作区（优先级：本变量 > 显式配置 > dsh 当前工作区 > 进程目录） |

cordis 配置项（config 键）：

| 键 | 默认 | 说明 |
|---|---|---|
| `inboxDir` | 空（=工作区/.wechat-inbox） | 收件目录；越界自动回退默认并警告 |
| `enableWeixinSendTool` | `true` | weixin_send 工具总开关 |
| `notifyChatId` | 空 | 完成通知/工具兜底目标（配置后 Web 会话也可用 weixin_send 推送） |
| `notifyOnTurnEnd` | `false` | 未绑定会话完成时推送通知 |
| `logDir` | 空（=state 同目录） | 网关日志目录（gateway.log，1MB 轮转） |
| `statePath` | `~/.dsh/mobile-remote-weixin/gateway-state.json` | 网关状态文件 |

## 健康与日志

- 健康快照（`/health`）：运行状态、轮询活动、连续错误数、绑定数、积压数、凭据、启动失败原因等——
  白名单字段，绝不包含 chatId/sessionId/进度等内部标识。
- 文件日志：`gateway.log`（append-only、0600、1MB 轮转只留一代、写失败静默降级），同时 tee 到 dsh 日志。

## 风险与边界（务必阅读）

- **全权限模式**：无人在环确认；白名单是唯一防线，**只能放你自己的微信号**。
- **weixin_send 外发面**：工具对作用域内 agent 可见，被注入的入站消息可诱导模型把工作区文本/文件推到微信；
  `filePath` 受 `/send` 同一白名单（工作区/收件目录）约束，但文本内容不受路径校验——不要在不信任的群里使用。
- **隐私**：消息经腾讯 iLink 服务器中转（非端到端加密）；媒体解密后落盘于工作区 `.wechat-inbox`。
- **账号共存**：同一微信账号被 OpenClaw 等其他 iLink 客户端驱动会抢消息，需停用其一。
- **凭据**：token 存本机（credentials 服务 + 自管回退文件 0600），泄露等同账号控制权。
- **平台风险**：iLink 非公开稳定 API，可能协议漂移；违规使用有封号风险，自担。

## 局限与未支持功能（诚实声明）

选择本插件前请知悉以下边界——部分是设计决策，部分在路线图外：

- **语音消息不支持识别**：收到语音一律回复「无法识别语音消息」（不下载、不转写、不落盘）。这是设计决策，语音转写不在范围内
- **媒体不进模型多模态管道**：图片/文件只做「落盘 + 给模型绝对路径提示」，插件本身不解析内容；需要配视觉类工具（如 `vision_analyze`）读取
- **无定时任务**：不能预约「每天 X 点做某事」（cron/桌宠类需求不在范围）
- **无远程审批按钮**：全权限模式 = 无人在环确认，微信里没有「批准/拒绝」交互；strict 模式为预留逃生舱（未完成审批流）
- **依赖非公开协议**：iLink 是腾讯未公开的 bot 协议，协议漂移可能导致功能失效，且违规使用有封号风险
- **单账号绑定**：一个 dsh 实例一套微信凭据；要多微信账号/多 bot 需多开 profile 实例（无内置多实例管理）
- **内容级文件去重未实现**：同一文件跨消息重发会重复落盘（仅保证「同一条消息崩溃重放」不重复下载）
- **命令与文案为中文**：斜杠命令、帮助清单、提示均为中文，无英文/i18n
- **无图形化设置面板**：配置全部走环境变量 / cordis patch（登录页仅提供扫码、状态条与健康端点）
- **运行要求**：Node ≥ 22.12、dsh web profile、微信账号需可扫码绑定 iLink bot

## 开发

```bash
npm install
npm run typecheck
npm test
npm run build
```

## License

MIT
