dsh-relay 测试与贡献指南

August 25, 2026 · View on GitHub

本插件把「测试用例」当作一等公民:任何改动/新通道,跑完整个测试套件并贴出通过输出,即可作为提交凭证。测试分三层:

文件覆盖依赖
单元测试src/index.test.js(69 项)路由(严格+语义)、提问应答解析、编号注册表、状态存储、编号每日重置、脱敏、邮件认证解析、iMessage attributedBody 提取与已读 SQL、微信 iLink 解析纯 Node,无外部服务
dry-run 集成src/dryrun.test.js(27 项)审批监听(批准/拒绝/超时/未启用)、提问监听(应答/超时/放行)、/relay 命令、重启恢复、注入防污染、会话范围语义模拟 ctx,零真实发送
apply 冒烟src/apply.smoke.js插件装载、监听器以 prepend+global 注册、通道未配置时不启动模拟 ctx

运行

npm test            # 统一运行器(等价 node test/run-all.mjs),任一失败即退出码 1
node src/index.test.js      # 单跑单元
node src/dryrun.test.js     # 单跑 dry-run

贡献流程(提交到仓库时):

  1. npm test → 输出以 全部测试套件通过 ✅ 结尾;
  2. 把完整输出贴进 PR/提交说明;
  3. 若新增了通道/功能,附上对应的手动验收记录(见下),或至少给出 dry-run 测试;
  4. 通过后即可合并,完善整体设计。

手动验收清单(真实通道 E2E,需一台已授权的设备)

已在本机 iMessage 上完成;其他通道(email/wechat/telegram…)按同一协议验收。 建议只在测试会话上做,避免污染正在使用的对话。

#场景操作预期
M1推送链路跑任意 agent 任务至一轮结束(或插件手动推送)通道收到 [✅ 完成] … 或诉求推送
M2收信+回执通道发 状态收到状态总览(总开关/会话策略/通道/待办)
M3语义待办通道发 还有哪些没处理收到待办列表(无则"(无)")
M4提问全流程网页 ask_user_question 触发通道收到 ❓ #N 需要你的意见,回复 #N 1 后网页拿到答案
M5审批-批准新会话触发审批通道收到 🔐 #N,回复「把N号批了」→ 任务放行
M6审批-拒绝再触发一次,回复 #N 拒绝任务被拒
M7编号应答并发两条诉求编号不冲突,按编号各自应答
M8开关通道发 关闭 / 开启状态切换,关闭后不再推送
M9会话范围-严格全部关闭 / 全部开启 / /enable <id>会话策略切换
M10会话范围-模糊打开cb200 / 开启会话cb200 / 把cb200对话打开 / 关闭cb200对话 / 关掉最近会话 / 启用最近 / 把那个对话关掉均作用于指定会话(短前缀/最近活跃),不误动总开关
M11绑定-模糊绑定当前对话 / 绑到最近会话 / 解绑 / 取消绑定绑定/解绑生效;解绑后裸文本不注入
M12会话列表-模糊有哪些会话 / 会话列表 / 列出当前所有对话 / 所有对话返回全量会话列表(id/标题/开关/活跃)
M13外来编号静默同通道其他 bot 发 #N …(非本插件编号且无 pending)本插件不回复
M14文本注入绑定会话后发裸文本注入到绑定会话(带安全护栏前缀),不跟随"最近活跃"
M15电脑端处理完成通知诉求超时后转网页,在电脑上批准/拒绝/回答通道收到 #N ✅/❌ 已在电脑端处理(回答摘要),待办从队列移除
M16轮次推送开关观察多轮任务默认推送轮次结束(turnEndPush: false);开启后片段 ≤120 字

Email 通道验收提示

Email 走与 iMessage 相同的 M1–M16 清单,额外注意:

  1. 配置 imap/smtp + passRef~/.dsh/.credentials.yaml 里加 DSH_RELAY_EMAIL_PASS: <授权码>);
  2. 第一封测试:发 状态 → 应收到状态总览;发件人必须是白名单地址;
  3. 回信匹配:直接"回复"插件推送的邮件(In-Reply-To 命中自动补编号),或正文写 #N 批准
  4. 认证失败(SPF/DKIM/DMARC 任一 fail)的邮件会被拒绝并告警。

