dsh-weixin-channel
DeepSeek Harness(DSH)插件。将 agent 接入微信单聊:扫码授权出一个机器人身份, 支持文本、图片、语音、文件的收发,并把微信用户映射为 DSH 会话。
通信走腾讯官方 iLink Bot API(即微信官方 ClawBot 通道),不是客户端逆向: 不模拟登录、不读取通讯录与朋友圈,只做「一个能收发消息的机器人」。
目录
- 1. 功能
- 2. 兼容性与依赖
- 3. 安装
- 4. 登录
- 5. 工具参考
- 6. 配置
- 7. 安全模型
- 8. 工作原理
- 9. 运维与故障排查
- 10. 已知限制
- 11. 合规与风险
- 12. 开发
- 13. 第三方代码
- 14. 许可证
- English Overview
1. 功能
| 能力 | 说明 |
|---|---|
| 扫码登录 | 生成二维码 PNG,用微信扫码授权;凭证落盘,重启免扫 |
| 入站消息 | 长轮询接收文本、图片、语音、文件、视频;消息 ID 去重 |
| 图片处理 | 入站图片经 DSH attachments 服务转为原生图片块,直接进入模型视野 |
| 语音转写 | 优先使用平台自带转写文本,不依赖外部 ASR 服务 |
| 出站消息 | 文本按句子边界自动切分;图片与文件按扩展名路由 |
| 会话映射 | 微信用户与 DSH 会话确定性映射,重启后可续接上下文 |
| 白名单 | 默认仅放行登录者本人;名单为空时不会退化为放行所有人 |
| 工具收口 | 微信会话可见的工具面被限制为白名单内的若干项(默认 26 个) |
| 插件页二维码 | 在 DSH 界面内直接显示登录二维码,无需翻找文件 |
2. 兼容性与依赖
| 项目 | 要求 | 说明 |
|---|---|---|
| DSH | 宿主包 ^0.1.0-rc.6 或 ^0.2.0-rc.1 |
见 peerDependencies |
| Node.js | >= 22 |
使用内置 fetch、node:crypto 等 |
| 微信 | 已开通 ClawBot / iLink Bot 通道的微信号 | 登录时由服务端下发二维码 |
@deepseek-ai/schemastery |
^3.18.1 |
配置校验 |
silk-wasm |
可选 | 解码 SILK 语音;未安装时落盘为原始 SILK(不影响转写文本) |
| Python | 可选 | 仅用于二维码解码对照测试(tools/qr-suite-check.py) |
3. 安装
3.1 通过 DSH CLI 安装(推荐)
# 从 npm 安装
dsh plugin --profile web add dsh-weixin-channel
# 或从源码安装
dsh plugin --profile web add github:ChencLi819/dsh-weixin-channel
# Desktop 版
dsh plugin --profile desktop add dsh-weixin-channel
安装完成后重启 DSH。
3.2 通过仓库脚本 / link 安装
以 link: 方式安装(改源码即时生效)时,需要在插件目录补一层指向宿主包的链接:
cd <你的 profile 目录>
pnpm add link:<本仓库路径>
# 补依赖链接:插件用 link 装进来时是 junction,Node 会解析成真实路径,
# 于是去真实路径下找 @deepseek-ai/*,找不到会报 ERR_MODULE_NOT_FOUND
$src = "$env:USERPROFILE\.dsh\profiles\node_modules\@deepseek-ai"
$dst = "<本仓库路径>\node_modules\@deepseek-ai"
New-Item -ItemType Directory -Force -Path (Split-Path $dst) | Out-Null
New-Item -ItemType Junction -Path $dst -Target $src
然后在 profile 的 package.json 的 dsh.profile.bundles 中加入 dsh-weixin-channel,重启 DSH。
插件会写入体检文件 ~/.dsh/wechat-channel/diagnostic.json。
3.3 升级与卸载
| 操作 | 步骤 |
|---|---|
| 升级 | 重新执行 CLI 安装命令;link: 方式更新源码后重启 DSH |
| 卸载 | 从 dsh.profile.bundles 中移除,并卸载依赖;如不再使用,删除 ~/.dsh/wechat-channel |
4. 登录
- 调用
wechat_login(action=start)申请二维码,返回 PNG 路径 (默认~/.dsh/wechat-channel/login-qr.png) - 用微信扫码
- 若手机提示输入配对码,调用
wechat_login(action=verify,code=<配对码>)
凭证保存在 ~/.dsh/wechat-channel/sdk/,之后启动自动登录。
action=logout 会停止轮询并删除凭证目录——仅清理内存不够,凭证留在磁盘上时
下次启动会自动登录回来。
二维码必须实时轮询。 二维码存在有效期,且扫码后需要插件在轮询中接收服务端下发的 凭证。因此不要用截图代替插件自身发起的登录流程,也不要关掉进程后再扫。
5. 工具参考
插件注册 3 个工具。所有工具的输出均为 { text: string }。
5.1 wechat_login
管理登录状态。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
action |
string | 是 | start 生成二维码 / verify 提交配对码 / logout 清除登录状态 |
code |
string | 否 | 配对码,action=verify 时必填 |
5.2 wechat_status
查询渠道状态:登录情况、二维码位置、收发计数、白名单。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
selftest |
boolean | false |
为 true 时真实建立一次会话,校验工具收口名单的有效性 |
selftest 会给出权威结论:哪些工具名有效、哪些不存在。日常无需开启。
5.3 wechat_send
向微信发送内容。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
userId |
string | 当前会话对端 | 目标微信用户 ID |
text |
string | — | 文本内容 |
file |
string | — | http(s) 链接(插件自行下载)或本地绝对路径 |
回复当前会话不需要调用此工具,正文会自动发送。text 与 file 同时提供时拆为两条消息发送
(微信不支持图文混合单条消息)。
6. 配置
默认值定义于 lib/config.js。在 profile 的 cordis.patch.yml 中按需覆盖,配置为深合并:
- id: wechat-channel
config:
allow:
users: ['wxid_xxxxxxxx'] # 留空 = 只放行登录者本人
send:
maxPerMinute: 6
session:
resetCommand: '/new'
tools:
restrict: true
allow: [wechat_login, wechat_status, wechat_send]
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
enabled |
boolean | true |
插件总开关 |
stateDir |
string | ~/.dsh/wechat-channel |
状态目录:凭证、context token、日志、媒体 |
baseUrl |
string | https://ilinkai.weixin.qq.com |
iLink Bot API 基址 |
botAgent |
string | dsh-weixin-channel/0.1 |
请求中携带的调用方标识 |
login.autoLogin |
boolean | true |
启动时使用已有凭证自动登录 |
login.qrDir |
string | 空(用 stateDir) |
二维码 PNG 落盘目录 |
login.qrScale / login.qrQuiet |
number | 8 / 4 |
二维码渲染参数 |
login.timeoutMs |
number | 180000 |
扫码等待时长 |
login.expiredCooldownMs |
number | 3600000 |
会话过期后的冷却时长 |
allow.users |
string[] | [] |
白名单;留空退化为只放行登录者本人 |
allow.selfOnly |
boolean | true |
仅放行登录者本人(忽略 users) |
allow.silentDrop |
boolean | true |
非白名单消息静默丢弃 |
session.prefix |
string | wechat |
会话 key 前缀 |
session.resetCommand |
string | /new |
重置会话的指令,留空则不启用 |
session.preset |
string | 空 | 可选的 agent preset;留空见下文说明 |
model.provider / model.model |
string | 空 | 微信会话的模型路由,留空则由宿主解析 |
tts.apiKeyEnv |
string | DASHSCOPE_API_KEY |
语音合成凭据的环境变量名 |
tts.apiKey |
string | 空 | 直接提供凭据(留空则按 apiKeyEnv 查找) |
tts.model / tts.voice |
string | qwen3-tts-flash / Cherry |
合成模型与音色 |
send.maxChars |
number | 2000 |
单条文本上限,超出按句子边界切分 |
send.typing |
boolean | true |
发送前显示「正在输入」 |
send.maxPerMinute |
number | 12 |
出站限流(0 = 不限) |
receive.poll |
boolean | true |
是否开启长轮询收消息 |
receive.dedupeSize |
number | 500 |
去重窗口大小 |
tools.enabled |
boolean | true |
是否注册 wechat_* 工具 |
tools.restrict |
boolean | true |
是否对微信会话启用工具收口 |
tools.allow / tools.deny |
string[] | 见 7.3 | 收口后的工具白/黑名单 |
7. 安全模型
7.1 三道边界
| 边界 | 机制 | 默认 |
|---|---|---|
| 谁能触发 | 白名单:仅处理指定微信用户的消息 | 仅登录者本人 |
| 能做什么 | 工具收口:限制微信会话可见的工具面 | 26 项(无 shell / 文件 / 子代理) |
| 能聊什么 | 仅单聊;协议本身不提供群聊能力 | 不可配置 |
默认拒绝:白名单为空时退化为「只放行扫码登录的那个微信号」,不会因为列表为空而放行所有人。
即便有上述边界,插件背后仍是一个具备工具调用能力的 agent。不要把该渠道暴露给不可信的人。
7.2 工具收口的实现
会话装配时调用 tools.restrict({ allow }),把该 agent 作用域可见的全局工具面限制为
tools.allow 中的项。这是过滤继承面,不是「可见但禁止调用」——模型侧这些工具根本不存在,
因此诱导其执行命令无效。
7.3 默认放行的工具(26 个)
| 分类 | 工具 |
|---|---|
| 渠道与状态 | wechat_login wechat_status wechat_send mood_now mood_feel mood_diary |
| 联网检索 | advanced_search multi_search research cache_search platform_search domain_search image_search video_search grab_image |
| 内容聚合 | feed_scroll feed_read feed_share feed_history feed_watch |
| 表情图库 | meme_list meme_update learn_meme |
| 图像理解 | qqbot_describe_image |
| 实用 | weather qqbot_time |
故意不放行:pwsh write edit subagent workflow send_message(任意命令、文件修改、派生 agent);
qqbot_say qqbot_send_file qqbot_voice_call(QQ 专用发送类);send_meme(输出其他渠道的标记格式);
present exit_plan_mode(GUI 卡片)。
7.4 收口的失效模式
dsh-tools 的 restrict() 遇到不认识的工具名会抛错,而不是忽略该项。
抛错会导致收口未装上、工具面全开——「看起来只是少了个功能,实际是把整面放开」。
插件对此的处理是向安全方向回退:按原样尝试一次,抛错后从报错信息中取出问题名、 移除后重试。因此写错一个名字的后果是「该项不放行」,而不是「全部放行」。
不要在外部预先用 tools.view(undefined).restrictableNames 校验名单:该调用的作用域与
restrict() 内部使用的不是同一个,会误判十余个正常工具为「不存在」。权威清单来自
restrict() 自身的报错信息,或 wechat_status({ selftest: true })。
7.5 收口的能力边界
restrict() 只能在 agent 已继承到的工具面内过滤,无法授予原本不在其中的工具。
部分工具(web_search web_fetch read glob grep write edit pwsh
schedule_* todo_write skill ask_user_question read_image present)不在该继承链上,
写入 allow 也会被剔除;这些工具在微信会话中本就不可见。联网能力由
advanced_search / multi_search / research 提供。
8. 工作原理
8.1 处理链路
登录 wechat_login → 申请二维码 → 扫码 → 配对码 → 凭证落盘
入站 长轮询 → 去重 → 白名单 → 记录 context token → 媒体落盘 / attachments → 投递到 DSH 会话
出站 session/event → 文本切分 → 限流 → 发送(回带 context token)
context token 必须持久化:iLink 要求每条出站消息回带最近一次入站消息的
context_token。插件重启后若丢失该 token,将无法再发送消息。
会话 key 不含 accountId:accountId 是每次扫码登录时服务端新建的机器人身份,
重新扫码即更换。若将其纳入会话 key,重新登录等同于换了一个人,聊天上下文全部丢失。
身份区分依靠微信 userId。
8.2 入站媒体
| 类型 | 处理方式 |
|---|---|
| 图片 | 交给 DSH attachments 服务,转为原生图片块进入模型视野(不依赖工具,收口后仍可用) |
| 语音 | 优先使用平台转写文本;音频落盘至 ~/.dsh/wechat-channel/media/<用户>/ |
| 文件 / 视频 | 落盘并在正文中附带路径 |
attachments 服务不可用时回退为「落盘 + 正文路径」,功能不丢失;启动日志会说明实际路径。
8.3 二维码
插件内置零依赖二维码编码器(lib/qr.js),支持版本 1–10(内容 ≤ 213 字节)。
iLink 登录链接约 89 字节,对应版本 6。
8.4 状态文件
| 路径 | 内容 |
|---|---|
~/.dsh/wechat-channel/sdk/ |
登录凭证(logout 时整体删除) |
~/.dsh/wechat-channel/state.json |
context token、去重窗口、登录元信息(原子写) |
~/.dsh/wechat-channel/diagnostic.json |
体检信息:登录状态、轮询状态、计数、异常标记 |
~/.dsh/wechat-channel/media/<用户>/ |
入站媒体文件 |
~/.dsh/wechat-channel/login-qr.png |
登录二维码 |
9. 运维与故障排查
node tools/run-tests.mjs # 全部 6 套测试
node tools/selftest.mjs # 纯逻辑自测
node tools/e2e.mjs # 端到端集成(假宿主)
node tools/probe-login.mjs # 登录链路探测
node tools/probe-poll-timing.mjs # 长轮询时序探测
node tools/probe-storage.mjs # SDK 存储目录探测
node tools/verify-qr.mjs # 二维码校验
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 工具未注册 | 插件未安装或未重载 | 检查 profile 的 bundles 与 cordis.patch.yml,重启 DSH |
ERR_MODULE_NOT_FOUND(@deepseek-ai/*) |
link: 安装缺少依赖链接 |
按 3.2 补一层 junction |
| 二维码扫了没反应 | 登录轮询中断;二维码过期 | 重新调用 wechat_login;确保进程在轮询(不要用截图代替) |
| 收不到消息 | 长轮询关闭;会话已过期(服务端返回 -14) |
检查 receive.poll;重新扫码登录 |
| 发不出消息 | 缺少 context token(重启后丢失) | 等待一次入站消息以刷新 token |
| 陌生人消息被丢弃 | 白名单生效(预期行为) | 如需放行,在 allow.users 中显式添加 |
| 语音只有文件没有文字 | 平台未提供转写文本 | 属预期;可自行接入 ASR 处理落盘文件 |
10. 已知限制
- 仅支持单聊。 微信机器人协议不提供群聊能力,这不是配置项。
- 出站语音不可用(协议层限制)。 TTS、SILK 编码、CDN 上传、
VoiceItem字段填充 均已验证通过,服务端返回message_id且无错误码,但对端收不到消息。 详见 12.3。入站语音正常。 - 会话过期需重新扫码。 服务端返回
-14时会话作废;插件会记录日志并在diagnostic.json中标记。 - 落盘语音为原始 SILK。 未安装
silk-wasm时不做格式转换;模型本身不解析音频, 实际可用的是平台转写文本,该路径不依赖silk-wasm。 - 二维码仅支持版本 1–10(内容 ≤ 213 字节)。
session.preset默认留空。 preset 依赖dsh-agent-presets扫描用户 preset 根目录; 若部署未扫描该目录(只列出随附 preset),填写后只会持续报警告。 工具收口由tools.restrict负责,不依赖目录发现。
11. 合规与风险
- 使用官方接口。 通信走腾讯官方 iLink Bot API,不涉及客户端逆向、不模拟登录、 不读取通讯录与朋友圈。
- 需要本人扫码授权。 登录凭证属于扫码的微信号,插件以该身份收发消息;
凭证保存在本地状态目录,
wechat_login action=logout可清除。 - 非官方项目。 本插件由社区维护,与腾讯、DeepSeek 均无隶属关系。
- 使用前请自行确认符合微信平台的相关条款与所在地区的适用规定;由使用者承担使用风险。
- 数据流向:消息内容与媒体经 iLink 服务端传输;使用云端 TTS 时文本会发送至所配置的 合成服务(默认阿里云百炼)。如不允许外发,请勿启用语音合成。
- 不要暴露给不可信的人:渠道背后是具备工具调用能力的 agent,尽管已有白名单与工具收口。
12. 开发
12.1 目录结构
index.js 插件入口:name / inject / Config / apply
lib/config.js 配置默认值与深合并
lib/log.js 日志(落盘 + 宿主 logger)
lib/store.js context token / 去重窗口 / 登录元信息(原子写)
lib/qr.js 零依赖二维码编码器
lib/client.js iLink 客户端封装(SDK + 扫码出图 + 限流)
lib/session.js 微信用户 ↔ DSH 会话映射与投递
lib/inbound.js 入站:去重 → 白名单 → 存 token → 媒体落盘 → 投递
lib/outbound.js 出站:session/event → 切分 → 发送
client/ui.js 客户端插件:插件页内的二维码展示
vendor/wechatbot/ 内置 SDK(MIT,见第 13 节)
tools/ 测试与探测脚本
12.2 测试
node tools/run-tests.mjs
包含 6 套:纯逻辑自测、回归测试、插件加载、端到端集成、二维码验收、二维码解码对照。
12.3 验证方法
原则:自洽的实现可以同时是错的,只有外部证据才算数。 (项目曾出现二维码「格式信息副本 2」写反的问题——编码器与解码器同为自己所写,一起错、互相验证通过。)
| 验证对象 | 外部证据 |
|---|---|
| 二维码 | ISO/IEC 18004 测试向量 + OpenCV 独立解码(非自研解码器) |
| 入站媒体解密 | FIPS-197 C.1 的 AES-128 标准向量(首块逐字节比对) |
| 工具收口 | 真实建立一次会话,检查 agent 视角实际可见的工具(wechat_status selftest) |
| 端到端 | 真实微信收发 |
13. 第三方代码
vendor/wechatbot/ 是 @wechatbot/wechatbot 2.2.0
的副本(dist/ 运行时代码与 types/ 类型声明),MIT 许可,作者 corespeed-io。
原始许可文本见 vendor/NOTICE.md。
内置而非 npm install 的原因:离线可用、版本锁定,避免上游发布新版本导致行为变更。
该 SDK 是 iLink 协议的第三方 MIT 实现,与本插件为直接依赖关系。
14. 许可证
English Overview
dsh-weixin-channel connects a DeepSeek Harness agent to WeChat one-to-one chats through Tencent's official iLink Bot API (the ClawBot channel). It performs QR-code authorization to obtain a bot identity, then sends and receives text, images, voice and files, mapping each WeChat user to a DSH session.
It is not a client-side reverse engineering project: no login emulation, no contact-list or
Moments access. Three tools are registered — wechat_login, wechat_status and wechat_send —
and the plugin ships with a default-deny whitelist (only the scanning account is allowed by default)
plus a tool restriction that limits what a WeChat session can invoke (26 tools by default, no shell,
no file access, no sub-agents).
dsh plugin --profile web add dsh-weixin-channel
Known limitations: group chats are not supported by the protocol, and outbound voice messages are
accepted by the server but not delivered to the recipient (documented in section 10).
Requires Node.js >= 22. Licensed under the MIT License; bundled third-party SDK is MIT
(see section 13). This is a community project, not affiliated with Tencent or DeepSeek.
No comments yet. Be the first to write one.