用专属邮箱收发指令并自动续接上下文回复
30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/11 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/secondlgn/dsh-mail.git状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 23 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件
查看扫描证据(共 9 处,展示前 9 条)
- 写入本地文件src/index.js:481
await mkdir(targetDir, { recursive: true }); - 写入本地文件src/index.js:498
await writeFile(path, data); - 读取本地文件src/outgoing.js:250
const data = attachment.data ?? await readFile(attachment.path); - 读取本地文件src/store.js:37
const text = await readFile(path, 'utf8'); - 写入本地文件src/store.js:48
await mkdir(dirname(path), { recursive: true }); - 写入本地文件src/store.js:53
await writeFile(handle, text, 'utf8'); - 写入本地文件src/store.js:59
await rename(temporary, path); - 删除本地文件src/store.js:61
await unlink(temporary).catch(() => {}); - 写入本地文件src/store.js:108
await mkdir(this.#dir, { recursive: true });
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 专属邮箱收发指令
- 邮件即会话入口
- 回复自动续接上下文
DSH 邮件通道插件:一台机器一个专属邮箱,邮件即会话入口,回复即续接上下文(零运行时依赖)
README
由 DeepSeek 最新模型翻译生成dsh-mail
给一台机器上的 DSH 配一个专属邮箱作为会话入口:邮件即会话,回复即续接上下文,答复自动回信,长任务可被打断。
- 纯出站:IMAP 轮询收信 + SMTP 发信。机器不需要公网 IP,不开放任何入站端口,没有回调地址。
- 准入靠白名单:只认 From 精确地址,其余一律丢弃,连回执都不发。
- 回复判定双保险:In-Reply-To/References 优先,主题里的 [dsh-xxxx] 标签兜底。
- 零运行时依赖:IMAP/SMTP 客户端与 MIME 解析都是本包自己实现的,只用 Node 内置模块。
设计取舍与逐条决策记录见 docs/design-spec.md。
展开完整文档(README 剩余 1 节)
dsh-mail
给一台机器上的 DSH 配一个专属邮箱作为会话入口:邮件即会话,回复即续接上下文,答复自动回信,长任务可被打断。
- 纯出站:IMAP 轮询收信 + SMTP 发信。机器不需要公网 IP,不开放任何入站端口,没有回调地址。
- 准入靠白名单:只认 From 精确地址,其余一律丢弃,连回执都不发。
- 回复判定双保险:In-Reply-To/References 优先,主题里的 [dsh-xxxx] 标签兜底。
- 零运行时依赖:IMAP/SMTP 客户端与 MIME 解析都是本包自己实现的,只用 Node 内置模块。
设计取舍与逐条决策记录见 docs/design-spec.md。
仓库结构
src/ 插件本体(Host 插件,ESM) index.js 插件入口:Config、withDefaults、apply imap.js IMAP 客户端(literal、IDLE、APPEND、互斥) smtp.js SMTP 客户端(多行应答、AUTH、点填充) mime.js MIME 解析:RFC 2047/2231、引文剥离、HTML 转文本 inbound.js 入站判定:白名单、循环抑制、线程归属、提示渲染 outgoing.js 出站组装:主题标签、线程信头、MIME 与附件 session.js 会话控制器适配(投递、打断、取结果) store.js 原子状态库(messages / threads / cursor) test/ 全部离线测试(node:test) tools/ 对真实邮箱的只读自检脚本 docs/design-spec.md 设计规格 v1 cordis.patch.yml 插件自带的 bundle 补丁(只含中性默认值)
状态
已实现并在离线仿真中验证:白名单准入、循环邮件抑制、新会话/续接判定、受理回执、结果回信、同线程回复打断当前轮次、附件落工作区、崩溃重放、Sent 文件夹回写。
未实现(按约定属于后续阶段):基于 plan todo 的阶段汇报、权限清单与超范围询问。
安装
插件是标准的 DSH Host 插件包(ESM,导出 name / inject / Config / apply),同时是一个 bundle:它自带的 cordis.patch.yml 负责往 profile 里插入插件行。
从 GitHub 安装
git clone https://github.com/secondlgn/dsh-mail
然后在你的 profile 目录($DSH_HOME/profiles/<name>/)里做两处改动:
// <profile>/package.json
{
"dependencies": {
// 二选一:指向本地克隆,或用 GitHub 源 "dsh-mail": "file:C:/path/to/dsh-mail", // "dsh-mail": "github:secondlgn/dsh-mail" },
"dsh": {
"profile": {
"bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-mail"]
}
}
}
<profile>/cordis.patch.yml
注意:这里【不能】写 name: 字段。带 name 的条目是「插入新行」的写法,而新行必须由 bundle 提供;在 profile 补丁里带 name 会被当作未匹配条目静默忽略,插件永远不会加载。
这里只能按 id 覆盖配置。
- id: dsh-mail
config:
enabled: true
imap:
host: imap.example.com
port: 993
user: agent@example.com
passwordEnv: DSH_MAIL_PASSWORD
smtp:
host: smtp.example.com
port: 465
user: agent@example.com
allowFrom:
- you@example.com
workspaceDir: C:/work
bundles 里必须加上 dsh-mail,它自带的 cordis.patch.yml 才会被插入;那里面只有中性默认值(enabled: false),真正的账号与白名单写在上面的 profile 补丁里。补丁会整体替换该行的 config,所以上面这份配置要写全,不能只写增量。
密码不要写进配置:用 passwordEnv(默认 DSH_MAIL_PASSWORD)指向环境变量。
allowFrom 为空时任何人都无法驱动这台机器——这是安全的默认值,不是 bug。
关于 workspaceDir
workspaceDir 决定邮箱会话的工作目录,每台机器不同,不要照抄。留空则用当前工作目录。
它必须是该机器上真实存在的绝对路径。
在界面上配置(不用改文件)
插件带一个浏览器半侧,在 DSH 的 侧栏 → 插件 页里注册一张可编辑的设置卡片(plugins.item 插槽,标题「邮件通道」)。在那张卡片里可以直接改:
- 邮箱:IMAP 服务器/端口/账号/收件夹、密码环境变量名、SMTP 服务器/端口、结果是否存入「已发送」
- 准入:白名单发件人、视为本机的额外地址
- 行为:工作目录、同线程打断、收到即回执、轮次结束回信、整体启用开关
改动写入当前 profile 的 cordis.patch.yml,并通过 volatile 引用即时生效——改完不需要重启。
实现要点:这些字段在 schema 上用 .volatile() 声明。DSH 的设置服务只能编辑 volatile 字段,且 volatility 不能嵌套,所以标的是整棵子树(imap、smtp、attachments),而不是叶子字段; volatile 字段在运行时是引用(Volatile<T>,需 .get() 读取),插件因此改为每次读取都解引用,才能拿到改动后的值。
stateDir 与 dshHome 仍是普通字段、只能改文件:它们决定状态库的位置,运行时改动会让通道与自己的历史脱钩。
密码目前是「环境变量名」输入框,不是直接填密码的密钥框。 界面上填的是 passwordEnv (默认 DSH_MAIL_PASSWORD),真实密码仍放在环境变量里。原因是密码写进配置文件就是明文落盘; DSH 提供了 SettingsSecretField 与凭据库(ctx.credentials)可以做到"只写不回显、可查存在性",但需要宿主半侧提供一条写入通道,属于下一步。在那之前,配置文件里不会出现明文密码。
验证安装
重启 DSH 后,插件行应以 include:dsh-mail 出现且状态为 active。若没出现,按顺序检查:
1. bundles 里是否真的加了 dsh-mail;
2. profile 补丁里是否误写了 name:(见上面的警告);
3. package.json 是否为合法 JSON。
网易邮箱(163/126/yeah)必读
网易对第三方客户端有额外要求,插件已经内置处理,但值得知道原因:
- 登录后必须先发 ID 命令(RFC 2971)声明客户端身份,否则 SELECT INBOX 会直接返回
NO [Unsafe Login]。插件在 CAPABILITY 里看到 ID 就自动发送。
- Sent 文件夹名为 modified UTF-7(163 上是 &XfJT0ZAB-,即「已发送」)。插件靠 RFC 6154 的 \Sent 属性识别它,不依赖名字,所以不需要手工配置 sentMailbox。
- 首次接入建议先在邮箱设置里开启 IMAP/SMTP 服务,并使用授权码而不是登录密码。
参考配置(同样是不带 name: 的 id 覆盖):
- id: dsh-mail
config:
enabled: true
imap:
host: imap.163.com
port: 993
user: you@163.com
passwordEnv: DSH_MAIL_PASSWORD
smtp:
host: smtp.163.com
port: 465
user: you@163.com
passwordEnv: DSH_MAIL_PASSWORD
allowFrom: [you@163.com]
上线前自检
仓库 tools/ 里有两个只读检查脚本,在仓库根目录运行(都需要 DSH_MAIL_PASSWORD 可见):
node tools/mail-probe.mjs you@163.com # 登录 + 能力 + Sent 文件夹识别
node tools/mail-rehearsal.mjs you@163.com # 抓最新几封真实邮件跑一遍判定与解析
两者都不发信、不写状态、不标记已读,可以在启用插件前对着真实邮箱放心跑。
配置项
| 键 | 默认 | 说明 |
| enabled | true | 关掉后插件不建立任何连接 |
| imap.host / imap.user | 必填 | IMAP 服务器与账号 |
| imap.port / imap.secure | 993 / true | 隐式 TLS |
| imap.password / imap.passwordEnv | '' / DSH_MAIL_PASSWORD | 密码内联或环境变量名 |
| imap.mailbox | INBOX | 轮询的文件夹 |
| imap.sentMailbox | '' | 留空则自动识别 \Sent,再按常见名称兜底 |
| imap.pollIntervalMs | 20000 | 轮询间隔(有效下限 2000),IDLE 不可用时按它轮询 |
| imap.idle | true | 首选 IDLE,不可用时自动退回轮询 |
| imap.maxIdleMs | 240000 | 单次 IDLE 的最长等待上限 |
| imap.rejectUnauthorized | true | 关掉即不校验 IMAP 服务器证书(仅用于自签调试) |
| smtp.host | 同 imap.host | 留空即复用 IMAP 主机 |
| smtp.port / smtp.secure | 465 / true | 提交端口 |
| smtp.password / smtp.passwordEnv | '' / DSH_MAIL_PASSWORD | 留空则复用 IMAP 的密码解析结果 |
| smtp.saveToSent | true | 发信后回写 Sent 文件夹 |
| smtp.rejectUnauthorized | true | 关掉即不校验 SMTP 服务器证书(仅用于自签调试) |
| allowFrom | [] | 允许驱动 agent 的发件人地址(精确匹配) |
| selfAddresses | [] | 额外视为「自己」的地址,用于自环检测 |
| fromName | DSH Agent | 发信显示名 |
| tagPrefix | [dsh- | 主题标签前缀 |
| workspaceDir | process.cwd() | 新建会话的工作目录 |
| agentPreset | '' | 邮箱会话使用的 agent 预设,留空用 profile 默认 |
| stateDir | <DSH_HOME>/mail | 插件状态目录 |
| dshHome | $DSH_HOME | 覆盖状态目录推导所用的 DSH 主目录 |
| sendReceipt | true | 受理后立即回执 |
| sendResult | true | 轮次结束后回结果 |
| includeModePreamble | true | 在提示里注入邮件排版要求 |
| interruptSameThread | true | 同线程回复打断正在跑的轮次 |
| maxMessageBytes | 32 MiB | 超过则丢弃 |
| maxResultChars | 60000 | 回信正文上限,超出截断并标注 |
| attachments.enabled | true | 是否落盘附件 |
| attachments.maxBytes / maxCount | 10 MiB / 10 | 单附件与数量上限 |
| attachments.dir | <workspace>/.dsh-mail/attachments | 附件根目录 |
| recovery | rerun | 崩溃恢复:rerun 重跑未完成的信,其他值标记失败 |
工作方式
会话判定
1. From 在白名单? 否 → 丢弃(不记录、不回信)
2. 自动回复 / 群发 / 邮件列表? 是 → 丢弃
3. Message-ID 已处理完? 是 → 丢弃
4. In-Reply-To/References → 我方 Message-ID → 命中线程标签
主题里的 [dsh-xxxx] → 命中线程标签
都没有 → 新建会话(旧线程若断了,就是新会话)
打断语义(关键行为)
只有回复属于「正在运行的那个会话」时才打断。 别的会话的来信只排队,不打扰当前轮次。
打断的实现是「先投递、再取消」:把新内容以 queue 投进会话,然后 cancel 掉当前轮次,让队列里的新提示立刻接管。steer 可用时优先用它。
投递保证
至少一次。进程在「已投递」与「记为完成」之间崩溃时会重放——重复回答比漏信便宜。为控制代价,重放前先读 state.json 里记录的原会话与收件人,不会因为线程映射变化而走错会话;同一线程的同一段文本也不会重复发送。
回执与回信
| 邮件 | 时机 | 内容 |
| 受理回执 | 通过准入后立即 | 已收到(回执 xxxxxx),会话 [dsh-xxxx],开始处理 |
| 结果回信 | 轮次结束 | 该轮的助手文本;未完成/被取消的轮次不发 |
回执的意义是让你在第一封信里就拿到会话号:之后就算客户端丢了 References,只要主题里的 [dsh-xxxx] 还在,就能续接。
turn/end 原因为 interrupted 或 forked 时不发结果,避免把被打断的半成品当成答复。
邮件模式提示
入站内容会包一层元信息送进模型,说明这是邮件而非对话框、只输出纯文本、控制行宽、不要提实现细节、不要复述提示本身,并附上发件人、主题、会话号、回执号。
状态文件
<stateDir>/
messages.json Message-ID → { 发件人, 主题, 正文, 会话, 线程标签, 回执号, 阶段, 时间 } threads.json 线程标签 ↔ 会话,以及我方发出的 Message-ID cursor.json 轮询水位(UID + UIDVALIDITY)
写入是原子的(临时文件 + fsync + rename),并按文件串行化。UIDVALIDITY 变化会重置 UID 水位,但消息身份始终由 Message-ID 决定,所以重置不会重放已完成的信。
开发
npm test # node --test,自动发现 test/*.test.mjs
npm run test:single-process # 沙箱下的退路:不用按文件子进程隔离
零依赖,npm install 不是必需的。用 DSH 自带的 Node 跑也一样(自带 TLS 与 node:test):
"C:/Users/<you>/.dsh/dsh-runtimes/dsh-primary-runtime/dependencies/node/bin/node.exe" --test
默认的按文件子进程隔离在某些文件沙箱下会以 EPERM 失败,这时改用 npm run test:single-process (把全部用例放进一个进程)。
测试全部离线:真实 TLS 连接(自签证书在进程内生成)对接脚本化的 IMAP/SMTP 服务器,加一个 Host 会话控制器桩。DSH_MAIL_TEST_TRACE=1 会打印插件自身的日志。
| 文件 | 覆盖 |
| test/mime.test.mjs | RFC 2047、GBK/UTF-8、引文剥离、HTML 转文本、附件识别 |
| test/inbound.test.mjs | 白名单、循环抑制、新会话/续接、重复与重放判定 |
| test/store.test.mjs | 原子写入、并发修改、水位语义、保留策略 |
| test/imap.test.mjs | 协议解析、literal(含多次写入与多 literal)、互斥、IDLE、APPEND |
| test/smtp.test.mjs | 多行应答、AUTH 三机制、DATA 点填充、MIME 组装 |
| test/channel.test.mjs | 端到端:准入 → 建会话 → 回执 → 结果、打断、附件、重放、Sent 回写 |
| test/tls-fixture.test.mjs | 测试用的自签证书夹具本身(本机名可用、他机名被拒) |
已知限制
- IDLE 单次等待有上限:最长等 imap.maxIdleMs(默认 240 s)再重新进入;轮询间隔的有效下限是 2000 ms。
- IDLE 期间无法发信:APPEND 需要空闲连接,若正卡在 IDLE 会等一个 IDLE 周期。当前实现里发信前会复用空闲连接,最坏情况下 Sent 回写延迟一个周期。
- 配置 schema 无法离线校验:DSH 用的 schemastery 是定制版,公共包解析不了它的 schema。因此 Config 的导入是容错的(Peer 不可解析时降级为空 schema),默认值另由 withDefaults 兜底,并由 test/channel.test.mjs 覆盖。
- 引文剥离是启发式:识别 > 引用、On ... wrote:、-----Original Message-----、Outlook/中文头部块与签名分隔符。剥离后正文为空时会退回原文并标注。
- 附件不自动解压:zip 原样落盘。
- 阶段汇报未实现:原本约定的「plan 的 todo 即阶段」需要订阅 todo_write 结果,尚未做。
- 权限询问未实现:超范围权限的邮件询问与超时默认拒绝尚未做;当前依赖 DSH 自身的审批通道。
安全边界
- 全部连接是出站的,不监听任何端口,没有回调地址。
- 准入只认 From 精确地址;allowFrom 为空时谁都不能驱动这台机器。
- 密码只从环境变量读(passwordEnv),配置与仓库里都不落明文。
- 状态目录(<DSH_HOME>/mail)保存的是已剥离引文的正文、Message-ID、线程映射和水位,用来排查「这封信为什么没回」;原始 MIME 报文不落盘,附件按配置落到工作区而不是状态目录。
- 崩溃恢复是「至少一次」,代价是可能重跑一封信;同一线程的同一段文本不会被重复发送。
许可
MIT,见 LICENSE。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
secondlgn/dsh-mail
用专属邮箱收发指令并自动续接上下文回复
相关插件 · 平台集成
智能体工程课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
★ 550已汉化DSH
71.2
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,110已汉化DeepSeek 客户端
70.9
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 2,031已汉化DSH
70.7
这是你的仓库?认领条目