@dsh-plus/notify-email 0.1.49 → 0.1.50
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/lib/client.js +893 -136
- package/lib/index.d.ts +2 -4
- package/lib/index.js +6 -14
- package/package.json +2 -2
- package/src/client/card.tsx +23 -28
- package/src/client/draft.ts +0 -4
- package/src/client/fields.tsx +4 -8
- package/src/client/i18n.ts +2 -2
- package/src/config.ts +5 -3
- package/src/mailer.ts +3 -7
- package/src/service.ts +7 -10
package/lib/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { UnwrapVolatile } from "@dsh-plus/shared";
|
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
//#region src/config.d.ts
|
|
5
5
|
declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
6
|
-
enabled: z<boolean, boolean, "volatile-defined">;
|
|
7
6
|
smtp: z<NoInfer<Schemastery.ObjectS<NoInfer<{
|
|
8
7
|
host: z<string, string, "defined">;
|
|
9
8
|
port: z<number, number, "defined">;
|
|
@@ -37,7 +36,6 @@ declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
37
36
|
maxBodyChars: z<number, number, "volatile-defined">;
|
|
38
37
|
dryRun: z<boolean, boolean, "volatile-defined">;
|
|
39
38
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
40
|
-
enabled: z<boolean, boolean, "volatile-defined">;
|
|
41
39
|
smtp: z<NoInfer<Schemastery.ObjectS<NoInfer<{
|
|
42
40
|
host: z<string, string, "defined">;
|
|
43
41
|
port: z<number, number, "defined">;
|
|
@@ -134,12 +132,12 @@ declare class NotifyEmailService extends Service {
|
|
|
134
132
|
dispatchDecision(call: DecisionCall): Promise<void>;
|
|
135
133
|
/** 任务停止类观察入口(turn-end watcher 调用)。 */
|
|
136
134
|
dispatchTurnEnd(info: TurnEndInfo): Promise<void>;
|
|
137
|
-
/**
|
|
135
|
+
/** 配置卡片「发送测试邮件」:走与常规通知同一条发送路径。 */
|
|
138
136
|
sendTest(): Promise<{
|
|
139
137
|
ok: boolean;
|
|
140
138
|
detail: string;
|
|
141
139
|
}>;
|
|
142
|
-
/** 通用直发接口(如 lifeboat
|
|
140
|
+
/** 通用直发接口(如 lifeboat 故障告警):走正常完整性门禁。 */
|
|
143
141
|
sendNotice(notice: {
|
|
144
142
|
subject: string;
|
|
145
143
|
text: string;
|
package/lib/index.js
CHANGED
|
@@ -97,7 +97,6 @@ const TriggerSchema = z.object({
|
|
|
97
97
|
onPlanReview: z.boolean().description("plan 待审批时通知").default(true)
|
|
98
98
|
});
|
|
99
99
|
const Config = z.object({
|
|
100
|
-
enabled: z.boolean().description("总开关").default(false).volatile(),
|
|
101
100
|
smtp: SmtpSchema.default({
|
|
102
101
|
host: "",
|
|
103
102
|
port: 465,
|
|
@@ -173,16 +172,9 @@ var Mailer = class {
|
|
|
173
172
|
this.audit = audit;
|
|
174
173
|
this.credentials = credentials;
|
|
175
174
|
}
|
|
176
|
-
/**
|
|
177
|
-
|
|
178
|
-
* @param force 为 true 时跳过 enabled 门禁(配置卡片的「发送测试邮件」),仍需 SMTP 完整。
|
|
179
|
-
*/
|
|
180
|
-
async send(msg, force = false) {
|
|
175
|
+
/** 通知发送:受配置完整性门禁(整行开关由官方行开关承担)。 */
|
|
176
|
+
async send(msg) {
|
|
181
177
|
const cfg = this.resolveConfig();
|
|
182
|
-
if (!force && !cfg.enabled) return this.record(msg, cfg, {
|
|
183
|
-
ok: false,
|
|
184
|
-
detail: "disabled"
|
|
185
|
-
});
|
|
186
178
|
if (cfg.smtp.host.length === 0 || cfg.smtp.from.length === 0 || cfg.to.length === 0) return this.record(msg, cfg, {
|
|
187
179
|
ok: false,
|
|
188
180
|
detail: "incomplete"
|
|
@@ -480,14 +472,14 @@ var NotifyEmailService = class extends Service {
|
|
|
480
472
|
async dispatchTurnEnd(info) {
|
|
481
473
|
await this.dispatch("turn-end", info, (t) => t.onTurnEnd?.(info));
|
|
482
474
|
}
|
|
483
|
-
/**
|
|
475
|
+
/** 配置卡片「发送测试邮件」:走与常规通知同一条发送路径。 */
|
|
484
476
|
async sendTest() {
|
|
485
477
|
return this.mailer.send({
|
|
486
478
|
subject: "[DSH] 邮件通知测试",
|
|
487
479
|
text: "这是一封来自 dsh notify-email 插件的测试邮件。"
|
|
488
|
-
}
|
|
480
|
+
});
|
|
489
481
|
}
|
|
490
|
-
/** 通用直发接口(如 lifeboat
|
|
482
|
+
/** 通用直发接口(如 lifeboat 故障告警):走正常完整性门禁。 */
|
|
491
483
|
async sendNotice(notice) {
|
|
492
484
|
return this.mailer.send(notice);
|
|
493
485
|
}
|
|
@@ -502,7 +494,7 @@ var NotifyEmailService = class extends Service {
|
|
|
502
494
|
}
|
|
503
495
|
if (notice === void 0) continue;
|
|
504
496
|
const result = await this.mailer.send(notice);
|
|
505
|
-
if (!result.ok && result.detail !== "
|
|
497
|
+
if (!result.ok && result.detail !== "incomplete") this.ctx.logger("notify-email").warn(`deliver failed via trigger ${trigger.id}: ${result.detail}`);
|
|
506
498
|
return;
|
|
507
499
|
}
|
|
508
500
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/notify-email",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: 任务结束/等待决策/出错时向指定邮箱发送邮件通知",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"nodemailer": "9.0.6",
|
|
35
|
-
"@dsh-plus/shared": "0.1.
|
|
35
|
+
"@dsh-plus/shared": "0.1.44"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.5",
|
package/src/client/card.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 「邮件通知」配置卡片:经 injectPluginConfigCard 注册(插件页 plugins.row.config /
|
|
3
3
|
* plugins.bundle.config,view 分发 summary/page)。
|
|
4
|
-
* 外壳与基础控件走 @dsh-plus/shared/client 套件(
|
|
4
|
+
* 外壳与基础控件走 @dsh-plus/shared/client 套件(ConfigShell/Section/字段控件),
|
|
5
5
|
* 本文件只保留业务字段与保存/测试逻辑。交互对齐官方卡片:折叠/展开、
|
|
6
6
|
* staged draft、未保存标记、保存/放弃;另加「发送测试邮件」。
|
|
7
7
|
* 配置读写经 ctx.remote.settings 直连(connection.api.settings
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
type CardAction,
|
|
16
|
-
CardChrome,
|
|
17
16
|
CardLoading,
|
|
18
17
|
type CardStatusState,
|
|
19
|
-
|
|
18
|
+
ConfigShell,
|
|
20
19
|
IDLE_STATUS,
|
|
21
20
|
type NamespaceSettingsApi,
|
|
22
21
|
type PluginConfigViewProps,
|
|
23
22
|
type Scope,
|
|
23
|
+
Section,
|
|
24
24
|
} from '@dsh-plus/shared/client'
|
|
25
25
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
26
26
|
import { SETTINGS_NS } from '../ns.ts'
|
|
@@ -209,11 +209,10 @@ export function NotifyEmailCard(props: CardProps): ReactElement | string | null
|
|
|
209
209
|
|
|
210
210
|
const disabled = !snapshot.writable
|
|
211
211
|
return (
|
|
212
|
-
<
|
|
212
|
+
<ConfigShell
|
|
213
213
|
prefix="dne"
|
|
214
214
|
title={t('title')}
|
|
215
215
|
description={t('description')}
|
|
216
|
-
statusBadge={{ text: t(draft.enabled ? 'enabledOn' : 'enabledOff'), on: draft.enabled }}
|
|
217
216
|
dirty={dirty}
|
|
218
217
|
dirtyLabel={t('unsaved')}
|
|
219
218
|
readOnlyNotice={disabled ? t('readOnly') : undefined}
|
|
@@ -233,28 +232,24 @@ export function NotifyEmailCard(props: CardProps): ReactElement | string | null
|
|
|
233
232
|
onSave,
|
|
234
233
|
})}
|
|
235
234
|
>
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
onEdit={edit}
|
|
256
|
-
onTrigger={editTrigger}
|
|
257
|
-
/>
|
|
258
|
-
</CardChrome>
|
|
235
|
+
<Section prefix="dne" title={t('smtpGroup')}>
|
|
236
|
+
<SmtpFields
|
|
237
|
+
t={t}
|
|
238
|
+
draft={draft}
|
|
239
|
+
disabled={disabled}
|
|
240
|
+
passConfigured={passConfigured}
|
|
241
|
+
onEdit={edit}
|
|
242
|
+
/>
|
|
243
|
+
</Section>
|
|
244
|
+
<Section prefix="dne" title={t('triggerGroup')}>
|
|
245
|
+
<TriggerFields
|
|
246
|
+
t={t}
|
|
247
|
+
draft={draft}
|
|
248
|
+
disabled={disabled}
|
|
249
|
+
onEdit={edit}
|
|
250
|
+
onTrigger={editTrigger}
|
|
251
|
+
/>
|
|
252
|
+
</Section>
|
|
253
|
+
</ConfigShell>
|
|
259
254
|
)
|
|
260
255
|
}
|
package/src/client/draft.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
/** settings 命名空间的脱敏解析值(smtp.pass 被脱敏剥除)。 */
|
|
8
8
|
export interface ConfigValue {
|
|
9
|
-
enabled: boolean
|
|
10
9
|
smtp: {
|
|
11
10
|
host: string
|
|
12
11
|
port: number
|
|
@@ -29,7 +28,6 @@ export interface ConfigValue {
|
|
|
29
28
|
|
|
30
29
|
/** 编辑草稿:数值字段以文本承载(保存时校验并折算)。 */
|
|
31
30
|
export interface Draft {
|
|
32
|
-
enabled: boolean
|
|
33
31
|
host: string
|
|
34
32
|
port: string
|
|
35
33
|
secure: boolean
|
|
@@ -60,7 +58,6 @@ export function isPositiveInt(text: string): boolean {
|
|
|
60
58
|
|
|
61
59
|
export function draftFromValue(value: ConfigValue): Draft {
|
|
62
60
|
return {
|
|
63
|
-
enabled: value.enabled,
|
|
64
61
|
host: value.smtp.host,
|
|
65
62
|
port: String(value.smtp.port),
|
|
66
63
|
secure: value.smtp.secure,
|
|
@@ -78,7 +75,6 @@ export function draftFromValue(value: ConfigValue): Draft {
|
|
|
78
75
|
/** 草稿 → 提交形状(pass 为空表示「保持已存密钥」,由调用方剔除该键)。 */
|
|
79
76
|
export function toPatch(draft: Draft): Record<string, unknown> {
|
|
80
77
|
return {
|
|
81
|
-
enabled: draft.enabled,
|
|
82
78
|
smtp: {
|
|
83
79
|
host: draft.host.trim(),
|
|
84
80
|
port: Number(draft.port),
|
package/src/client/fields.tsx
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module notify-email/client/fields
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { CheckRow, TextField } from '@dsh-plus/shared/client'
|
|
7
|
+
import { CheckRow, NumberField, TextField } from '@dsh-plus/shared/client'
|
|
8
8
|
import type { ReactElement } from 'react'
|
|
9
9
|
|
|
10
10
|
import { type Draft, isPositiveInt, TRIGGER_KEYS, type TriggerKey } from './draft.ts'
|
|
@@ -32,13 +32,12 @@ export function SmtpFields(props: CardFieldsProps & { passConfigured: boolean })
|
|
|
32
32
|
disabled={disabled}
|
|
33
33
|
onEdit={(v) => onEdit('host', v)}
|
|
34
34
|
/>
|
|
35
|
-
<
|
|
35
|
+
<NumberField
|
|
36
36
|
prefix="dne"
|
|
37
37
|
id="dne-port"
|
|
38
38
|
label={t('port')}
|
|
39
39
|
hint={t('portHint')}
|
|
40
40
|
value={draft.port}
|
|
41
|
-
numeric
|
|
42
41
|
disabled={disabled}
|
|
43
42
|
invalid={!isPositiveInt(draft.port)}
|
|
44
43
|
invalidLabel={t('invalidNumber')}
|
|
@@ -104,7 +103,6 @@ export function TriggerFields(
|
|
|
104
103
|
disabled={disabled}
|
|
105
104
|
onEdit={(v) => onEdit('toText', v)}
|
|
106
105
|
/>
|
|
107
|
-
<p className="dne-groupLabel">{t('triggerGroup')}</p>
|
|
108
106
|
{TRIGGER_KEYS.map((key) => (
|
|
109
107
|
<CheckRow
|
|
110
108
|
prefix="dne"
|
|
@@ -116,25 +114,23 @@ export function TriggerFields(
|
|
|
116
114
|
onEdit={(v) => props.onTrigger(key, v)}
|
|
117
115
|
/>
|
|
118
116
|
))}
|
|
119
|
-
<
|
|
117
|
+
<NumberField
|
|
120
118
|
prefix="dne"
|
|
121
119
|
id="dne-debounce"
|
|
122
120
|
label={t('idleDebounceMs')}
|
|
123
121
|
hint={t('idleDebounceMsHint')}
|
|
124
122
|
value={draft.idleDebounceMs}
|
|
125
|
-
numeric
|
|
126
123
|
disabled={disabled}
|
|
127
124
|
invalid={!isPositiveInt(draft.idleDebounceMs)}
|
|
128
125
|
invalidLabel={t('invalidNumber')}
|
|
129
126
|
onEdit={(v) => onEdit('idleDebounceMs', v)}
|
|
130
127
|
/>
|
|
131
|
-
<
|
|
128
|
+
<NumberField
|
|
132
129
|
prefix="dne"
|
|
133
130
|
id="dne-maxchars"
|
|
134
131
|
label={t('maxBodyChars')}
|
|
135
132
|
hint={t('maxBodyCharsHint')}
|
|
136
133
|
value={draft.maxBodyChars}
|
|
137
|
-
numeric
|
|
138
134
|
disabled={disabled}
|
|
139
135
|
invalid={!isPositiveInt(draft.maxBodyChars) || Number(draft.maxBodyChars) < 200}
|
|
140
136
|
invalidLabel={t('invalidNumber')}
|
package/src/client/i18n.ts
CHANGED
|
@@ -11,7 +11,7 @@ const ownZh = {
|
|
|
11
11
|
title: '邮件通知',
|
|
12
12
|
description: '任务结束、等待决策或出错时向指定邮箱发送邮件。',
|
|
13
13
|
summaryLine: '任务结束、等待决策或出错时向指定邮箱发送邮件。',
|
|
14
|
-
|
|
14
|
+
smtpGroup: 'SMTP 连接',
|
|
15
15
|
host: 'SMTP 服务器',
|
|
16
16
|
hostHint: '如 smtp.qq.com、smtp.163.com。',
|
|
17
17
|
port: '端口',
|
|
@@ -50,7 +50,7 @@ const ownEn = {
|
|
|
50
50
|
title: 'Email notifications',
|
|
51
51
|
description: 'Email a mailbox when a task finishes, awaits a decision, or fails.',
|
|
52
52
|
summaryLine: 'Email a mailbox when a task finishes, awaits a decision, or fails.',
|
|
53
|
-
|
|
53
|
+
smtpGroup: 'SMTP connection',
|
|
54
54
|
host: 'SMTP host',
|
|
55
55
|
hostHint: 'e.g. smtp.qq.com, smtp.gmail.com.',
|
|
56
56
|
port: 'Port',
|
package/src/config.ts
CHANGED
|
@@ -39,7 +39,6 @@ const TriggerSchema = z.object({
|
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
export const Config = z.object({
|
|
42
|
-
enabled: z.boolean().description('总开关').default(false).volatile(),
|
|
43
42
|
smtp: SmtpSchema.default({
|
|
44
43
|
host: '',
|
|
45
44
|
port: 465,
|
|
@@ -71,7 +70,10 @@ export type NotifyEmailConfigFields = Schemastery.TypeT<typeof Config>
|
|
|
71
70
|
export type NotifyEmailConfig = UnwrapVolatile<NotifyEmailConfigFields>
|
|
72
71
|
export type TriggerToggles = NotifyEmailConfig['triggers']
|
|
73
72
|
|
|
74
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* 是否具备发信条件:SMTP 主机/发件人/收件人齐备。
|
|
75
|
+
* 整行开关由官方行开关承担(profile patch `disabled`),配置内不再有第二套总开关。
|
|
76
|
+
*/
|
|
75
77
|
export function isDeliverable(cfg: NotifyEmailConfig): boolean {
|
|
76
|
-
return cfg.
|
|
78
|
+
return cfg.smtp.host.length > 0 && cfg.smtp.from.length > 0 && cfg.to.length > 0
|
|
77
79
|
}
|
package/src/mailer.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface MailMessage {
|
|
|
19
19
|
|
|
20
20
|
export interface SendResult {
|
|
21
21
|
ok: boolean
|
|
22
|
-
/** 'sent' | 'dry-run' | '
|
|
22
|
+
/** 'sent' | 'dry-run' | 'incomplete' | 错误消息。 */
|
|
23
23
|
detail: string
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -102,13 +102,9 @@ export class Mailer {
|
|
|
102
102
|
this.credentials = credentials
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
* @param force 为 true 时跳过 enabled 门禁(配置卡片的「发送测试邮件」),仍需 SMTP 完整。
|
|
108
|
-
*/
|
|
109
|
-
async send(msg: MailMessage, force = false): Promise<SendResult> {
|
|
105
|
+
/** 通知发送:受配置完整性门禁(整行开关由官方行开关承担)。 */
|
|
106
|
+
async send(msg: MailMessage): Promise<SendResult> {
|
|
110
107
|
const cfg = this.resolveConfig()
|
|
111
|
-
if (!force && !cfg.enabled) return this.record(msg, cfg, { ok: false, detail: 'disabled' })
|
|
112
108
|
if (cfg.smtp.host.length === 0 || cfg.smtp.from.length === 0 || cfg.to.length === 0) {
|
|
113
109
|
return this.record(msg, cfg, { ok: false, detail: 'incomplete' })
|
|
114
110
|
}
|
package/src/service.ts
CHANGED
|
@@ -90,18 +90,15 @@ export class NotifyEmailService extends Service {
|
|
|
90
90
|
await this.dispatch('turn-end', info, (t) => t.onTurnEnd?.(info))
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/**
|
|
93
|
+
/** 配置卡片「发送测试邮件」:走与常规通知同一条发送路径。 */
|
|
94
94
|
async sendTest(): Promise<{ ok: boolean; detail: string }> {
|
|
95
|
-
return this.mailer.send(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
},
|
|
100
|
-
true,
|
|
101
|
-
)
|
|
95
|
+
return this.mailer.send({
|
|
96
|
+
subject: '[DSH] 邮件通知测试',
|
|
97
|
+
text: '这是一封来自 dsh notify-email 插件的测试邮件。',
|
|
98
|
+
})
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
/** 通用直发接口(如 lifeboat
|
|
101
|
+
/** 通用直发接口(如 lifeboat 故障告警):走正常完整性门禁。 */
|
|
105
102
|
async sendNotice(notice: {
|
|
106
103
|
subject: string
|
|
107
104
|
text: string
|
|
@@ -128,7 +125,7 @@ export class NotifyEmailService extends Service {
|
|
|
128
125
|
}
|
|
129
126
|
if (notice === undefined) continue
|
|
130
127
|
const result = await this.mailer.send(notice)
|
|
131
|
-
if (!result.ok && result.detail !== '
|
|
128
|
+
if (!result.ok && result.detail !== 'incomplete') {
|
|
132
129
|
this.ctx
|
|
133
130
|
.logger('notify-email')
|
|
134
131
|
.warn(`deliver failed via trigger ${trigger.id}: ${result.detail}`)
|