通道成熟度与实现来源

「尽量沿用成熟实现」的落实:每个通道的实现来源都明确标注,新增通道优先移植下列成熟仓库。

状态说明✅ 端到端验收 = 作者已在真实通道上跑通完整闭环(收发回执 + 诉求应答 + 回信匹配);预留接口 = 未实现,欢迎社区按 src/channels/types.js 契约接入。

通道实现成熟来源状态
iMessageAppleScript 发送 + chat.db 轮询(attributedBody 提取)自研(macOS 26 移除了消息类脚本,chat.db 是唯一可靠收信途径);模式沿用 im-bridge 的桥接/去重/游标设计端到端验收(收发回执 + 诉求 #N 应答闭环 + 已读标注本地生效)
WeChatiLink 扫码登录 + 长轮询dsh-im-bridge(MIT)src/ilink.ts 逐行移植端到端验收(扫码绑定 → 收信 → 路由 → 回执,全链路走通)
Emailimapflow(IMAP)+ nodemailer(SMTP)+ mailparser(MIME)业界标准 Node 库(多数 DSH 邮件插件的同一栈);SPF/DKIM/DMARC 校验、In-Reply-To 编号回填端到端验收(IMAP 轮询收信 + SMTP 发送 + 认证校验 + 回信闭环:Gmail 回复 → In-Reply-To 补编号 → 插件收信应答,全链路走通)
TelegramBot API 长轮询(待接入)LoserFox/telegram(源自 Hermes 适配器:getUpdates/sendMessage/HTML 分段/用户白名单)🔧 预留接口,待社区实现
飞书/Lark卡片交互(待接入)imetn/dsh-lark-bridge(WebSocket 长连接、卡片审批/提问)🔧 预留接口,待社区实现
钉钉待接入参照 im-bridge 通道层契约🔧 预留接口,待社区实现

邀请社区贡献:Telegram / 飞书 / 钉钉 尚未实现,按 src/channels/types.js 契约实现 configured/start/stop/send/isTrusted/status + 接入 pushInbound 即可;也欢迎为各通道在更多平台上的 E2E 验收补充验证记录(见下方贡献流程)。

通道契约见 src/channels/types.jsconfigured/start/stop/send/isTrusted/status + pushInbound)。

历史修复(回归测试锚点)

这些 bug 都有对应单测,改动时不可回退:

  • 双重复去重:去重只在 pushInbound 一处(轮询不得预标记),否则入站永不派发(dryrun 覆盖);
  • 同账号消息:iMessage 手机/电脑同 Apple ID 时消息记为 is_from_me=1、正文在 attributedBody——收信必须两者都收、用自标记 D5HR42 排除自身推送;
  • ASCII 命令提取:attributedBody 分段提取需命令词兜底(/sessions 等);
  • 注入目标:只进显式绑定会话(/bind 或配置 boundSessions),禁止跟随"最近活跃"(避免污染用户正在用的对话);
  • 外来编号静默:非本插件编号且无 pending 时静默,不插嘴同通道其他 bot;
  • 电脑端处理完成通知:诉求超时移交电脑端(网页 UI)后,待办即从队列移除,并向通道推送 #N ✅/❌ 已在电脑端处理,避免手机悬挂未答复诉求(dryrun 覆盖);
  • 模糊语意-会话目标打开cb200/开启会话cb200/把X对话打开/关掉最近会话/绑定当前对话/解绑/有哪些会话 必须解析到会话级操作,通道名(微信/imessage)不得被吞、不得误动总开关;
  • 轮次推送默认关闭turnEndPush !== true 不推送,开启时片段 ≤120 字(避免把整段回复推到通道);
  • 编号每日重置stale 诉求重启后不可应答外发脱敏邮件发件人认证

覆盖矩阵

正向推演的测试设计表 + 质量检测基线(覆盖率快照、盲区标注、回归实证记录)见 test/DRYRUN.md。复测:npm run coverage