dsh-mail-digest
每轮回答结束后,把一条详略得当的中文摘要发到你的邮箱;而且你可以直接回邮件——用邮件答我的提问、或者收到摘要后续接对话。地址、授权码、收件人全在 DSH 的设置页里填,不用改配置文件、不用命令行。
- 摘要不是全文搬运,也不是固定字数的截断:长度由结论多少决定——结论少就一句话,结论多就多写几句。
- 双向:邮件不只是通知。我把问你的事情发到邮箱,你回信就能选;每封摘要也能直接回复,内容会当作新消息继续这段对话。
- 纯本地收发:自己实现的零依赖 SMTP + IMAP 客户端,不经过任何云服务,不装额外依赖。
- QQ / 163 / 126 预置:选一个邮箱类型,服务器和端口自动填好,只要填地址和授权码。
- 不会被随机邮件骗到:每封发出去的邮件带一次性随机 ID,只有「主题里是那个 ID + 发件人是你 + 确实在回复我们那封」才认。
- 待批准权限清单:需要你放行的操作(要写工作区之外的路径这类)会攒成一份清单,每封邮件都带上,你回来一次看完再批,不用守着屏幕。
待批准权限:为什么邮件里会多一块
agent 有时要做工作区之外的写操作(改 ~/.dsh 下的技能、配置、profile 等),
这类操作需要你批准。如果你正好不在电脑前,当场弹权限申请就会把任务卡住。
所以约定:你不在时不要申请权限,而是把待办攒进清单,放进每封摘要邮件的固定区块:
⚠ 待你批准的权限(2 项,我有工作区外的操作需要你放行)
1. 把三条实测坑整理进技能
需要:写 ~/.dsh/skills
原因:下次发布插件还会踩同样的坑
2. DSH 升级后重新校验插件层
需要:重启 DSH 后跑一次自检
你回来时批准任意一项即可(回复本邮件说明也可以)。
主题上也会标出来,手机上不用点开就能看到:[DSH] ✅ 会话名 ⚠2项待批准 [DSH-T:...]。
- 清单持久化存盘(
pending-permissions.json,与配置文件同目录),DSH 重启不丢
- 我通过
mail_digest 工具的 pendingPermissions 参数维护它;传空数组 [] 即清空
- 清单为空时,邮件里不会出现这个区块(不占版面)
安装
在普通 PowerShell 窗口里跑(不要走 DSH 沙箱:沙箱写不了 ~\.dsh):
# 一键脚本(装 / 卸载都行)
powershell -ExecutionPolicy Bypass -File .\安装邮件摘要.ps1
powershell -ExecutionPolicy Bypass -File .\安装邮件摘要.ps1 -Uninstall
或者手动两步:
# 1. 让插件能从自己的 node_modules 解析 DSH 官方包(junction,不占空间)
$dest = ".\node_modules\@deepseek-ai"
New-Item -ItemType Directory -Force $dest | Out-Null
$src = Get-ChildItem "$env:LOCALAPPDATA\npm-cache\_npx\*\node_modules\@deepseek-ai" -Directory |
Select-Object -First 1
Get-ChildItem $src.FullName -Directory |
ForEach-Object { cmd /c mklink /J "$dest\$($_.Name)" $_.FullName }
# 2. 装进 web profile(用 link: 软链,改代码即生效)
dsh plugin --profile web add "link:$($PWD.Path)"
装完重启 DSH(退出桌面应用时会连后端一起重启),然后打开设置页:
http://127.0.0.1:3080/dsh-mail-digest
配置
设置页五块,填完点「保存」,再点「发送测试邮件」;要用回信功能就再点「测试收信」:
| 填什么 | 说明 |
|---|
| 邮箱类型 | QQ / 163 / 126 / 自定义 SMTP |
| 发件邮箱地址 | 你的完整邮箱地址 |
| 授权码 / 密码 | 不是登录密码。网页版邮箱 → 设置 → 账户 → 开启 IMAP/SMTP 服务后生成的授权码 |
| 收件地址 | 留空 = 发给自己 |
| 回信通道 | 打开「启用回信通道」才能回信(需要邮箱已开启 IMAP 服务) |
点「发送测试邮件」验证发信,点「测试收信」验证 IMAP 登录(只验证登录,不读你任何邮件内容)。授权码只存在本机
%USERPROFILE%\.dsh\dsh-mail-digest\config.json,界面回显时永远是空的。
各邮箱的坑
| 邮箱 | 注意 |
|---|
| 163 / 126 | ① 登录后必须声明客户端身份(ID 命令),否则 SELECT 被拒:NO SELECT Unsafe Login。② 服务端主题搜索不可用:对 MIME 编码过的主题(=?gb18030?B?...?=),UID SEARCH SUBJECT "..." 一律返回空——所以插件改成取回候选邮件后在本地匹配主题。插件都已处理,你不需要做什么。 |
| QQ | 回复长主题时会按显示宽度插入空格,把 [DSH-Q:x] 改成 [D SH-Q:x]。插件的本地匹配会压缩空白后再比对,所以不受影响;你也不要去手改主题。 |
| 全部 | 授权码不是登录密码,要在网页版邮箱里单独生成,且必须先开启 IMAP/SMTP 服务。 |
回信落在哪个文件夹
插件每次收信会在一条连接内枚举文件夹,优先搜 INBOX 与「已发送 / Sent」这类,
以兜住「你自己回复自己的邮件被归到已发送」的情况(163 的「已发送」就是这种)。
为什么不用服务端主题搜索
踩过的坑(实测于 163):UID SEARCH ALL 能正常列出全部 7 封邮件,而
UID SEARCH SUBJECT "DSH-"、"DSH"、"[DSH-" 各种形态一律返回空。
一旦依赖它,你的回信连候选集都进不来,后面客户端逻辑再对也没用
(等于在下游修、上游已筛空)。
所以收信流程是:UID SEARCH ALL 拿 UID 列表 → UID FETCH (FLAGS BODY.PEEK[])
取回最近的若干封 → 本地比对主题(压缩空白后子串匹配)→ 本地按 \Seen
判断未读。代价是每轮多取几十封的头部,换来的是任何客户端改坏标记都能认出来。
回信怎么写
内容写在正文,主题保持原样别动(主题里的 [DSH-?:xxxx] 是线程标记,插件靠它找到你的回信)。
| 情况 | 正文这样写 |
|---|
| 多个问题选选项 | 1. A 换行 2. B;写一行 1. A 2. B 也行 |
| 只有一个问题 | 直接写字母,例如 A |
| 对某个选项不满意 | 直接写你要的内容,例如 1. 用私有仓库 |
| 收到摘要后想继续聊 | 直接写你要说的话 |
一次可选的字母是 A/B/C…(按邮件里列出的顺序)。
邮件长什么样
我已经给 DSH 装好邮件摘要插件了:它订阅每轮结束事件,把这一轮回答压成一条摘要发到你的邮箱。
────────────
会话:给 dsh 加邮件插件
工作区:dsh
回合:第 1 轮 · 用时 3 分 12 秒
时间:2026-09-23 09:12:04
────────────
直接回复本邮件即可继续对话(保持主题不变)。
主题形如 [DSH] ✅ 会话名 [DSH-T:k3f9a2x8m1qp],出错是 ⚠️,被中止是 ⏹️。
末尾那个标记每封都不一样,是你回信时的凭据。
提问邮件形如 [DSH] ❓ 会话名 [DSH-Q:abc123def456],正文里列出问题和选项。
为什么不会被随机邮件骗到
收信时过三道闸,任何一道不过就丢弃:
| 闸 | 判据 |
|---|
| ① 主题 | 必须有写全的 [DSH-T:<token>] / [DSH-Q:<token>],而且 token 必须是我们真的发出去过的那一串 |
| ② 发件人 | 必须是你配置的收件地址 |
| ③ 出处 | 回信的 References/In-Reply-To 要能对上我们那封的 Message-ID(客户端剥掉这两个头时靠 ①② 放行) |
token 是 12 位密码学随机串、每封都换,所以外人既猜不到也撞不上。
摘要怎么来的
三级来源,前一级不可用才退到下一级:
- 我自己写 —— 我在回答结尾调用
mail_digest 工具提交一条摘要(最准,长度我自己掂量);
- 模型压缩 —— 没写就用
ctx.llm.stream 把这一轮回答压成一条;
- 确定性兜底 —— 模型路由不可用时,从回答里挑出结论句和收尾句拼一条。
三级的字数纪律都是「详略控制」:提示词给的是参考区间,产出不做字数校验、不做截断。
行为开关
| 开关 | 默认 | 说明 |
|---|
| 启用邮件摘要 | 开 | 关掉之后一轮都不发 |
| 子代理回合也发 | 关 | 一次任务可能因此收到几十封,默认不发子代理 |
| 摘要来源 | 自动 | 自动 / 总是用模型压缩 / 不发信只记日志 |
| 一分钟最多发几封 | 6 | 防死循环刷屏 |
| 同一会话最小间隔 | 0 | 秒 |
已知限制
- 发信失败不会重试:日志里会记原因(比如
535 授权码不对),但不会补发。
- 不加密的服务器默认拒绝发信:没有 STARTTLS 就拒绝把授权码发出去。本机中继要发,得在设置页勾「允许未加密发送」。
- 摘要由模型生成时会有 Token 开销:每轮多一次很小的模型调用;想省掉就让回答结尾调用
mail_digest(走第一级,不再额外调模型)。
- 只在
turn/end 触发:一轮里我调了工具、分了多步,最后只发一封。
- 收信默认 15 秒一轮:回信不会秒到,最多等十几秒;提问邮件挂了 30 分钟没回就交回界面。
- 只读未读回信(默认):避免同一封被重复采纳;取消这个开关会重复处理同一封邮件。
- 回信要保住主题里的标记:那个
[DSH-?:...] 被删掉或改写就找不到了。
- 界面与邮件同时作答:先到的生效,另一边的对话框不会自动关闭,需要你手动关掉。
- 收件人不在白名单里的回信一律丢弃:所以别用另一个邮箱回信。
卸载
dsh plugin --profile web remove dsh-mail-digest
配置文件不会自动删,要清掉就删 %USERPROFILE%\.dsh\dsh-mail-digest\。
自检
node smoke.mjs # 入口契约 / 工具注册 / 摘要函数 / 配置 / 设置页 / 深色配色 (52 项)
node test-reply.mjs # 回复解析 / 1. A 格式 / 防误判 token / 发件人白名单 (74 项)
node test-bridge.mjs # 收信判定整合:三道闸 / 两种通道 / 文件夹挑选 (41 项)
node test-mime.mjs # MIME:中文编码 / multipart / HTML / 引用剥离 (31 项)
node test-smtp.mjs # 假 SMTP 服务器对发:认证 / 中文主题 / 正文还原 / 安全底线 (16 项)
排查真机问题时(不依赖插件、不标记已读):
node ../tools/imap-diag.mjs # 逐步打印 IMAP 对话,看卡在哪一步
node ../tools/imap-fetch.mjs # 抓真实回信并逐封展示解析结果
License
MIT