dsh-email 0.17.0 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/docs/DRAFTS.en.md +8 -0
- package/docs/DRAFTS.md +8 -0
- package/docs/USAGE.md +2 -0
- package/docs/VALIDATION.md +2 -0
- package/docs/validation/0.17.1.md +29 -0
- package/lib/client.js +7 -0
- package/lib/config.js +3 -1
- package/lib/mail-client.js +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
[历史英文记录](docs/CHANGELOG.en.md)
|
|
6
6
|
|
|
7
|
+
## 0.17.2(2026-10-20)
|
|
8
|
+
|
|
9
|
+
- 移动邮件前核对 IMAP 的 MOVE / UIDPLUS 能力;两者均不支持时明确拒绝,不再触发可能清除其他已标记删除邮件的全局 EXPUNGE(#23)。
|
|
10
|
+
|
|
11
|
+
## 0.17.1(2026-10-18)
|
|
12
|
+
|
|
13
|
+
- 完成 [#19](https://github.com/STARDUSTLC666/dsh-email/issues/19) 的发信策略说明:全局确认开关关闭时,账号页显示中英文影响范围,明确所有账号的模型发信、回复和转发都不再逐次确认;只读设置不能编辑该开关。
|
|
14
|
+
- 区分按账号的可信名单与全局关闭确认,说明禁止名单、宿主权限和草稿手动确认继续生效;增加可信名单和全局关闭确认均保留宿主拒绝结果的回归。
|
|
15
|
+
- 修复新版宿主中切换全局设置后,空服务商字段使手填服务器账号报 `provider "" 未知` 的问题;单账号、多账号与 YAML 账号均保留原服务器和凭据。
|
|
16
|
+
|
|
7
17
|
## 0.17.0(2026-10-17)
|
|
8
18
|
|
|
9
19
|
- 设置面板顶部新增「首次使用」分步引导:一次只讲一步,支持上一步/下一步/跳过,看完记在本机不再出现。
|
package/docs/DRAFTS.en.md
CHANGED
|
@@ -43,6 +43,14 @@ trustedRecipientsYaml: |
|
|
|
43
43
|
|
|
44
44
|
Renaming an account does not transfer old rules. Rule changes do not reconnect the mailbox.
|
|
45
45
|
|
|
46
|
+
## Global send confirmation
|
|
47
|
+
|
|
48
|
+
At the top of Settings → Email → Accounts, use “Confirm model-initiated sends.” It is on by default. Turning it off saves automatically and displays the scope of the change.
|
|
49
|
+
|
|
50
|
+
This is the global `sendApproval` setting and affects every account. With it off, model calls to `email_send` and `email_reply` (including reply-all and forward) skip the plugin's individual confirmation, including recipients outside the trusted list. Account trust rules only skip confirmation for `email_send` when every To/Cc recipient matches; they do not mean unrestricted sending.
|
|
51
|
+
|
|
52
|
+
Blocked addresses and domains still stop delivery before an SMTP connection. Host permissions still apply, and the Drafts tab still requires human review and explicit send confirmation. Keep global confirmation on and configure account trust rules to allow only selected recipients.
|
|
53
|
+
|
|
46
54
|
## Storage and limits
|
|
47
55
|
|
|
48
56
|
Drafts live at `DSH_HOME/data/dsh-email/drafts-v1.json`, with uploads under `draft-uploads`. Without DSH_HOME the default is `~/.dsh`. Profiles sharing DSH_HOME share drafts; accounts resolve against the current profile and the actual identity appears in review.
|
package/docs/DRAFTS.md
CHANGED
|
@@ -43,6 +43,14 @@ trustedRecipientsYaml: |
|
|
|
43
43
|
|
|
44
44
|
账号改名后旧规则不会自动作用于新账号。规则变化不会重建邮箱连接。
|
|
45
45
|
|
|
46
|
+
## 全局发信确认
|
|
47
|
+
|
|
48
|
+
在「设置 → 邮件 → 邮箱账号」顶部,可切换「模型发信前确认」。默认开启;关闭会自动保存,并显示影响范围提示。
|
|
49
|
+
|
|
50
|
+
这个开关对应全局 `sendApproval`,对所有账号生效。关闭后,模型调用 `email_send` 或 `email_reply`(含回复全部、转发)不再经过插件逐次确认,即使收件人不在可信名单中。按账号的可信名单仍只对全部 To/Cc 均匹配的 `email_send` 跳过确认,不能等同于完全开放。
|
|
51
|
+
|
|
52
|
+
禁止地址和禁止域名继续在 SMTP 连接前拦截;宿主权限仍生效,草稿页仍需人工核对并确认发送。只想放行特定收件人时,保留全局确认并配置账号的可信规则即可。
|
|
53
|
+
|
|
46
54
|
## 存储与范围
|
|
47
55
|
|
|
48
56
|
草稿保存于 `DSH_HOME/data/dsh-email/drafts-v1.json`,上传副本位于其下 `draft-uploads`。未指定 DSH_HOME 时使用 `~/.dsh`。同一 DSH_HOME 共用草稿,发件账号以当前 profile 配置为准,预览会显示实际身份。
|
package/docs/USAGE.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
## 本次改进
|
|
6
6
|
|
|
7
|
+
`email_mark(action:"move")` 要求服务器支持 MOVE 或 UIDPLUS;两者均无时会拒绝移动,保持源邮件不变,避免全局 EXPUNGE 误删其他已标记删除的邮件。请改用邮箱原生客户端或升级服务器。
|
|
8
|
+
|
|
7
9
|
`email_send` 和 `email_draft` 的相对附件路径以当前会话工作区为基准,避免切换工作区后误读 DSH 启动目录的同名文件;绝对路径保持原样,没有会话上下文时仍使用启动目录。
|
|
8
10
|
|
|
9
11
|
在「收件人规则」页按账号设置禁止地址与域名;配置文本的字段为 `denyAddresses` / `denyDomains`。禁止规则覆盖 To、Cc,即使关闭发信确认也生效。域名按完整域名匹配,不自动包含子域名。移除被禁止的收件人并重新预览后才能确认发送;只有全部 To/Cc 匹配且主动开启的可信规则才会跳过 `email_send` 插件确认,宿主权限仍生效。
|
package/docs/VALIDATION.md
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 0.17.1 发信策略与设置保存验收
|
|
2
|
+
|
|
3
|
+
日期:2026-10-18。对应 [Issue #19](https://github.com/STARDUSTLC666/dsh-email/issues/19)。
|
|
4
|
+
|
|
5
|
+
环境:Windows x64、Node 24.16.0、官方 npm `@deepseek-ai/dsh@0.2.1-rc.2`,独立 `DSH_HOME`。实际安装候选 tarball;两个账号的 SMTP 均指向 `127.0.0.1` 测试服务,地址使用 `.invalid`。
|
|
6
|
+
|
|
7
|
+
## 改动
|
|
8
|
+
|
|
9
|
+
- 账号页已有全局 `sendApproval` 开关;关闭后新增中英文影响范围提示,只读设置不能编辑该开关。可信名单仍是按账号、所有 To/Cc 均匹配才跳过 `email_send` 确认。
|
|
10
|
+
- 修复新版宿主直接保存空服务商后,自定义 IMAP/SMTP 账号报 `provider "" 未知`。空选择与旧设置投影、YAML 序列化一致地按未设置处理,未知的非空服务商仍报错。
|
|
11
|
+
|
|
12
|
+
## 验证结果
|
|
13
|
+
|
|
14
|
+
| 检查 | 结果 |
|
|
15
|
+
| :-- | :-- |
|
|
16
|
+
| 空服务商回归 | 修复前新增测试失败;修复后单账号、账号映射、YAML、账号级空值均保留服务器与凭据 |
|
|
17
|
+
| 本地全套测试 | 349 项通过,0 失败、0 跳过 |
|
|
18
|
+
| 真实宿主允许分支 | 可信 To/Cc 实际提交到本机 SMTP;全局关闭确认后两个账号均可向未列入可信名单的地址提交 |
|
|
19
|
+
| 拒绝优先与宿主权限 | 被禁止的 Cc 覆盖可信规则;全局关闭确认时,禁止地址和真实 `tools.guard()` 拒绝仍阻止 SMTP 提交 |
|
|
20
|
+
| 实际页面保存后发送 | 浏览器切换开关并自动保存后,两张自定义账号卡片仍可解析,后续工具发信获得本机 SMTP 回执 |
|
|
21
|
+
| 页面与持久化 | 中文提示可见;重载保留开关与提示;切换英文正常;重新开启后提示消失并保存 |
|
|
22
|
+
| 页面布局与可访问性 | 提示由复选框的 `aria-describedby` 关联;检查时内容宽度与滚动宽度均为 605px,无控制台错误 |
|
|
23
|
+
| 草稿确认 | 在真实宿主中关闭全局确认后,草稿预览成功,未明确确认的发送仍返回 400 |
|
|
24
|
+
|
|
25
|
+
## 未验证范围
|
|
26
|
+
|
|
27
|
+
本轮没有运行真实模型回合中的逐次审批弹框。宿主工具 API 在缺少活跃模型回合时不能完成该交互;默认确认、混合名单、其他账号、子域名与回复策略另有自动契约测试,不把非回合调用的失败算成交互审批验收。
|
|
28
|
+
|
|
29
|
+
未连接个人邮箱、真实 IMAP 或外部 SMTP,也未验证 OAuth 登录、实际收件箱投递或原生 Desktop 窗口操作。
|
package/lib/client.js
CHANGED
|
@@ -358,6 +358,7 @@ var UI = {
|
|
|
358
358
|
|
|
359
359
|
// 全局设置
|
|
360
360
|
"global.sendApproval": "模型发信前确认(建议保留;可信规则可按账号跳过)",
|
|
361
|
+
"global.sendApprovalRisk": "关闭后,所有账号的模型发信、回复和转发将不再逐次询问,未列入可信名单的收件人也可能直接收到邮件。禁止收件人规则和宿主权限继续生效;草稿页仍需手动确认发送。",
|
|
361
362
|
"global.downloadDirLabel": "附件下载目录(默认保存在当前工作区)",
|
|
362
363
|
"global.downloadDirPlaceholder": "留空使用默认",
|
|
363
364
|
"global.hint": "以上设置对所有账号统一生效。需要插件确认的工具发信在 Full Access 下不会弹框;可改用草稿页手动核对并确认。禁止收件人规则始终生效。",
|
|
@@ -539,6 +540,7 @@ var UI = {
|
|
|
539
540
|
|
|
540
541
|
// 全局设置
|
|
541
542
|
"global.sendApproval": "Confirm model-initiated sends (recommended; saved trust can skip per account)",
|
|
543
|
+
"global.sendApprovalRisk": "With this off, model-initiated sends, replies and forwards skip this plugin's confirmation for every account, including recipients outside the trusted list. Blocked-recipient rules and host permissions still apply. Drafts still require manual send confirmation.",
|
|
542
544
|
"global.downloadDirLabel": "Attachment download directory (defaults to the current workspace)",
|
|
543
545
|
"global.downloadDirPlaceholder": "Leave empty for the default",
|
|
544
546
|
"global.hint": "These settings apply to every account. Tool sends that still require plugin confirmation cannot show a dialog under Full Access; use the Drafts tab to review and confirm manually. Blocked-recipient rules always apply.",
|
|
@@ -2685,10 +2687,15 @@ function EmailSettingsSection() {
|
|
|
2685
2687
|
h("input", {
|
|
2686
2688
|
type: "checkbox",
|
|
2687
2689
|
checked: draft.sendApproval === true,
|
|
2690
|
+
disabled: snapshot?.writable === false,
|
|
2691
|
+
"aria-describedby": draft.sendApproval === false ? "dshe-send-approval-risk" : undefined,
|
|
2688
2692
|
onChange: (e) => update({ sendApproval: e.target.checked }),
|
|
2689
2693
|
}),
|
|
2690
2694
|
t("global.sendApproval"),
|
|
2691
2695
|
]),
|
|
2696
|
+
draft.sendApproval === false
|
|
2697
|
+
? h("div", { id: "dshe-send-approval-risk", className: "dshe-alert info", role: "status" }, t("global.sendApprovalRisk"))
|
|
2698
|
+
: null,
|
|
2692
2699
|
h("div", { className: "dshe-field" }, [
|
|
2693
2700
|
h("label", null, t("global.downloadDirLabel")),
|
|
2694
2701
|
fieldInput("text", draft.downloadDir, (v) => update({ downloadDir: v }), t("global.downloadDirPlaceholder")),
|
package/lib/config.js
CHANGED
|
@@ -365,7 +365,9 @@ export function mergeAccountDefaults(common, account = {}) {
|
|
|
365
365
|
}
|
|
366
366
|
/** Merge one account over shared defaults and its selected provider, then validate it. */
|
|
367
367
|
function resolveAccount(name, common, acc, allowEnvPassword, providers, known) {
|
|
368
|
-
|
|
368
|
+
// Public settings persist the form's empty custom-server selection directly.
|
|
369
|
+
// Normalize it like the legacy projection and YAML writer before preset lookup.
|
|
370
|
+
const requested = (acc.provider === '' ? undefined : acc.provider) ?? (common.provider === '' ? undefined : common.provider);
|
|
369
371
|
const preset = requested === undefined ? undefined : providers[requested];
|
|
370
372
|
if (requested !== undefined && preset === undefined) {
|
|
371
373
|
throw new Error(`dsh-email:账号 "${name}" 的 provider "${requested}" 未知,可选:${known.join('/')};或省略 provider 直接填 imap.host 与 smtp.host`);
|
package/lib/mail-client.js
CHANGED
|
@@ -1033,6 +1033,12 @@ export class EmailPool {
|
|
|
1033
1033
|
throw new MailError('move 操作需要 toFolder 参数(用 email_folders 查看可用文件夹)');
|
|
1034
1034
|
if (target === folderName)
|
|
1035
1035
|
throw new MailError('邮件已在文件夹 "' + folderName + '" 中,无需移动');
|
|
1036
|
+
// ImapFlow falls back to COPY + STORE + EXPUNGE without MOVE. Only
|
|
1037
|
+
// UIDPLUS can scope that expunge to this UID; plain EXPUNGE also deletes
|
|
1038
|
+
// unrelated mail that another client marked \\Deleted.
|
|
1039
|
+
if (!client.capabilities?.has('MOVE') && !client.capabilities?.has('UIDPLUS')) {
|
|
1040
|
+
throw new MailError('服务器同时缺少 MOVE 和 UIDPLUS,无法安全移动邮件;操作已取消,源邮件未改动。请用邮箱原生客户端处理或升级服务器。');
|
|
1041
|
+
}
|
|
1036
1042
|
const folders = await client.list();
|
|
1037
1043
|
signal?.throwIfAborted();
|
|
1038
1044
|
if (!folders.some(row => row.path === target)) {
|