dsh-email 0.10.2 → 0.10.3
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.en.md +5 -1
- package/README.md +5 -1
- package/lib/approval.d.ts +34 -0
- package/lib/approval.js +55 -0
- package/lib/index.d.ts +8 -12
- package/lib/index.js +17 -669
- package/lib/mail-client.d.ts +12 -10
- package/lib/mail-client.js +141 -52
- package/lib/runtime.d.ts +36 -0
- package/lib/runtime.js +92 -0
- package/lib/tool-contract.d.ts +423 -0
- package/lib/tool-contract.js +353 -0
- package/lib/tools.d.ts +13 -0
- package/lib/tools.js +191 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -149,9 +149,13 @@ Every provider requires an authorization code / app-specific password instead of
|
|
|
149
149
|
```sh
|
|
150
150
|
pnpm install
|
|
151
151
|
pnpm run build # tsc → lib/
|
|
152
|
-
pnpm test #
|
|
152
|
+
pnpm test # build + offline tests; no real mailbox required
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
`src/index.ts` composes the plugin. `runtime.ts` owns live settings, account pools, and separate tool/web watch cursors. `tools.ts` wires the ten tool implementations. `tool-contract.ts` defines parameters, output schemas, and text rendering. `approval.ts` owns the outgoing-mail gate. IMAP/SMTP transport remains in `mail-client.ts`, and browser routes remain in `web.ts`.
|
|
156
|
+
|
|
157
|
+
Tests cover pool replacement after live settings changes, unload cleanup, cancellation and workspace propagation, independent tool/web cursors, and rejected approval preventing send execution. In-memory clients replace mailbox connections.
|
|
158
|
+
|
|
155
159
|
## License
|
|
156
160
|
|
|
157
161
|
MIT. This is a community plugin, not affiliated with DeepSeek; `@deepseek-ai/*` is an officially reserved namespace.
|
package/README.md
CHANGED
|
@@ -183,9 +183,13 @@ dsh plugin --profile web remove dsh-email
|
|
|
183
183
|
```sh
|
|
184
184
|
pnpm install
|
|
185
185
|
pnpm run build # tsc → lib/
|
|
186
|
-
pnpm test # 构建 +
|
|
186
|
+
pnpm test # 构建 + 离线测试,无需真实邮箱
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
`src/index.ts` 只负责组合插件。`runtime.ts` 管理动态设置、账号连接池和网页/工具各自的监视游标;`tools.ts` 接线十个工具的执行逻辑;`tool-contract.ts` 集中维护参数、输出 schema 和中文渲染;`approval.ts` 管理发信审批。IMAP/SMTP 传输仍由 `mail-client.ts` 负责,网页路由由 `web.ts` 负责。
|
|
190
|
+
|
|
191
|
+
测试覆盖动态配置换池、卸载释放、取消信号与工作区透传、工具/网页游标隔离,以及审批拒绝时不会进入发送执行。测试用内存客户端替代邮箱连接。
|
|
192
|
+
|
|
189
193
|
## 协议
|
|
190
194
|
|
|
191
195
|
MIT。这是一个社区插件,与 DeepSeek 官方无关;`@deepseek-ai/*` 为官方保留命名空间。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Outgoing-mail approval is independent of tool execution. */
|
|
2
|
+
import type { EmailRuntime } from './runtime.js';
|
|
3
|
+
type ApprovalDecision = {
|
|
4
|
+
kind: 'allow';
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'deny';
|
|
7
|
+
reason: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'ask';
|
|
10
|
+
reason?: string;
|
|
11
|
+
};
|
|
12
|
+
interface PendingExecution {
|
|
13
|
+
name: string;
|
|
14
|
+
arguments?: unknown;
|
|
15
|
+
agent?: unknown;
|
|
16
|
+
callId?: string;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}
|
|
19
|
+
interface ApprovalContext {
|
|
20
|
+
on(event: 'tools/pre-execute', listener: (exec: PendingExecution, next: () => Promise<ApprovalDecision>) => Promise<ApprovalDecision>, options: {
|
|
21
|
+
prepend: boolean;
|
|
22
|
+
}): unknown;
|
|
23
|
+
get(name: 'approval'): {
|
|
24
|
+
request(input: {
|
|
25
|
+
agent?: unknown;
|
|
26
|
+
toolName: string;
|
|
27
|
+
callId?: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}): Promise<string>;
|
|
31
|
+
} | undefined;
|
|
32
|
+
}
|
|
33
|
+
export declare function installSendApproval(ctx: ApprovalContext, runtime: Pick<EmailRuntime, 'getSettingsValue' | 'getEffectiveSettings'>): void;
|
|
34
|
+
export {};
|
package/lib/approval.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function installSendApproval(ctx, runtime) {
|
|
2
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
3
|
+
if (exec?.name !== 'email_send' && exec?.name !== 'email_reply')
|
|
4
|
+
return next();
|
|
5
|
+
const value = runtime.getSettingsValue();
|
|
6
|
+
if (value.sendApproval === false)
|
|
7
|
+
return next();
|
|
8
|
+
try {
|
|
9
|
+
runtime.getEffectiveSettings();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return next(); // unconfigured: let the tool report the actionable hint
|
|
13
|
+
}
|
|
14
|
+
let reason;
|
|
15
|
+
if (exec.name === 'email_send') {
|
|
16
|
+
const args = (exec.arguments ?? {});
|
|
17
|
+
const attachCount = Array.isArray(args.attachments) ? args.attachments.length : 0;
|
|
18
|
+
reason = '发送邮件给 ' + args.to + ',主题「' + args.subject + '」' + (attachCount > 0 ? ',附件 ' + attachCount + ' 个' : '');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const args = (exec.arguments ?? {});
|
|
22
|
+
const mode = typeof args.mode === 'string' && args.mode.trim() !== '' ? args.mode.trim().toLowerCase() : 'reply';
|
|
23
|
+
const modeLabel = mode === 'forward' ? '转发' : mode === 'reply-all' ? '回复全部' : '回复';
|
|
24
|
+
reason = modeLabel + '邮件(原邮件 uid=' + args.uid + ')' + (mode === 'forward' && typeof args.to === 'string' && args.to.trim() !== '' ? ',收件人 ' + args.to : '');
|
|
25
|
+
}
|
|
26
|
+
// Gate-owned approval: we run the approval round-trip ourselves so the
|
|
27
|
+
// denial reason is always honest and actionable — including the Full
|
|
28
|
+
// Access case where the harness policy answers 'rejected' without ever
|
|
29
|
+
// showing a dialog.
|
|
30
|
+
const approval = ctx.get('approval');
|
|
31
|
+
if (approval === undefined) {
|
|
32
|
+
return {
|
|
33
|
+
kind: 'deny',
|
|
34
|
+
reason: 'email_send 需要确认,但当前环境没有审批通道(如 headless)。如确定安全,可在配置中设置 sendApproval: false 后直接发送。',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const outcome = await approval.request({
|
|
38
|
+
agent: exec.agent,
|
|
39
|
+
toolName: exec.name,
|
|
40
|
+
callId: exec.callId,
|
|
41
|
+
reason,
|
|
42
|
+
signal: exec.signal,
|
|
43
|
+
});
|
|
44
|
+
if (outcome === 'allowed-once')
|
|
45
|
+
return next();
|
|
46
|
+
if (outcome === 'cancelled')
|
|
47
|
+
return { kind: 'deny', reason: '发信确认被取消,邮件未发送。' };
|
|
48
|
+
if (outcome === 'unavailable')
|
|
49
|
+
return { kind: 'deny', reason: '发信确认不可用(没有可用的审批界面),邮件未发送。' };
|
|
50
|
+
return {
|
|
51
|
+
kind: 'deny',
|
|
52
|
+
reason: '发信未获批准:要么你拒绝了,要么当前会话处于 Full Access(审批策略 never,不会弹框)。若在 Full Access:切到 Read Only / Write 再发,或关闭 sendApproval(自行承担风险)。',
|
|
53
|
+
};
|
|
54
|
+
}, { prepend: true });
|
|
55
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { EmailConfig } from './config.js';
|
|
2
2
|
export declare const name = "tool-email";
|
|
3
3
|
export declare const inject: string[];
|
|
4
4
|
export type Config = EmailConfig;
|
|
5
|
-
/**
|
|
6
|
-
* 解析天级日期参数为 Date。接受 YYYY-MM-DD 或完整 ISO 时间。
|
|
7
|
-
* endInclusive=true 时返回“该日结束”(次日零点),用于 until 语义(IMAP BEFORE 是不含当天的)。
|
|
8
|
-
*/
|
|
9
|
-
export declare function parseEmailDay(input: string, label: string, endInclusive?: boolean): Date;
|
|
5
|
+
/** Compose settings/pool lifecycle, tools, browser routes and the outgoing-mail gate. */
|
|
10
6
|
export declare function apply(ctx: any, config?: Config): void;
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
7
|
+
export { clampInt, defaultDownloadDir, EMAIL_PASSWORD_ENV, parseAccountsYaml, PROVIDER_NAMES, resolveEmailConfig, resolveEmailSettings } from './config.js';
|
|
8
|
+
export { buildReplyMessage, EmailPool, extractMessageIds, MailError, messageMatchesQuery, messageOf, selectAttachmentPart, validateAttachmentPaths } from './mail-client.js';
|
|
9
|
+
export { flattenAddresses, parseRawMessage, sanitizeFilename, stripHtml, truncateText } from './parse.js';
|
|
10
|
+
export { EmailSettingsSchema, SETTINGS_NAMESPACE, toEmailConfig, toSettingsBase, validateSettingsValue } from './settings.js';
|
|
11
|
+
export { parseEmailDay } from './tool-contract.js';
|
|
12
|
+
export { EmailSettingsBackend, installEmailSettingsWeb, SETTINGS_ROUTE } from './web.js';
|