@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/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
- /** 配置卡片「发送测试邮件」:绕过 enabled 门禁,仍需 SMTP 完整。 */
135
+ /** 配置卡片「发送测试邮件」:走与常规通知同一条发送路径。 */
138
136
  sendTest(): Promise<{
139
137
  ok: boolean;
140
138
  detail: string;
141
139
  }>;
142
- /** 通用直发接口(如 lifeboat 故障告警):走正常 enabled/完整性门禁。 */
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
- * 常规通知发送:受 enabled 与配置完整性门禁。
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
- /** 配置卡片「发送测试邮件」:绕过 enabled 门禁,仍需 SMTP 完整。 */
475
+ /** 配置卡片「发送测试邮件」:走与常规通知同一条发送路径。 */
484
476
  async sendTest() {
485
477
  return this.mailer.send({
486
478
  subject: "[DSH] 邮件通知测试",
487
479
  text: "这是一封来自 dsh notify-email 插件的测试邮件。"
488
- }, true);
480
+ });
489
481
  }
490
- /** 通用直发接口(如 lifeboat 故障告警):走正常 enabled/完整性门禁。 */
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 !== "disabled" && result.detail !== "incomplete") this.ctx.logger("notify-email").warn(`deliver failed via trigger ${trigger.id}: ${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.49",
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.43"
35
+ "@dsh-plus/shared": "0.1.44"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.5",
@@ -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 套件(CardChrome/TextField/CheckRow),
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
- CheckRow,
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
- <CardChrome
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
- <CheckRow
237
- prefix="dne"
238
- id="dne-enabled"
239
- label={t('enabled')}
240
- checked={draft.enabled}
241
- disabled={disabled}
242
- onEdit={(v) => edit('enabled', v)}
243
- />
244
- <SmtpFields
245
- t={t}
246
- draft={draft}
247
- disabled={disabled}
248
- passConfigured={passConfigured}
249
- onEdit={edit}
250
- />
251
- <TriggerFields
252
- t={t}
253
- draft={draft}
254
- disabled={disabled}
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
  }
@@ -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),
@@ -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
- <TextField
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
- <TextField
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
- <TextField
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')}
@@ -11,7 +11,7 @@ const ownZh = {
11
11
  title: '邮件通知',
12
12
  description: '任务结束、等待决策或出错时向指定邮箱发送邮件。',
13
13
  summaryLine: '任务结束、等待决策或出错时向指定邮箱发送邮件。',
14
- enabled: '启用通知',
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
- enabled: 'Enable notifications',
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
- /** 是否具备发信条件(enabled 之外的最小完整性)。 */
73
+ /**
74
+ * 是否具备发信条件:SMTP 主机/发件人/收件人齐备。
75
+ * 整行开关由官方行开关承担(profile patch `disabled`),配置内不再有第二套总开关。
76
+ */
75
77
  export function isDeliverable(cfg: NotifyEmailConfig): boolean {
76
- return cfg.enabled && cfg.smtp.host.length > 0 && cfg.smtp.from.length > 0 && cfg.to.length > 0
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' | 'disabled' | 'incomplete' | 错误消息。 */
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
- * 常规通知发送:受 enabled 与配置完整性门禁。
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
- /** 配置卡片「发送测试邮件」:绕过 enabled 门禁,仍需 SMTP 完整。 */
93
+ /** 配置卡片「发送测试邮件」:走与常规通知同一条发送路径。 */
94
94
  async sendTest(): Promise<{ ok: boolean; detail: string }> {
95
- return this.mailer.send(
96
- {
97
- subject: '[DSH] 邮件通知测试',
98
- text: '这是一封来自 dsh notify-email 插件的测试邮件。',
99
- },
100
- true,
101
- )
95
+ return this.mailer.send({
96
+ subject: '[DSH] 邮件通知测试',
97
+ text: '这是一封来自 dsh notify-email 插件的测试邮件。',
98
+ })
102
99
  }
103
100
 
104
- /** 通用直发接口(如 lifeboat 故障告警):走正常 enabled/完整性门禁。 */
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 !== 'disabled' && result.detail !== 'incomplete') {
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}`)