dsh-email 0.16.0 → 0.16.1

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 CHANGED
@@ -4,6 +4,11 @@
4
4
 
5
5
  [历史英文记录](docs/CHANGELOG.en.md)
6
6
 
7
+ ## 0.16.1 (2026-10-08)
8
+
9
+ - 修复切换会话工作区后相对附件可能读取启动目录同名文件的问题:发信与草稿工具都按当前会话解析路径,绝对路径和无会话调用保持兼容。
10
+ - 中英文账号页及模型工具说明区分逐次确认、主动启用的可信规则与禁止规则,提供 Full Access 下使用草稿手动核对的入口;修正配置文档中的禁止规则字段名。
11
+
7
12
  ## 0.16.0 (2026-10-05)
8
13
 
9
14
  - 增加每账号收件人禁止地址与禁止域名;禁止规则优先于可信名单,并在所有发信路径的 SMTP 连接前拦截。草稿预览显示阻止原因并保留可编辑草稿。
package/README.en.md CHANGED
@@ -8,6 +8,8 @@ Read, send, search and organize email from DSH with multiple accounts.
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/dsh-email)](https://www.npmjs.com/package/dsh-email) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-email-downloads.svg)](https://www.npmjs.com/package/dsh-email)
10
10
 
11
+ Feedback and contributions are welcome: report [issues](https://github.com/STARDUSTLC666/dsh-email/issues) or submit [pull requests](https://github.com/STARDUSTLC666/dsh-email/pulls).
12
+
11
13
  ## What it does
12
14
 
13
15
  - Read, send, reply and forward using IMAP and SMTP.
@@ -15,7 +17,7 @@ Read, send, search and organize email from DSH with multiple accounts.
15
17
  - Configure multiple accounts, Outlook OAuth2 and send approval.
16
18
  - Turn new-mail popups off in settings; the preference survives restarts.
17
19
  - Prepare and edit local drafts, review To/Cc, content and attachment bytes, then confirm sending.
18
- - Manage trusted recipients per account and preview address, group and Cc matches.
20
+ - Manage trusted and blocked recipients per account and preview address, group and Cc matches.
19
21
 
20
22
  ## Install
21
23
 
package/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/dsh-email)](https://www.npmjs.com/package/dsh-email) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-email-downloads.svg)](https://www.npmjs.com/package/dsh-email)
10
10
 
11
+ 欢迎使用,遇到问题或有改进建议,请提交 [issues](https://github.com/STARDUSTLC666/dsh-email/issues) 和 [PR](https://github.com/STARDUSTLC666/dsh-email/pulls)。
12
+
11
13
  ## 功能
12
14
 
13
15
  - 通过 IMAP / SMTP 收发、回复和转发邮件。
@@ -15,7 +17,7 @@
15
17
  - 多账号设置,支持 Outlook OAuth2 和发信审批。
16
18
  - 新邮件弹窗可在设置中关闭,保存后重启仍生效。
17
19
  - 准备和编辑本地草稿,核对发件人、To/Cc、正文与附件预览后再确认发送。
18
- - 按账号管理可信收件人,预览规则如何匹配地址、地址组和抄送。
20
+ - 按账号管理可信与禁止收件人,预览规则如何匹配地址、地址组和抄送。
19
21
 
20
22
  ## 安装
21
23
 
@@ -4,6 +4,11 @@
4
4
 
5
5
  These English notes preserve the earlier translations. The main changelog contains the consolidated version history.
6
6
 
7
+ ## 0.16.1 (2026-10-08)
8
+
9
+ - Resolve relative attachments against the current session workspace in both send and draft tools, preserving absolute paths and calls without session context.
10
+ - Clarify saved trust, deny rules and Full Access confirmation behavior in both languages and model-facing descriptions; correct the documented deny-rule field names.
11
+
7
12
  ## 0.16.0 (2026-10-05)
8
13
 
9
14
  - Add per-account blocked addresses and domains. Deny rules override trust and stop every sending path before SMTP connection. Draft previews explain the block and preserve editable drafts.
package/docs/DRAFTS.en.md CHANGED
@@ -7,7 +7,7 @@
7
7
  Ask the assistant to use `email_draft`, or create a draft in Settings → Email → Drafts. The tool only saves a local draft; it does not submit mail. Parameters are `to`, `subject`, `text`, with optional `account`, `cc`, and an `attachments` array of local paths.
8
8
 
9
9
  1. Choose the sender account, edit To/Cc, subject and plain text, then save.
10
- 2. Add attachments. Browser uploads become plugin-owned copies; tool-supplied paths remain references to the original files.
10
+ 2. Add attachments. Browser uploads become plugin-owned copies; tool-supplied paths remain references to the original files. Relative tool paths resolve against the current session workspace, while absolute paths remain exact. Calls without session context use the DSH startup directory.
11
11
  3. Review the actual sender and display name, every expanded To/Cc address, content, attachment names and sizes. Download preview attachments to inspect them.
12
12
  4. Acknowledge the review and confirm sending. Drafts require manual confirmation even when all recipients match trusted rules.
13
13
 
package/docs/DRAFTS.md CHANGED
@@ -7,7 +7,7 @@
7
7
  让助手使用 `email_draft` 准备邮件,或在「设置 → 邮件 → 草稿」新建。这个工具只写本地草稿,不连接 SMTP 发信。参数为 `to`、`subject`、`text`,可选 `account`、`cc` 和本地附件路径数组 `attachments`。
8
8
 
9
9
  1. 选择发件账号,编辑 To、Cc、主题和纯文本正文,然后保存。
10
- 2. 添加附件。网页文件选择器保存本插件的副本;工具提供的路径保留原文件引用。
10
+ 2. 添加附件。网页文件选择器保存本插件的副本;工具提供的路径保留原文件引用。工具中的相对路径按当前会话工作区解析,绝对路径保持原样;没有会话上下文时使用 DSH 启动目录。
11
11
  3. 点「检查发送预览」,核对真实发件地址与显示名、展开后的全部 To/Cc、正文、附件名称和大小。可下载本次预览的附件检查。
12
12
  4. 勾选已核对,再点「确认发送」。收件人规则即使全部匹配,草稿仍需这次手动确认。
13
13
 
package/docs/USAGE.en.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  ## Current improvements
6
6
 
7
- Optionally configure blockedAddresses / blockedDomains per account. Rules cover To and Cc and remain active with sendApproval=off. Domains match exactly, excluding subdomains unless listed. Remove blocked recipients and preview again before sending.
7
+ Relative attachment paths in `email_send` and `email_draft` resolve against the current session workspace, avoiding a same-name file in the DSH startup directory after switching workspaces. Absolute paths remain exact; calls without session context retain startup-directory fallback.
8
+
9
+ Set blocked addresses and domains per account in the Recipient Rules tab; the text configuration fields are `denyAddresses` / `denyDomains`. Rules cover To and Cc and remain active with send approval off. Domains match exactly, excluding subdomains unless listed. Remove blocked recipients and preview again before sending. Trust skips `email_send` plugin confirmation only when every To/Cc address matches and skipping has been explicitly enabled; host permissions still apply.
8
10
 
9
11
  ## Tools
10
12
 
package/docs/USAGE.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  ## 本次改进
6
6
 
7
- 设置页可选填 blockedAddresses / blockedDomains。禁止规则覆盖 To、Cc,sendApproval=off 时仍生效。域名按完整域名匹配,不自动包含子域名。移除被禁止的收件人并重新预览后才能确认发送。
7
+ `email_send` 和 `email_draft` 的相对附件路径以当前会话工作区为基准,避免切换工作区后误读 DSH 启动目录的同名文件;绝对路径保持原样,没有会话上下文时仍使用启动目录。
8
+
9
+ 在「收件人规则」页按账号设置禁止地址与域名;配置文本的字段为 `denyAddresses` / `denyDomains`。禁止规则覆盖 To、Cc,即使关闭发信确认也生效。域名按完整域名匹配,不自动包含子域名。移除被禁止的收件人并重新预览后才能确认发送;只有全部 To/Cc 匹配且主动开启的可信规则才会跳过 `email_send` 插件确认,宿主权限仍生效。
8
10
 
9
11
  ## 工具一览
10
12
 
@@ -1,5 +1,7 @@
1
1
  # dsh-email 验证记录
2
2
 
3
+ [0.16.1 会话附件与发信策略验收](validation/0.16.1.md)。
4
+
3
5
  [0.15.3 通知开关验收](validation/0.15.3.md)。
4
6
 
5
7
  [0.15.2 中英文界面验收](validation-language-2026-10-03.md)。
@@ -0,0 +1,21 @@
1
+ # 0.16.1:会话附件与发信策略说明
2
+
3
+ 日期:2026-10-08。Windows / Node 24.16.0,官方 DSH 0.2.1-alpha.1 的隔离网页版。
4
+
5
+ ## 修复与回归
6
+
7
+ - 在启动目录和会话工作区分别放置同名 `report.txt`,内容不同。`email_send` 实际文件读取和 `email_draft` 的附件预览均取得会话文件内容,启动目录原文件保持不变。
8
+ - 绝对附件路径保持精确;没有会话时保留启动目录回退。三个新增回归通过,既有收件人拒绝、确认、取消、大小限制与草稿快照规则保持。
9
+ - 中英文确认开关不再笼统声称 Full Access 必然拒绝:主动保存且启用的可信规则只跳过 `email_send` 插件确认,宿主权限仍生效;禁止规则始终优先。
10
+
11
+ ## 实际界面操作
12
+
13
+ 通过明确标记的脚本工具选择适配器,在聊天中用相对路径创建草稿;进入「设置 → 邮件 → 草稿」,检查发送预览,下载并核对附件,勾选核对框后向本机合成 SMTP 服务确认发送。服务器仅接收一封邮件,附件内容与当前会话文件一致;已发送草稿不再提供再次发送按钮。
14
+
15
+ 切换官方界面语言后核对英文账号页,确认说明、草稿入口和禁止规则提示均显示英文。1280×720 截图中相关说明可读,浏览器控制台无 error / warn,页面无空白或错误覆盖层。
16
+
17
+ ## 范围
18
+
19
+ 未操作用户原生 Electron 窗口,未使用真实邮箱凭据,未向外部邮箱发送测试邮件。脚本选工具验收不证明模型自主选工具能力;UI 的 SMTP 回执不代表外部收件箱送达。附件仅改变路径基准,不增加文件访问权限或关闭确认。
20
+
21
+ 开放反馈 [Email #19](https://github.com/STARDUSTLC666/dsh-email/issues/19) 的名单控件、匹配预览和显式开关均已具备;仍保留主动启用、域名精确匹配及宿主权限边界,咨询没有被自动关闭。
package/lib/client.js CHANGED
@@ -288,10 +288,10 @@ var UI = {
288
288
  "provider.undefinedPresetSuffix": "(未定义预设)",
289
289
 
290
290
  // 全局设置
291
- "global.sendApproval": "发信前弹确认(强烈建议保留;Full Access 模式下会被自动拒绝)",
291
+ "global.sendApproval": "模型发信前确认(建议保留;可信规则可按账号跳过)",
292
292
  "global.downloadDirLabel": "附件下载目录(默认保存在当前工作区)",
293
293
  "global.downloadDirPlaceholder": "留空使用默认",
294
- "global.hint": "以上设置对所有账号统一生效。",
294
+ "global.hint": "以上设置对所有账号统一生效。需要插件确认的工具发信在 Full Access 下不会弹框;可改用草稿页手动核对并确认。禁止收件人规则始终生效。",
295
295
 
296
296
  // 自动保存状态条
297
297
  "status.saved": "已保存",
@@ -469,10 +469,10 @@ var UI = {
469
469
  "provider.undefinedPresetSuffix": "(undefined preset)",
470
470
 
471
471
  // 全局设置
472
- "global.sendApproval": "Ask for confirmation before sending (strongly recommended; auto-denied under Full Access)",
472
+ "global.sendApproval": "Confirm model-initiated sends (recommended; saved trust can skip per account)",
473
473
  "global.downloadDirLabel": "Attachment download directory (defaults to the current workspace)",
474
474
  "global.downloadDirPlaceholder": "Leave empty for the default",
475
- "global.hint": "These settings apply to every account.",
475
+ "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.",
476
476
 
477
477
  // 自动保存状态条
478
478
  "status.saved": "Saved",
@@ -297,8 +297,8 @@ export const descriptions = {
297
297
  "email_read": 'Read one full email message by its uid (from email_list or email_search). Returns the plain-text body (HTML mail is converted; oversized bodies are truncated) plus attachment metadata; use email_attachment to download one.',
298
298
  "email_mark": 'Change an existing message: mark it read/unread, star/unstar it, or move it to another folder. Use after email_list/email_search when the user wants to tidy the mailbox (archive, clear unread, flag important mail). Moving uses the server MOVE/COPY so the uid changes; the new uid is reported when the server provides it.',
299
299
  "email_search": 'Search emails by a keyword. The server first searches sender, recipients and subject; those hits are re-verified against the envelopes and, when none of them really carries the keyword (some servers answer every search with the same uids), recent messages are scanned locally including their body while bodySearchFallback is enabled. offset skips the newest matches for paging; since/until still constrain both paths. Returns the same compact rows as email_list.',
300
- "email_send": 'Send an email from a configured account, optionally with file attachments (absolute paths, or relative to the dsh process cwd). Sending asks the user for approval (recipient, subject and attachment count are shown) unless sendApproval is disabled; in Full Access mode the approval policy never asks, so the send is refused with an explanation instead. Never invent recipients or content without the user\'s instruction.',
301
- "email_reply": 'Reply to, reply-all to, or forward an existing message (mode: reply | reply-all | forward). Recipients come from the original message (your own address is excluded automatically), the subject gets a single Re:/Fwd: prefix, the original text is quoted underneath, and In-Reply-To/References headers keep mail clients threading correctly. mode=forward needs the to parameter. Like email_send, this asks the user for approval before sending. Never invent recipients or content without the user\'s instruction.',
300
+ "email_send": 'Send an email from a configured account, optionally with file attachments (absolute paths, or relative to the current session workspace; process cwd is the fallback without a session). Saved blocked-address/domain rules reject To/Cc recipients even when sendApproval is off. Plugin confirmation is required by default, unless sendApproval is disabled or every To/Cc recipient matches saved trust rules with skipApproval enabled. Host permissions still apply. Full Access refuses a send that still requires plugin confirmation; use email_draft for human review and explicit sending. Never invent recipients or content without the user\'s instruction.',
301
+ "email_reply": 'Reply to, reply-all to, or forward an existing message (mode: reply | reply-all | forward). Recipients come from the original message (your own address is excluded automatically), the subject gets a single Re:/Fwd: prefix, the original text is quoted underneath, and In-Reply-To/References headers keep mail clients threading correctly. mode=forward needs the to parameter. Plugin confirmation is required unless sendApproval is disabled; trusted-recipient skipping only applies to email_send. Blocked recipients still stop delivery before SMTP and host permissions remain in effect. Never invent recipients or content without the user\'s instruction.',
302
302
  "email_folders": 'List the mailbox folders of an account (INBOX, Sent, Trash, custom folders, ...). Use the returned path values as the folder argument of the other email tools.',
303
303
  "email_health": 'Self-check for dsh-email: summarizes configured accounts (provider / IMAP / SMTP hosts) without any network connection and never shows passwords. Run this first when troubleshooting.',
304
304
  "email_attachment": 'Download one attachment of a message to a local file (size capped by maxAttachmentBytes). The index matches the attachments array of email_read. Returns the absolute path of the written file.',
@@ -340,7 +340,7 @@ export const parameters = {
340
340
  subject: { type: 'string', required: true, description: 'Email subject' },
341
341
  text: { type: 'string', description: 'Plain-text body' },
342
342
  cc: { type: 'string', description: 'CC recipient(s), comma-separated' },
343
- attachments: { type: 'array', items: { type: 'string' }, description: 'File paths to attach (absolute, or relative to the dsh process cwd)' },
343
+ attachments: { type: 'array', items: { type: 'string' }, description: 'File paths to attach (absolute, or relative to the current session workspace; process cwd is the fallback without a session)' },
344
344
  account: { type: 'string', description: ACCOUNT_HINT },
345
345
  }),
346
346
  "email_reply": compileParameters({
package/lib/tools.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { resolve } from 'node:path';
1
2
  import { clampInt, PROVIDER_PRESETS } from './config.js';
2
3
  import { messageOf } from './mail-client.js';
3
4
  import { NOT_LOGGED_IN_MESSAGE, oauth2StateOf } from './oauth2.js';
@@ -5,16 +6,22 @@ import { descriptions, parameters, MAX_LIMIT, MARK_ACTIONS, REPLY_MODES, executi
5
6
  /** 单次工具调用的 deadline:普通查询 60s,正文扫描/watch 这类整批操作 120s。 */
6
7
  const QUERY_TIMEOUT_MS = 60000;
7
8
  const BATCH_TIMEOUT_MS = 120000;
9
+ function attachmentPaths(value, exec) {
10
+ const context = exec;
11
+ const cwd = context?.agent?.session?.header?.cwd;
12
+ const base = typeof cwd === 'string' && cwd.trim() ? cwd : process.cwd();
13
+ return normalizeAttachmentPaths(value)?.map(path => resolve(base, path));
14
+ }
8
15
  export function buildEmailTools(runtime, drafts) {
9
16
  const { getPool, getEffectiveSettings, watch: watchCore } = runtime;
10
17
  return [
11
18
  ...(drafts ? [{
12
19
  name: 'email_draft',
13
20
  description: '准备可编辑的本地邮件草稿,不投递邮件。用户在设置 → 邮件 → 草稿中编辑、检查附件预览并明确确认后才发送。可在邮箱尚未配置时准备文字草稿;不用于回复原邮件线程。',
14
- parameters: { type: 'object', properties: { account: { type: 'string', description: '发件账号名,省略时使用默认账号' }, to: { type: 'string', description: 'To 地址,支持显示名和地址组' }, cc: { type: 'string' }, subject: { type: 'string' }, text: { type: 'string', description: '纯文本正文' }, attachments: { type: 'array', items: { type: 'string' }, description: '最多 10 个本地附件路径,用户在预览中检查后才提交' } }, required: ['to', 'subject', 'text'], additionalProperties: false },
21
+ parameters: { type: 'object', properties: { account: { type: 'string', description: '发件账号名,省略时使用默认账号' }, to: { type: 'string', description: 'To 地址,支持显示名和地址组' }, cc: { type: 'string' }, subject: { type: 'string' }, text: { type: 'string', description: '纯文本正文' }, attachments: { type: 'array', items: { type: 'string' }, description: '最多 10 个本地附件路径;相对路径以当前会话工作区为基准,用户在预览中检查后才提交' } }, required: ['to', 'subject', 'text'], additionalProperties: false },
15
22
  output: { schema: { type: 'object', properties: { id: { type: 'string' }, state: { type: 'string' }, subject: { type: 'string' } }, required: ['id', 'state', 'subject'] }, render: (_args, value) => [{ type: 'text', text: '本地草稿已保存,邮件未发送。请在设置 → 邮件 → 草稿中打开并检查。\n草稿编号:' + value.id + '\n主题:' + value.subject }] },
16
23
  timeoutMs: QUERY_TIMEOUT_MS,
17
- async execute(args, exec) { const draft = await drafts.create(args, executionSignal(exec)); return { id: draft.id, state: draft.state, subject: draft.subject }; },
24
+ async execute(args, exec) { const input = args; const draft = await drafts.create({ ...input, attachments: attachmentPaths(input.attachments, exec) }, executionSignal(exec)); return { id: draft.id, state: draft.state, subject: draft.subject }; },
18
25
  }] : []),
19
26
  {
20
27
  name: 'email_list',
@@ -110,7 +117,7 @@ export function buildEmailTools(runtime, drafts) {
110
117
  throw new Error('to 不能为空');
111
118
  if (typeof args.subject !== 'string' || args.subject.trim() === '')
112
119
  throw new Error('subject 不能为空');
113
- return await getPool().send(args.account, args.to.trim(), args.subject.trim(), typeof args.text === 'string' ? args.text : undefined, typeof args.cc === 'string' && args.cc.trim() !== '' ? args.cc.trim() : undefined, normalizeAttachmentPaths(args.attachments), executionSignal(exec));
120
+ return await getPool().send(args.account, args.to.trim(), args.subject.trim(), typeof args.text === 'string' ? args.text : undefined, typeof args.cc === 'string' && args.cc.trim() !== '' ? args.cc.trim() : undefined, attachmentPaths(args.attachments, exec), executionSignal(exec));
114
121
  }
115
122
  },
116
123
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-email",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "在 DSH 中收发、搜索和整理邮件,支持多个邮箱账号。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",