dsh-email 0.3.3 → 0.4.0

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/README.md CHANGED
@@ -120,7 +120,7 @@ dsh plugin --profile web add dsh-email
120
120
 
121
121
  - **授权码就是你的邮箱钥匙**。它写在本机(profile 的 `cordis.patch.yml` 或 `settings.yaml`),请勿提交到任何 Git 仓库;更推荐用环境变量 `DSH_EMAIL_PASSWORD`。
122
122
  - `email_send` 默认走 DSH 审批通道:每次发信都显示「发送邮件给 xx,主题「xx」」,你批准才发出。没有审批通道的环境(如无 UI 的 headless)会**直接拒绝发信**,这是安全默认。
123
- - 注意:会话处于 **Full Access(完全访问)** 模式时,harness 会把审批策略置为 never,`email_send` 会被**静默拒绝**(不弹框)。想发信请把访问模式切回 Read Only Write
123
+ - 会话处于 **Full Access(完全访问)** 模式时,harness 的审批策略是 never(不弹任何确认框)——`email_send` 会**被拦截并给出明确提示**。两条出路:① 把访问模式切回 Read Only / Write;② 关闭 `sendApproval`(设置页勾掉「发信前确认」),即显式声明自行承担风险。
124
124
  - 本插件不做任何联网上报,凭证只在内存中用于连接你的邮箱服务器。
125
125
 
126
126
  ## 已知限制(v0.3)
@@ -138,7 +138,7 @@ dsh plugin --profile web add dsh-email
138
138
  ```sh
139
139
  pnpm install
140
140
  pnpm run build # tsc → lib/
141
- pnpm test # 构建 + node --test(配置/解析/注册与审批门,34 个用例,无需真实邮箱)
141
+ pnpm test # 构建 + node --test(配置/解析/注册与审批门,37 个用例,无需真实邮箱)
142
142
  ```
143
143
 
144
144
  ## 协议
package/lib/index.js CHANGED
@@ -164,6 +164,20 @@ function renderFolders(value) {
164
164
  function renderAttachment(value) {
165
165
  return oneText('账号 ' + value.account + ' 已下载附件 "' + value.filename + '"(' + value.contentType + ',' + value.size + ' 字节)到:\n' + value.path + '\n可用 read 工具读取该文件。');
166
166
  }
167
+ /**
168
+ * Best-effort read of the session's effective approval policy. Duck-typed on
169
+ * the permission-presets seam; any drift or absence yields undefined and the
170
+ * gate falls back to the plain ask flow.
171
+ */
172
+ function effectiveApprovalPolicy(ctx, exec) {
173
+ try {
174
+ const preset = ctx.get?.('permissionPresets')?.current?.(exec?.agent?.session?.events);
175
+ return typeof preset?.approval === 'string' ? preset.approval : undefined;
176
+ }
177
+ catch {
178
+ return undefined;
179
+ }
180
+ }
167
181
  function fingerprintSettings(settings) {
168
182
  return JSON.stringify({
169
183
  accounts: [...settings.accounts.entries()].map(([name, account]) => [name, account]),
@@ -278,7 +292,7 @@ export function apply(ctx, config = {}) {
278
292
  });
279
293
  ctx.tools.register({
280
294
  name: 'email_send',
281
- description: 'Send an email from a configured account, optionally with file attachments (absolute paths, or relative to the dsh process cwd). Sending first asks the user for approval (recipient, subject and attachment count are shown) unless sendApproval is disabled; never invent recipients or content without the user\'s instruction.',
295
+ description: '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.',
282
296
  parameters: compileParameters({
283
297
  to: { type: 'string', required: true, description: 'Recipient(s), comma-separated' },
284
298
  subject: { type: 'string', required: true, description: 'Email subject' },
@@ -358,6 +372,15 @@ export function apply(ctx, config = {}) {
358
372
  const args = (exec.args ?? {});
359
373
  const attachCount = Array.isArray(args.attachments) ? args.attachments.length : 0;
360
374
  const reason = '发送邮件给 ' + args.to + ',主题「' + args.subject + '」' + (attachCount > 0 ? ',附件 ' + attachCount + ' 个' : '');
375
+ // Full Access (approval policy 'never') rejects asks before any answerer
376
+ // runs, so surface the actionable explanation instead of a misleading
377
+ // "the user rejected" failure.
378
+ if (effectiveApprovalPolicy(ctx, exec) === 'never') {
379
+ return {
380
+ kind: 'deny',
381
+ reason: 'email_send 需要确认,但当前会话处于 Full Access(审批策略 never),不会弹出确认框。两条路:① 把访问模式切回 Read Only 或 Write 再发;② 在设置页关闭「发信前确认」(sendApproval: false),自行承担风险。',
382
+ };
383
+ }
361
384
  return { kind: 'ask', reason };
362
385
  }, { prepend: true });
363
386
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-email",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "IMAP/SMTP email tools for DeepSeek Harness: list, read, search and send mail, with QQ/163/126/Sina/Aliyun/Gmail/Outlook/iCloud presets.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",