扫码后即可在微信里与本地 AI 助手对话
30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/17 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add 350Lxin/dsh-wechat-bridge该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
扫二维码,就能在微信里找到你的 DeepSeek Harness 智能体(腾讯 iLink 机器人通道)。
README
由 DeepSeek 最新模型翻译生成微信桥接(WeChat Bridge)
扫一个二维码,就能从微信里跟你这台机器上的 Harness AI 对话。
底层走的是腾讯官方的 iLink 机器人通道(ilinkai.weixin.qq.com),不是 Wechaty / WeChatFerry 那套个人号 hook。不需要公网 IP、不需要 Docker、不需要自建服务器。
安装
从 GitHub 装(推荐先克隆,路径安装更便于改配置)
git clone https://github.com/350Lxin/dsh-wechat-bridge.git
dsh --profile web plugin install /absolute/path/to/dsh-wechat-bridge
装完必须重启一次 Harness,然后按下面三步配置。
要求:Node.js ≥ 20。
展开完整文档(README 剩余 1 节)
微信桥接(WeChat Bridge)
扫一个二维码,就能从微信里跟你这台机器上的 Harness AI 对话。
底层走的是腾讯官方的 iLink 机器人通道(ilinkai.weixin.qq.com),不是 Wechaty / WeChatFerry 那套个人号 hook。不需要公网 IP、不需要 Docker、不需要自建服务器。
安装
从 GitHub 装(推荐先克隆,路径安装更便于改配置)
git clone https://github.com/350Lxin/dsh-wechat-bridge.git
dsh --profile web plugin install /absolute/path/to/dsh-wechat-bridge
装完必须重启一次 Harness,然后按下面三步配置。
要求:Node.js ≥ 20。
装上之后先做三件事
1. 重启 Harness
插件第一次装载要重启一次(之后改配置不用):
停掉正在跑的 dsh,然后
dsh --profile web web --port 3080
2. 配置工作目录(必做)
编辑 ~/.dsh/profiles/web/cordis.patch.yml,加一节覆盖:
- id: wechat-bridge
name: 'dsh-wechat-bridge/host/bridge.js'
disabled: false # 打开通道;默认是关的 config: workspaceDir: 'C:\Users\you\Desktop\dsh-wechat-workspace'
allowFrom: [] # 白名单:留空 = 谁都不回
allowGroups: [] # 群聊白名单:留空 = 群里不回
permissionPreset: 'read-only'
wechat-bridge 默认 disabled: true。这是故意的:打开通道等于允许白名单里的人花你的 token。
3. 打开面板扫码
在 Harness Web UI 左侧图标栏点「微信」→「显示二维码」→ 用手机微信扫。
扫码时微信客户端会弹出授权页,请读一下上面的文案再确认。
扫完在「最近消息」里能看到对方 ID(形如 xxxx@im.wechat),点「加入白名单」,然后就能在微信里跟它说话了。
三个插件的分工
| 行 id | 作用 | 默认 |
| wechat-ilink | iLink 协议层:扫码登录、长轮询收消息、发消息、媒体下载解密 | 开 |
| wechat-bridge | 会话桥:白名单、权限、每人一个只读会话、回复提取与分片 | 关 |
| wechat-web | 面板路由:/dsh-wechat/ | 开 |
wechat-web 的每个路由都会先问 connection.requestRejection(),走官方的 Host/Origin 栅栏和浏览器鉴权;未通过的一律 401/403。服务缺失时失败即关闭(503),不会裸奔。
配置项(wechat-bridge)
| 字段 | 默认 | 说明 |
| workspaceDir | (空) | 每个联系人一个子目录。空 = 不工作,必须配 |
| allowFrom | [] | 允许的私聊联系人 ID 列表。空 = 全拒 |
| allowGroups | [] | 允许的群 ID 列表。空 = 群里不回 |
| groupRequireMention | true | 群里必须 @ 机器人才回 |
| replyToSenderInGroups | true | 群里回复发给发信人而不是群。见下方「群聊的未验证之处」 |
| permissionPreset | read-only | 会话锁定的权限预设 |
| maxInboundChars | 4000 | 入站消息字数上限;超限直接回绝,不会建会话、不消耗额度 |
| maxRepliesPerPeerPerDay | 50 | 每人每天回复上限(超限每天只提醒一次) |
| maxReplyChars | 200 | 单条微信消息字数上限 |
| replyTimeoutMs | 180000 | 单次回复等待上限 |
| historyTurns | 12 | 预留(会话上下文由 Harness 会话日志自己维护) |
群聊的未验证之处(请读)
WeixinMessage 的 schema 里有 group_id 字段,但腾讯官方那个客户端在全部 20 多个源文件里 一次都没读过它,而且把每条消息的 ChatType 硬编码成 'direct',回复一律发给 from_user_id。
这有两种可能,我无法从接口本身判断:
1. 群消息根本不会被投递到这个机器人通道;
2. 会投递,但官方客户端选择只当作私聊处理。
因此「在群里 @ 机器人」这条路我没有验证过,而且群内回复很可能发不出去(因为 sendmessage 用的是 to_user_id,把群 ID 填进去是否被接受未知)。
我的处理:
- 默认 replyToSenderInGroups: true —— 群里被 @ 后,回复私聊发给发信人。
这与唯一存在的参考实现一致,是已知能工作的方向。
- 群聊仍然有独立的会话(按群 ID 隔离上下文)和独立的白名单条目。
- 如果你实测出群内回复可行,把它设成 false 即可,不用改代码。
如果你不打算用群聊,这条完全不影响你。
安全边界(请读完)
- 白名单默认全拒。 不在名单里的消息会被记录为「忽略」但绝不回复。
- 只读。 会话固定在只读权限预设上:能读文件、能搜索、能联网,不能改盘、不能执行命令。
- 每个联系人一个独立目录,workspaceDir/<peer-id>/,互相看不到对方的文件。
- 微信侧输入不可信。 回复会做 Markdown 降级(去表格/代码围栏/强调符)、截断、分片;不返回本地路径。
- 只读 ≠ 零权限。 只读沙箱仍然能读工作目录里的东西。所以 workspaceDir 要选一个干净的目录。
- 内容会离开这台机器。 微信里收到的内容会进入本地会话日志,并发给模型服务商。
关于协议本身
iLink 目前挂在腾讯自家的 OpenClaw 产品线下。腾讯是否正式允许第三方调用这条通道,我无法从接口本身证实——这也是为什么扫码时客户端会弹授权页。请以那个页面上的文案为准。
它比个人号 hook 方案安全得多(不是逆向、不是外挂),但不保证零风险。第一轮建议别用你最看重的主号。
实现的协议细节
来源:腾讯自己发布的 npm 包 @tencent-weixin/openclaw-weixin (author: Tencent,MIT)。以下端点已在本机实测通过:
| 用途 | 请求 |
| 取二维码 | POST /ilink/bot/get_bot_qrcode?bot_type=3,body {local_token_list} |
| 轮询扫码状态 | GET /ilink/bot/get_qrcode_status?qrcode=<key>[&verify_code=<码>] |
| 收消息 | POST /ilink/bot/getupdates,body {get_updates_buf, base_info} |
| 发消息 | POST /ilink/bot/sendmessage,body {msg:{...}} |
公共请求头:iLink-App-Id: bot、iLink-App-ClientVersion(uint32)。
带鉴权的请求另有:AuthorizationType: ilink_bot_token、Authorization: Bearer <token>、X-WECHAT-UIN。
登录状态机完整处理了 8 种回包:wait / scaned / confirmed / expired(自动刷新,最多 3 次)/ scaned_but_redirect(切 IDC 主机)/ need_verifycode(回填手机上的数字)/ verify_code_blocked / binded_redirect(已绑定,不是失败)。
两个必须处理的坑:
1. message_id / msg_id / svr_id 是超出 Number.MAX_SAFE_INTEGER 的 uint64。 直接 JSON.parse 会精度丢失,所以收包前先给这几个字段加引号(见 host/ilink.js 的 parseWeixinJson)。
2. get_updates_buf 必须落盘。 它是长轮询的断点,丢了会漏消息。同时 errcode: -14 表示会话超时,需要重新扫码。
开发这个插件时踩到的坑(留给以后改它的人)
1. Cordis 拒绝访问没在 inject 里声明的服务。 web.js 一开始只声明了 'webServer',却用 Reflect.get(ctx, 'connection') 去读信任栅栏。
在真实 Harness 里七个路由全部返回 400,错误是 cannot get property "connection" without inject;而所有离线测试全绿——因为手写的 context double 对任何名字都照答不误。
修法:inject = ['webServer', 'connection'],并且用 ctx.connection 而不是 Reflect.get。
诊断它唯一有效的手段是在 handler 里把真实堆栈写进文件,ctx.logger 的输出看不到。
回归保护在 tests/cordis.integration.test.mjs,做法是源码级检查(列出 inject 声明、扫出所有 ctx.<service> 读取、确认没有未声明的),不会因 Cordis 版本差异而失效。
2. 可选服务用 ctx.get(name),必需服务用 ctx.<name> + inject。
ctx.get() 对未知服务返回 undefined(wechatBridge、wechatSessionBridge 就是这么读的);
裸属性访问在没声明时会失败。
3. 这个 checkout 是 tsx 直跑源码的 dev 环境:改插件文件不会热重载。 每次改完都要重启 Harness 才生效。磁盘上是新代码、进程里跑旧模块时,症状是「新加的路由 404、旧路由报错」,极易误判成代码写错。
4. 二维码不用微信那个页面,自己画。 微信返回的 qrcode_img_content 是一个客户端渲染的 SPA(外链 JS + #app 占位),它读 localStorage。所以:
- 放进 sandbox iframe(不带 allow-same-origin)→ 储能被禁 → 页面崩成白框;
- 带上 allow-same-origin → 同源内容可以自己摘掉沙箱 → 等于没有隔离,腾讯的 JS 就跑在你的源上。
正解是不嵌它:那个 URL 本身就是可扫内容,所以 host/qr.js 自己把它编码成 SVG。
零第三方脚本在本源执行,而且二维码全对比度渲染、不依赖外链。
写 QR 编码器时踩到的两个坑(都由测试抓到):
- 格式信息必须从最高位开始写到那串格子里。写反了符号看起来完全正常(定位符、数据区、纠错码全对),但任何扫描器都读不出来。
- 第二份格式信息是 7 格 + 8 格,且必须跳过暗模块 (size-8, 8)。
占用暗模块会把后 7 位格式信息写坏。
两个坑都是靠「和第三方编码器逐格对比」定位的;tests/qr.test.mjs 把这两个不变式固化成了断言。
5. 测试参数的陷阱(我自己踩的)。 写登录状态机测试时,我把轮询间隔设成 5 秒、超时设成 4 秒,于是循环第一轮就到期退出,表现为「提交验证码后不再轮询」。
看起来像产品 bug,实际是测试参数自相矛盾。
现在 makeTransport 用 20ms 间隔,并在注释里写明了原因。
6. 扫码成功后必须立刻启动接收循环。 index.js 只在启动时已有凭据的情况下调 transport.start()。
所以如果只在那边启动,刚扫完码的账号要再重启一次才开始收消息——也就是「扫完了但发消息没反应」。
修法:wechat-web 的登录流程在 result.connected 时也调一次 start()(幂等)。
回归保护在 tests/web.test.mjs 的「a successful scan starts the receive loop」,它同时断言登录失败时不会启动(避免无凭据空转)。
这条曾经是真实的缺口:我写这部分时只想到「配好凭据后重启」,没想到「扫码就是产生凭据的那一刻」。它是靠通读调用链发现的,不是靠测试。
7. 文案不要拿协议状态当显示条件——它会把人引向错误的排查方向。 二维码卡片的标题原本写成: qr.status === 'wait' ? T.waitingQr : T.connection
而 qr.status 在整个"等你扫"期间一直是 'wait'(那是网关的状态值)。
于是二维码已经画在屏幕上、正等着被扫的时候,标题却写着「正在获取二维码…」。
实际后果:操作者报告「一直显示正在获取二维码」,我据此去查网络、超时、服务可用性——全是错的方向。真相是功能完全正常,只是标签在骗人。
教训:显示文案要描述卡片在做什么(phase === 'requesting-qr'),不要复用协议字段。
回归保护在 tests/web.test.mjs 的「the panel copy describes the card」,它是源码级断言:禁止 qr.status === 'wait' ? T.waitingQr 这种写法再次出现。
8. 「加入白名单」按钮依赖被忽略的那条流水记录。 面板要从 entry.group 判断该把 ID 加进 allowFrom 还是 allowGroups,而它操作的对象是被忽略的记录(还没被授权的那个群)。
我最初只在「已接受」的记录上写了 group 标记,导致: 群里点「加入白名单」会把群 ID 加进私聊白名单,群永远无法从面板授权。
修法:record(entry, group) 统一兜住,所有记录都带标记。
回归保护在 tests/bridge.test.mjs 的 case 17,它回放面板的判断逻辑并断言归类正确。
9. conversation(会话归属)和 deliveryTarget(回复发往谁)是两个不同的东西。 群聊里出错时,我的失败提示原先发给 conversation(群 ID)——又踩回了「群内回复」这条未验证的路。
凡是发消息的地方都必须用 deliveryTarget。回归保护在 case 18。
11. 状态目录的位置取决于启动方式——别假设只有一个。 DSH_PROFILE_DIR 有值时凭据落在 $DSH_PROFILE_DIR/dsh-wechat;没有时落在 $DSH_HOME/dsh-wechat(即 ~/.dsh/dsh-wechat)。
真实的 Harness 用哪种方式启动,决定登录写在哪里。
我在这上面栽了跟头:你扫码成功、凭据已写入 ~/.dsh/dsh-wechat/account.json,而我连续几轮在 ~/.dsh/profiles/web/dsh-wechat/ 下查,得出「面板从未被点击」的 错误结论,还据此把目标标记为受阻。教训:排查状态类问题前,先确认代码实际写到哪个路径,而不是假设路径。
同时这也暴露了一个真实风险:如果启动方式变了,原路径的登录会凭空失效。
现在 index.js 会计算两个候选目录,restore() 在主目录为空时回退采用另一个,并把后续写入落到采用的那个文件上。
12. 测试绝不能读真实状态。 加上状态目录回退之后,Cordis 集成测试里的 index.js 读到了你真实的账号并开始轮询真实网关,表现为「the transport starts signed out」断言失败——测试在改动它本该只观察的状态。
修法:测试把 DSH_HOME 与 DSH_PROFILE_DIR 都指向临时目录。
教训:任何会读磁盘或联网的插件测试,都必须先把环境隔离,否则测试会污染运维现场。
13. 同一个 inject 缺陷我犯了两次——第二次才被真实堆栈抓住。 web.js 因为没声明 connection 让 7 个路由全 400;后来 bridge.js 又因为没声明 agents,让每一条真实微信消息都失败:
inbound: from=<peer>@im.wechat group= id=751... items=1
turn FAILED: Error: cannot get property "agents" without inject
at ensureAgent (host/bridge.js:265)
at answer (host/bridge.js:361)
at handle (host/bridge.js:567)
为什么两套离线测试都没抓到:手写的 context double 对任何服务名都照答不误,而 Cordis 在使用时刻才拒绝。症状是「消息收到了,但每一条都回『我这边出错了』」。
修法有三层,缺一不可:
- inject 声明全部必需服务(agents/sessionPersistence/permissionPresets),可选服务(sessionTitle/workspaceRegistry/agentDefaultModel)改用 ctx.get();
- tests/bridge.test.mjs 的 context double 改为遵循 inject 清单:访问未声明的属性直接抛 cannot get property "<name>" without inject,与 Cordis 行为一致;
- tests/cordis.integration.test.mjs 加源码级审查:剥离注释后枚举每个 Host 半边里的所有 ctx.<service> 读取,逐一核对是否在 inject 中,并断言桥接层必须声明 wechatBridge/agents/sessionPersistence/permissionPresets。
最讽刺的一层:我早就写下过「每个 ctx.<service> 都必须在 inject 里」这条教训,却没有机械地执行它。现在的源码审查就是为了让「靠记性」不再成为必要条件。
10. 浏览器侧的路由必须是「文档相对」,不能是「原点绝对」。 Harness 的 index 带 ,而且前端路由的既定约定就是 document-relative (见 .agents/notes/implemented/architecture/2026-09-14-web-document-relative-app-routes.md:「a document-relative fetch('api/session/create') still succeeds beneath it」)。
我原先写的是 const API = '/dsh-wechat'。在根路径挂载下它侥幸能用,但一旦部署挂到子路径(例如 /some/mount/),代理会剥掉前缀、而浏览器却按原点根去解析—— 面板的 6 个请求和二维码图片全部 404。
修法:const API = 'dsh-wechat'(不带前导斜杠),由浏览器按 解析。
验证:new URL('dsh-wechat/status', 'http://h/some/mount/') → /some/mount/dsh-wechat/status,而绝对形式 → /dsh-wechat/status(错)。
回归保护在 tests/web.test.mjs 的「the panel copy describes the card」段末两条断言。
测试
node tests/run-all.mjs # 八套全跑(含打真实腾讯网关)
node tests/run-all.mjs --offline # 跳过联网那套
可选:装上独立的解码器/编码器,多两层交叉验证
npm install --no-save jsqr qrcode
汇总行区分通过 / 跳过 / 失败:
passed 8 skipped 0 failed 0 (of 8)
三套 real-Cordis 套件(ilink.boot / cordis.integration / e2e.wiring)需要本机有 Harness 安装来借用 @deepseek-ai/cordis。找不到时它们以退出码 77 结束、显示为 SKIPPED,不会被算作通过——否则 CI 会在一片绿里隐藏掉最该跑的那几套(inject 那类缺陷正是它们抓的)。在 GitHub Actions 里没有 Harness,所以你会看到 passed 4 skipped 3。
| 套件 | 覆盖 |
| tests/qr.test.mjs | QR 编码器:用 jsqr 独立解码回原串、自带解析器复核纠错码、SVG 路径与模块矩阵逐格一致、超长载荷拒绝 |
| tests/ilink.login.test.mjs | 登录状态机 8 个分支全部覆盖:wait/scaned/confirmed/expired(含刷新上限)/scaned_but_redirect/need_verifycode/verify_code_blocked/binded_redirect,外加网关不可达与超时 |
| tests/bridge.test.mjs | 会话映射、白名单、群 @ 门禁、限速、回复提取、分片、超时、媒体落盘 |
| tests/web.test.mjs | 7 个路由、信任栅栏(401/403/503 失败即关闭)、二维码 SVG 渲染与解码回环、兄弟行缺失时的退化 |
| tests/cordis.integration.test.mjs | 用真实 Cordis 激活三个插件;源码级守 inject 声明 |
| tests/e2e.wiring.test.mjs | 用真实 Cordis 服务图把传输层与桥接层接起来跑完整收发(网关用替身) |
| tests/ilink.live.test.mjs | 打真实腾讯网关取二维码;uint64 精度、AES-128-ECB、消息展平 |
诊断日志:两个文件都写在状态目录下。
<状态目录>/login-probe.log # 登录:取码、每次轮询的状态转换、失败原因 <状态目录>/bridge-probe.log # 桥接:建会话的每一步,以及回合失败的完整堆栈
状态目录是这两个之一,取决于启动方式(见下方第 11 条):
$DSH_PROFILE_DIR/dsh-wechat/ # 例如 ~/.dsh/profiles/web/dsh-wechat/ $DSH_HOME/dsh-wechat/ # 例如 ~/.dsh/dsh-wechat/
两个都不含 token,且不需要浏览器鉴权就能读——面板报错时先看它们。
bridge-probe.log 是排查「微信里显示『我这边出错了』」的第一现场:它记录 ensureAgent 的每一步(工作目录、模型选项、建/恢复会话、权限预置是否生效、提示是否注入成功)以及失败时的完整堆栈。这条路径以前只把一句短消息发到微信、把堆栈丢进 Harness 日志,维护者从文件系统看不到——所以这个文件是专门补上的。
测试通过 DSH_WECHAT_PROBE_DIR 把这两个文件改到临时目录,所以跑测试不会污染你自己的诊断记录。
还有个预览脚本,它做的是「扫码之前能做的最后一步证明」:
npm install --no-save jsqr
node tests/qr.preview.mjs
它向真实网关要一张登录码 → 用面板路由所调的同一个渲染函数画成 SVG → 光栅化 → 交给独立解码器读回。任何环节(协议、编码器、路由载荷)出问题都会在这里暴露。
同时写出 qr-preview.png,可以直接用手机扫。
文件
index.js Host:iLink 传输服务(ctx.wechatBridge) host/ilink.js 协议实现:登录、长轮询、发送、媒体解密 host/bridge.js 会话桥:白名单、权限、会话映射、回复提取 host/markdown.js 微信文本降级与分片 host/qr.js 自带 QR 编码器(字节模式 / 纠错等级 M / 版本 1-10) host/web.js /dsh-wechat/ 路由 client.js 侧栏面板:二维码、状态、消息流水、白名单
locale/{en,zh}.json 插件元数据与文案
tests/ 七套测试预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
350Lxin/dsh-wechat-bridge
扫码后即可在微信里与本地 AI 助手对话
相关插件 · 平台集成
智能体工程课walkinglabs/learn-harness-engineering
一门项目制课程,教你搭建环境、管理状态、做验证、加控制机制,让 AI 编程智能体稳定干活。
★ 21,155已汉化DSH
71.7
视频翻译配音liuzhao1225/YouDub-webui
开源视频本地化工作台:导入视频,识别、翻译并逐句克隆原声,生成字幕和配音成片。
★ 5,792已汉化Cordis
71.4
失败经验库Ikalus1988/MisakaNet
📚 零依赖、由 git 支撑的微课程库,让 AI 智能体异步分享并检索经过验证的调试经验。| https://misakanet.org
★ 553已汉化DSH
71.2
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,170已汉化DeepSeek 客户端
71
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 2,041已汉化DSH
70.7
这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。