@dsh-plus/notify-email 0.1.11 → 0.1.13
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 +44 -31
- package/lib/index.js +10 -9
- package/package.json +16 -17
- package/src/client/api.ts +2 -2
- package/src/client/card.tsx +11 -17
- package/src/client/client.ts +16 -14
- package/src/config-api.ts +2 -2
- package/src/config.ts +2 -3
- package/src/ns.ts +4 -3
- package/src/service.ts +11 -7
package/lib/client.js
CHANGED
|
@@ -196,14 +196,15 @@ window.__ModuleLoader__.load({
|
|
|
196
196
|
//#endregion
|
|
197
197
|
//#region ../shared/src/client/scope.ts
|
|
198
198
|
/**
|
|
199
|
-
*
|
|
199
|
+
* remote.settings 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
200
200
|
* 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
|
|
201
201
|
* 刷新:remote `settings/document-updated`(按命名空间过滤)与
|
|
202
202
|
* `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
|
|
203
203
|
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
204
204
|
* effect 归属调用方 fiber(label 需含插件名以便诊断)。
|
|
205
205
|
*/
|
|
206
|
-
function
|
|
206
|
+
function createSettingsScope(c, ns, label) {
|
|
207
|
+
const remote = c.get("remote");
|
|
207
208
|
let state = {
|
|
208
209
|
status: "loading",
|
|
209
210
|
value: void 0,
|
|
@@ -218,36 +219,37 @@ window.__ModuleLoader__.load({
|
|
|
218
219
|
};
|
|
219
220
|
const load = async () => {
|
|
220
221
|
const gen = ++generation;
|
|
221
|
-
let
|
|
222
|
+
let view;
|
|
222
223
|
try {
|
|
223
|
-
response = await
|
|
224
|
+
const response = await remote.settings.describe();
|
|
225
|
+
if (!response.ok) return;
|
|
226
|
+
view = response.value;
|
|
224
227
|
} catch {
|
|
225
228
|
return;
|
|
226
229
|
}
|
|
227
|
-
if (gen !== generation
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
if (view === void 0) {
|
|
230
|
+
if (gen !== generation) return;
|
|
231
|
+
const namespace = view.namespaces.find((candidate) => candidate.ns === ns);
|
|
232
|
+
if (namespace === void 0) {
|
|
231
233
|
publish({
|
|
232
234
|
status: "unavailable",
|
|
233
235
|
value: void 0,
|
|
234
236
|
revision: void 0,
|
|
235
|
-
writable
|
|
237
|
+
writable: view.writable
|
|
236
238
|
});
|
|
237
239
|
return;
|
|
238
240
|
}
|
|
239
241
|
publish({
|
|
240
242
|
status: "ready",
|
|
241
|
-
value:
|
|
242
|
-
revision:
|
|
243
|
-
writable
|
|
243
|
+
value: namespace.value,
|
|
244
|
+
revision: namespace.revision,
|
|
245
|
+
writable: view.writable
|
|
244
246
|
});
|
|
245
247
|
};
|
|
246
248
|
const refresh = (namespace) => {
|
|
247
249
|
if (namespace !== void 0 && namespace !== ns) return;
|
|
248
250
|
load();
|
|
249
251
|
};
|
|
250
|
-
const disposers = [
|
|
252
|
+
const disposers = [remote.$on("settings/document-updated", refresh), c.on("connection/reset", () => {
|
|
251
253
|
load();
|
|
252
254
|
})];
|
|
253
255
|
c.effect(() => () => {
|
|
@@ -265,6 +267,20 @@ window.__ModuleLoader__.load({
|
|
|
265
267
|
load
|
|
266
268
|
};
|
|
267
269
|
}
|
|
270
|
+
/** 以 remote.settings 构造命名空间绑定的 api 面。 */
|
|
271
|
+
function createNamespaceApi(remote, ns) {
|
|
272
|
+
return {
|
|
273
|
+
update: async (patch, expectedRevision) => {
|
|
274
|
+
const response = await remote.update(ns, patch, expectedRevision);
|
|
275
|
+
if (!response.ok) throw new Error(response.error.message ?? "settings update failed");
|
|
276
|
+
},
|
|
277
|
+
describe: async () => {
|
|
278
|
+
const response = await remote.describe();
|
|
279
|
+
if (!response.ok) throw new Error(response.error.message ?? "settings describe failed");
|
|
280
|
+
return response.value;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
268
284
|
//#endregion
|
|
269
285
|
//#region ../shared/src/client/styles.ts
|
|
270
286
|
/**
|
|
@@ -369,9 +385,10 @@ ${extra}
|
|
|
369
385
|
//#region src/ns.ts
|
|
370
386
|
/**
|
|
371
387
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
372
|
-
*
|
|
373
|
-
* settings.plugin.item keyed 槽位的 key ——
|
|
374
|
-
*
|
|
388
|
+
* 服务端直接以字面量注册(0.1.2-alpha.2 起编译期校验命名空间语法,见
|
|
389
|
+
* config.ts),浏览器半将其作为 settings.plugin.item keyed 槽位的 key ——
|
|
390
|
+
* 同一字面量两处共用,防止漂移。
|
|
391
|
+
* 契约:该槽位按「卡片编辑的 settings 命名空间」作 key 分发,
|
|
375
392
|
* 官方配置页只渲染 key 命中 Host 已注册命名空间的卡片。
|
|
376
393
|
* @module notify-email/ns
|
|
377
394
|
*/
|
|
@@ -397,7 +414,8 @@ ${extra}
|
|
|
397
414
|
* 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/TextField/CheckRow),
|
|
398
415
|
* 本文件只保留业务字段与保存/测试逻辑。交互对齐官方卡片:折叠/展开、
|
|
399
416
|
* staged draft、未保存标记、保存/放弃;另加「发送测试邮件」。
|
|
400
|
-
*
|
|
417
|
+
* 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
|
|
418
|
+
* 已移除):value 为 schema 解析后的脱敏视图
|
|
401
419
|
* (smtp.pass 不出现,passConfigured 由 describe 的 secrets 探测),
|
|
402
420
|
* 保存经 settings.update 深合并(空 pass 剔除 = 保持不变)。
|
|
403
421
|
* @module notify-email/client/card
|
|
@@ -462,10 +480,10 @@ ${extra}
|
|
|
462
480
|
}, [value, draft]);
|
|
463
481
|
(0, react.useEffect)(() => {
|
|
464
482
|
let alive = true;
|
|
465
|
-
api.describe(
|
|
466
|
-
if (!alive
|
|
467
|
-
const
|
|
468
|
-
setPassConfigured(
|
|
483
|
+
api.describe().then((view) => {
|
|
484
|
+
if (!alive) return;
|
|
485
|
+
const ns = view.namespaces.find((candidate) => candidate.ns === SETTINGS_NS);
|
|
486
|
+
setPassConfigured(ns?.secrets.some((secret) => secret.path.join(".") === "smtp.pass" && secret.set) ?? false);
|
|
469
487
|
}).catch(() => {});
|
|
470
488
|
return () => {
|
|
471
489
|
alive = false;
|
|
@@ -498,12 +516,7 @@ ${extra}
|
|
|
498
516
|
const patch = toPatch(draft);
|
|
499
517
|
if (patch["smtp"]["pass"] === "") delete patch["smtp"]["pass"];
|
|
500
518
|
const revision = scope.getSnapshot().revision;
|
|
501
|
-
api.update({
|
|
502
|
-
ns: SETTINGS_NS,
|
|
503
|
-
patch,
|
|
504
|
-
...revision !== void 0 ? { expectedRevision: revision } : {}
|
|
505
|
-
}).then(async (response) => {
|
|
506
|
-
if (!response.result.ok) throw new Error(response.result.error?.message ?? t("saveFailed"));
|
|
519
|
+
api.update(patch, revision).then(async () => {
|
|
507
520
|
await scope.load();
|
|
508
521
|
const next = scope.getSnapshot().value;
|
|
509
522
|
if (next !== void 0) setDraft(draftFromValue(next));
|
|
@@ -793,8 +806,8 @@ ${extra}
|
|
|
793
806
|
const inject = [
|
|
794
807
|
"slots",
|
|
795
808
|
"locale",
|
|
796
|
-
"
|
|
797
|
-
"remote"
|
|
809
|
+
"remote",
|
|
810
|
+
"remote.settings"
|
|
798
811
|
];
|
|
799
812
|
function apply(ctx) {
|
|
800
813
|
const c = ctx;
|
|
@@ -806,8 +819,8 @@ ${extra}
|
|
|
806
819
|
zh,
|
|
807
820
|
en
|
|
808
821
|
}), "notify-email: locale");
|
|
809
|
-
const
|
|
810
|
-
const
|
|
822
|
+
const scope = createSettingsScope(c, SETTINGS_NS, "notify-email: settings scope");
|
|
823
|
+
const api = createNamespaceApi(c.get("remote").settings, SETTINGS_NS);
|
|
811
824
|
c.slots.inject("settings.plugin.item", () => c.slots.register({
|
|
812
825
|
name: "settings.plugin.item",
|
|
813
826
|
key: SETTINGS_NS,
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
1
|
import z from "@deepseek-ai/schemastery";
|
|
3
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
4
3
|
import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
|
|
@@ -13,8 +12,8 @@ import nodemailer from "nodemailer";
|
|
|
13
12
|
* 共用同一 schemastery schema。SMTP 密码标记 secret 角色,任何读取通道不得回传。
|
|
14
13
|
* @module notify-email/config
|
|
15
14
|
*/
|
|
16
|
-
/** settings
|
|
17
|
-
const SETTINGS_NS =
|
|
15
|
+
/** settings 命名空间(字面量即合法命名空间,0.1.2-alpha.2 起编译期校验;webui 配置卡片与插件运行期读取同一份)。 */
|
|
16
|
+
const SETTINGS_NS = "dsh-plus-notify-email";
|
|
18
17
|
const SmtpSchema = z.object({
|
|
19
18
|
host: z.string().description("SMTP 服务器主机名").default(""),
|
|
20
19
|
port: z.natural().max(65535).description("SMTP 端口").default(465),
|
|
@@ -409,7 +408,7 @@ function installTurnEndWatcher(ctx, service, resolveConfig) {
|
|
|
409
408
|
//#region src/service.ts
|
|
410
409
|
/**
|
|
411
410
|
* NotifyEmailService:通知编排中枢。
|
|
412
|
-
* - 配置:
|
|
411
|
+
* - 配置:settings.installSection 接入 settings 用户层($DSH_HOME/settings.yaml,热生效),
|
|
413
412
|
* 无 settings provider 时退化为 cordis 行级 config。
|
|
414
413
|
* - 触发器:registerTrigger 是第三方插件的扩展接口;内置官方适配器同路径注册。
|
|
415
414
|
* - 投递:首个非空 EmailNotice 经 Mailer 发送;单个触发器抛错不影响其余。
|
|
@@ -423,11 +422,13 @@ var NotifyEmailService = class extends Service {
|
|
|
423
422
|
constructor(ctx, config) {
|
|
424
423
|
super(ctx, "notifyEmail");
|
|
425
424
|
this.current = () => config;
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
425
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
426
|
+
settingsCtx.settings.installSection(ctx, SETTINGS_NS, Config, config, {
|
|
427
|
+
setSource: (source) => {
|
|
428
|
+
this.current = source;
|
|
429
|
+
},
|
|
430
|
+
onChange: () => {}
|
|
431
|
+
});
|
|
431
432
|
});
|
|
432
433
|
const logger = ctx.logger("notify-email");
|
|
433
434
|
const audit = createJsonlAuditSink(dshHomePath("logs", "notify-email.jsonl"), (message) => logger.warn(`audit write failed: ${message}`));
|
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.13",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: 任务结束/等待决策/出错时向指定邮箱发送邮件通知",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,36 +23,35 @@
|
|
|
23
23
|
"dsh": {
|
|
24
24
|
"client": {
|
|
25
25
|
"inject": [
|
|
26
|
-
"@deepseek-ai/dsh-client-
|
|
26
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
27
27
|
"@deepseek-ai/dsh-client-locale",
|
|
28
|
-
"@deepseek-ai/dsh-client-connection",
|
|
29
28
|
"@deepseek-ai/dsh-api-remotes"
|
|
30
29
|
],
|
|
31
30
|
"platform": "web"
|
|
32
31
|
}
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
|
-
"nodemailer": "9.0.
|
|
36
|
-
"@dsh-plus/shared": "0.1.
|
|
34
|
+
"nodemailer": "9.0.6",
|
|
35
|
+
"@dsh-plus/shared": "0.1.7"
|
|
37
36
|
},
|
|
38
37
|
"peerDependencies": {
|
|
39
38
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
40
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
41
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
39
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.2-alpha.2",
|
|
40
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
|
|
42
41
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
45
|
-
"@deepseek-ai/cordis": "4.0.
|
|
46
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
47
|
-
"@deepseek-ai/dsh-home-paths": "0.1.
|
|
48
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
49
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
50
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
51
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
52
|
-
"@deepseek-ai/dsh-tools": "0.1.
|
|
53
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
44
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
45
|
+
"@deepseek-ai/dsh-agent": "0.1.2-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-home-paths": "0.1.2-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-session": "0.1.2-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-tools": "0.1.2-alpha.2",
|
|
52
|
+
"@deepseek-ai/schemastery": "3.18.2",
|
|
54
53
|
"@types/nodemailer": "8.0.1",
|
|
55
|
-
"@types/react": "~18.3.
|
|
54
|
+
"@types/react": "~18.3.31",
|
|
56
55
|
"react": "^18.2.0"
|
|
57
56
|
},
|
|
58
57
|
"publishConfig": {
|
package/src/client/api.ts
CHANGED
package/src/client/card.tsx
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/TextField/CheckRow),
|
|
4
4
|
* 本文件只保留业务字段与保存/测试逻辑。交互对齐官方卡片:折叠/展开、
|
|
5
5
|
* staged draft、未保存标记、保存/放弃;另加「发送测试邮件」。
|
|
6
|
-
*
|
|
6
|
+
* 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
|
|
7
|
+
* 已移除):value 为 schema 解析后的脱敏视图
|
|
7
8
|
* (smtp.pass 不出现,passConfigured 由 describe 的 secrets 探测),
|
|
8
9
|
* 保存经 settings.update 深合并(空 pass 剔除 = 保持不变)。
|
|
9
10
|
* @module notify-email/client/card
|
|
@@ -14,8 +15,8 @@ import {
|
|
|
14
15
|
type CardStatusState,
|
|
15
16
|
CheckRow,
|
|
16
17
|
IDLE_STATUS,
|
|
18
|
+
type NamespaceSettingsApi,
|
|
17
19
|
type Scope,
|
|
18
|
-
type SettingsApi,
|
|
19
20
|
TextField,
|
|
20
21
|
} from '@dsh-plus/shared/client'
|
|
21
22
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
@@ -25,7 +26,7 @@ import { sendTest } from './api.ts'
|
|
|
25
26
|
export interface CardProps {
|
|
26
27
|
t(key: string): string
|
|
27
28
|
scope: Scope
|
|
28
|
-
api:
|
|
29
|
+
api: NamespaceSettingsApi
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/** settings 命名空间的脱敏解析值(smtp.pass 被脱敏剥除)。 */
|
|
@@ -136,12 +137,12 @@ export function NotifyEmailCard(props: CardProps): ReactElement | null {
|
|
|
136
137
|
useEffect(() => {
|
|
137
138
|
let alive = true
|
|
138
139
|
api
|
|
139
|
-
.describe(
|
|
140
|
-
.then((
|
|
141
|
-
if (!alive
|
|
142
|
-
const
|
|
140
|
+
.describe()
|
|
141
|
+
.then((view) => {
|
|
142
|
+
if (!alive) return
|
|
143
|
+
const ns = view.namespaces.find((candidate) => candidate.ns === SETTINGS_NS)
|
|
143
144
|
setPassConfigured(
|
|
144
|
-
|
|
145
|
+
ns?.secrets.some((secret) => secret.path.join('.') === 'smtp.pass' && secret.set) ??
|
|
145
146
|
false,
|
|
146
147
|
)
|
|
147
148
|
})
|
|
@@ -190,15 +191,8 @@ export function NotifyEmailCard(props: CardProps): ReactElement | null {
|
|
|
190
191
|
}
|
|
191
192
|
const revision = scope.getSnapshot().revision
|
|
192
193
|
api
|
|
193
|
-
.update(
|
|
194
|
-
|
|
195
|
-
patch,
|
|
196
|
-
...(revision !== undefined ? { expectedRevision: revision } : {}),
|
|
197
|
-
})
|
|
198
|
-
.then(async (response) => {
|
|
199
|
-
if (!response.result.ok) {
|
|
200
|
-
throw new Error(response.result.error?.message ?? t('saveFailed'))
|
|
201
|
-
}
|
|
194
|
+
.update(patch, revision)
|
|
195
|
+
.then(async () => {
|
|
202
196
|
await scope.load()
|
|
203
197
|
const next = scope.getSnapshot().value as ConfigValue | undefined
|
|
204
198
|
if (next !== undefined) setDraft(draftFromValue(next))
|
package/src/client/client.ts
CHANGED
|
@@ -3,18 +3,24 @@
|
|
|
3
3
|
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
4
4
|
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
5
5
|
*
|
|
6
|
-
* 类型说明:浏览器半只用到 slots/locale/
|
|
6
|
+
* 类型说明:浏览器半只用到 slots/locale/remote 的很窄一面,
|
|
7
7
|
* scope 与基础控件走 @dsh-plus/shared/client 套件(收编自本插件原实现)。
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 该插槽为 keyed 槽位(0.1.2-alpha.2 复核不变):key 必须是卡片编辑的 settings
|
|
9
|
+
* 命名空间(即服务端注册的同一字面量,见 ../ns.ts),
|
|
10
10
|
* 官方配置页按此 key 与 Host 已注册命名空间配对分发。
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
|
|
12
|
+
* 已移除;不复用 settingsScope 服务——非 loopback 页面下它固定 memory 模式无数据);
|
|
13
13
|
* 自定义端点仅剩「发送测试邮件」(api.ts)。
|
|
14
14
|
* @module @dsh-plus/notify-email/client
|
|
15
15
|
*/
|
|
16
16
|
import type { Context } from '@deepseek-ai/cordis'
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
cardCss,
|
|
19
|
+
createNamespaceApi,
|
|
20
|
+
createSettingsScope,
|
|
21
|
+
injectCardStyle,
|
|
22
|
+
type SettingsRemoteFace,
|
|
23
|
+
} from '@dsh-plus/shared/client'
|
|
18
24
|
import { SETTINGS_NS } from '../ns.ts'
|
|
19
25
|
import { NotifyEmailCard } from './card.tsx'
|
|
20
26
|
import { en, NS, zh } from './i18n.ts'
|
|
@@ -22,7 +28,7 @@ import { en, NS, zh } from './i18n.ts'
|
|
|
22
28
|
export const name = 'dsh-plus-notify-email'
|
|
23
29
|
|
|
24
30
|
/** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
|
|
25
|
-
export const inject = ['slots', 'locale', '
|
|
31
|
+
export const inject = ['slots', 'locale', 'remote', 'remote.settings'] as const
|
|
26
32
|
|
|
27
33
|
interface SlotsLike {
|
|
28
34
|
inject(key: string, callback: () => unknown): unknown
|
|
@@ -35,17 +41,13 @@ interface LocaleLike {
|
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
interface RemoteLike {
|
|
44
|
+
settings: SettingsRemoteFace
|
|
38
45
|
$on(event: string, listener: (payload?: unknown) => void): () => void
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
interface ConnectionLike {
|
|
42
|
-
api: { settings: Parameters<typeof createApiScope>[0] }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
48
|
interface ClientContext {
|
|
46
49
|
slots: SlotsLike
|
|
47
50
|
locale: LocaleLike
|
|
48
|
-
get(key: 'connection'): ConnectionLike
|
|
49
51
|
get(key: 'remote'): RemoteLike
|
|
50
52
|
on(event: string, listener: () => void): () => void
|
|
51
53
|
effect(execute: () => () => void, label?: string): unknown
|
|
@@ -61,8 +63,8 @@ export function apply(ctx: Context): void {
|
|
|
61
63
|
'notify-email: style',
|
|
62
64
|
)
|
|
63
65
|
c.effect(() => c.locale.register(NS, { zh, en }), 'notify-email: locale')
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
+
const scope = createSettingsScope(c, SETTINGS_NS, 'notify-email: settings scope')
|
|
67
|
+
const api = createNamespaceApi(c.get('remote').settings, SETTINGS_NS)
|
|
66
68
|
c.slots.inject('settings.plugin.item', () =>
|
|
67
69
|
c.slots.register(
|
|
68
70
|
{
|
package/src/config-api.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 自定义端点 HTTP
|
|
3
|
-
*
|
|
2
|
+
* 自定义端点 HTTP 通道:配置读写走官方 settings 命名空间(0.1.2-alpha.1 起
|
|
3
|
+
* 第三方命名空间全量开放),这里只保留官方传输没有的「发送测试邮件」端点。
|
|
4
4
|
* 仅监听 dsh web 同源(webserver 默认 127.0.0.1),与 GUI 其余面同等暴露面。
|
|
5
5
|
* @module notify-email/config-api
|
|
6
6
|
*/
|
package/src/config.ts
CHANGED
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
* @module notify-email/config
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
9
8
|
import z from '@deepseek-ai/schemastery'
|
|
10
9
|
|
|
11
10
|
import { SETTINGS_NS as NS_LITERAL } from './ns.ts'
|
|
12
11
|
|
|
13
|
-
/** settings
|
|
14
|
-
export const SETTINGS_NS =
|
|
12
|
+
/** settings 命名空间(字面量即合法命名空间,0.1.2-alpha.2 起编译期校验;webui 配置卡片与插件运行期读取同一份)。 */
|
|
13
|
+
export const SETTINGS_NS = NS_LITERAL
|
|
15
14
|
|
|
16
15
|
const SmtpSchema = z.object({
|
|
17
16
|
host: z.string().description('SMTP 服务器主机名').default(''),
|
package/src/ns.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
3
|
-
*
|
|
4
|
-
* settings.plugin.item keyed 槽位的 key ——
|
|
5
|
-
*
|
|
3
|
+
* 服务端直接以字面量注册(0.1.2-alpha.2 起编译期校验命名空间语法,见
|
|
4
|
+
* config.ts),浏览器半将其作为 settings.plugin.item keyed 槽位的 key ——
|
|
5
|
+
* 同一字面量两处共用,防止漂移。
|
|
6
|
+
* 契约:该槽位按「卡片编辑的 settings 命名空间」作 key 分发,
|
|
6
7
|
* 官方配置页只渲染 key 命中 Host 已注册命名空间的卡片。
|
|
7
8
|
* @module notify-email/ns
|
|
8
9
|
*/
|
package/src/service.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NotifyEmailService:通知编排中枢。
|
|
3
|
-
* - 配置:
|
|
3
|
+
* - 配置:settings.installSection 接入 settings 用户层($DSH_HOME/settings.yaml,热生效),
|
|
4
4
|
* 无 settings provider 时退化为 cordis 行级 config。
|
|
5
5
|
* - 触发器:registerTrigger 是第三方插件的扩展接口;内置官方适配器同路径注册。
|
|
6
6
|
* - 投递:首个非空 EmailNotice 经 Mailer 发送;单个触发器抛错不影响其余。
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Context, Service } from '@deepseek-ai/cordis'
|
|
10
10
|
import { dshHomePath } from '@deepseek-ai/dsh-home-paths'
|
|
11
|
-
import {
|
|
11
|
+
import type {} from '@deepseek-ai/dsh-settings'
|
|
12
12
|
import { createJsonlAuditSink } from './audit.ts'
|
|
13
13
|
import { Config, type NotifyEmailConfig, SETTINGS_NS } from './config.ts'
|
|
14
14
|
import { registerTestApi } from './config-api.ts'
|
|
@@ -34,11 +34,15 @@ export class NotifyEmailService extends Service {
|
|
|
34
34
|
constructor(ctx: Context, config: NotifyEmailConfig) {
|
|
35
35
|
super(ctx, 'notifyEmail')
|
|
36
36
|
this.current = () => config
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
// 官方 installSection 范式(0.1.2-alpha.2):settings 在时以行级 config 为
|
|
38
|
+
// base 注册用户层,缺席/detach 时回落行级 config。
|
|
39
|
+
ctx.inject(['settings'], (settingsCtx) => {
|
|
40
|
+
settingsCtx.settings.installSection(ctx, SETTINGS_NS, Config, config, {
|
|
41
|
+
setSource: (source) => {
|
|
42
|
+
this.current = source
|
|
43
|
+
},
|
|
44
|
+
onChange: () => {},
|
|
45
|
+
})
|
|
42
46
|
})
|
|
43
47
|
const logger = ctx.logger('notify-email')
|
|
44
48
|
const audit = createJsonlAuditSink(dshHomePath('logs', 'notify-email.jsonl'), (message) =>
|