dsh-mail-sender 0.1.2 → 0.1.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/cordis.patch.yml +3 -16
- package/lib/index.d.ts +74 -13
- package/lib/index.js +68 -54
- package/package.json +12 -7
- package/readme.md +95 -4
package/cordis.patch.yml
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
imap:
|
|
5
|
-
host: imap.example.com
|
|
6
|
-
port: 993
|
|
7
|
-
secure: true
|
|
8
|
-
user: ""
|
|
9
|
-
password: ""
|
|
10
|
-
smtp:
|
|
11
|
-
host: smtp.example.com
|
|
12
|
-
port: 465
|
|
13
|
-
secure: true
|
|
14
|
-
user: ""
|
|
15
|
-
password: ""
|
|
16
|
-
allowedAddresses: []
|
|
1
|
+
- insert:
|
|
2
|
+
- id: dsh-mail-sender
|
|
3
|
+
name: dsh-mail-sender
|
package/lib/index.d.ts
CHANGED
|
@@ -2,23 +2,84 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
2
2
|
import Schema from '@deepseek-ai/schemastery';
|
|
3
3
|
export declare const name = "dsh-mail-sender";
|
|
4
4
|
export declare const inject: string[];
|
|
5
|
+
/** schemastery `.volatile()` 字段解析后的稳定引用 */
|
|
6
|
+
interface Ref<T> {
|
|
7
|
+
get(): T;
|
|
8
|
+
}
|
|
5
9
|
export interface Config {
|
|
6
10
|
imap: {
|
|
7
|
-
host: string
|
|
8
|
-
port: number
|
|
9
|
-
secure: boolean
|
|
10
|
-
user: string
|
|
11
|
-
password: string
|
|
11
|
+
host: Ref<string>;
|
|
12
|
+
port: Ref<number>;
|
|
13
|
+
secure: Ref<boolean>;
|
|
14
|
+
user: Ref<string>;
|
|
15
|
+
password: Ref<string>;
|
|
12
16
|
};
|
|
13
17
|
smtp: {
|
|
14
|
-
host: string
|
|
15
|
-
port: number
|
|
16
|
-
secure: boolean
|
|
17
|
-
user: string
|
|
18
|
-
password: string
|
|
18
|
+
host: Ref<string>;
|
|
19
|
+
port: Ref<number>;
|
|
20
|
+
secure: Ref<boolean>;
|
|
21
|
+
user: Ref<string>;
|
|
22
|
+
password: Ref<string>;
|
|
19
23
|
};
|
|
20
|
-
/**
|
|
21
|
-
allowedAddresses: string[]
|
|
24
|
+
/** 入站+出站合并白名单 */
|
|
25
|
+
allowedAddresses: Ref<string[]>;
|
|
22
26
|
}
|
|
23
|
-
export declare const Config: Schema<
|
|
27
|
+
export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
28
|
+
imap: Schema<Schemastery.ObjectS<NoInfer<{
|
|
29
|
+
host: Schema<string, string, "volatile-defined">;
|
|
30
|
+
port: Schema<number, number, "volatile-defined">;
|
|
31
|
+
secure: Schema<boolean, boolean, "volatile-defined">;
|
|
32
|
+
user: Schema<string, string, "volatile-defined">;
|
|
33
|
+
password: Schema<string, string, "volatile-defined">;
|
|
34
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
35
|
+
host: Schema<string, string, "volatile-defined">;
|
|
36
|
+
port: Schema<number, number, "volatile-defined">;
|
|
37
|
+
secure: Schema<boolean, boolean, "volatile-defined">;
|
|
38
|
+
user: Schema<string, string, "volatile-defined">;
|
|
39
|
+
password: Schema<string, string, "volatile-defined">;
|
|
40
|
+
}>>, "plain">;
|
|
41
|
+
smtp: Schema<Schemastery.ObjectS<NoInfer<{
|
|
42
|
+
host: Schema<string, string, "volatile-defined">;
|
|
43
|
+
port: Schema<number, number, "volatile-defined">;
|
|
44
|
+
secure: Schema<boolean, boolean, "volatile-defined">;
|
|
45
|
+
user: Schema<string, string, "volatile-defined">;
|
|
46
|
+
password: Schema<string, string, "volatile-defined">;
|
|
47
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
48
|
+
host: Schema<string, string, "volatile-defined">;
|
|
49
|
+
port: Schema<number, number, "volatile-defined">;
|
|
50
|
+
secure: Schema<boolean, boolean, "volatile-defined">;
|
|
51
|
+
user: Schema<string, string, "volatile-defined">;
|
|
52
|
+
password: Schema<string, string, "volatile-defined">;
|
|
53
|
+
}>>, "plain">;
|
|
54
|
+
allowedAddresses: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
55
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
56
|
+
imap: Schema<Schemastery.ObjectS<NoInfer<{
|
|
57
|
+
host: Schema<string, string, "volatile-defined">;
|
|
58
|
+
port: Schema<number, number, "volatile-defined">;
|
|
59
|
+
secure: Schema<boolean, boolean, "volatile-defined">;
|
|
60
|
+
user: Schema<string, string, "volatile-defined">;
|
|
61
|
+
password: Schema<string, string, "volatile-defined">;
|
|
62
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
63
|
+
host: Schema<string, string, "volatile-defined">;
|
|
64
|
+
port: Schema<number, number, "volatile-defined">;
|
|
65
|
+
secure: Schema<boolean, boolean, "volatile-defined">;
|
|
66
|
+
user: Schema<string, string, "volatile-defined">;
|
|
67
|
+
password: Schema<string, string, "volatile-defined">;
|
|
68
|
+
}>>, "plain">;
|
|
69
|
+
smtp: Schema<Schemastery.ObjectS<NoInfer<{
|
|
70
|
+
host: Schema<string, string, "volatile-defined">;
|
|
71
|
+
port: Schema<number, number, "volatile-defined">;
|
|
72
|
+
secure: Schema<boolean, boolean, "volatile-defined">;
|
|
73
|
+
user: Schema<string, string, "volatile-defined">;
|
|
74
|
+
password: Schema<string, string, "volatile-defined">;
|
|
75
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
76
|
+
host: Schema<string, string, "volatile-defined">;
|
|
77
|
+
port: Schema<number, number, "volatile-defined">;
|
|
78
|
+
secure: Schema<boolean, boolean, "volatile-defined">;
|
|
79
|
+
user: Schema<string, string, "volatile-defined">;
|
|
80
|
+
password: Schema<string, string, "volatile-defined">;
|
|
81
|
+
}>>, "plain">;
|
|
82
|
+
allowedAddresses: Schema<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
83
|
+
}>>, "plain">;
|
|
24
84
|
export declare function apply(ctx: Context, config: Config): void;
|
|
85
|
+
export {};
|
package/lib/index.js
CHANGED
|
@@ -7,56 +7,64 @@ export const name = 'dsh-mail-sender';
|
|
|
7
7
|
export const inject = ['agents'];
|
|
8
8
|
export const Config = Schema.object({
|
|
9
9
|
imap: Schema.object({
|
|
10
|
-
host: Schema.string().
|
|
11
|
-
port: Schema.number().default(993),
|
|
12
|
-
secure: Schema.boolean().default(true),
|
|
13
|
-
user: Schema.string().
|
|
14
|
-
password: Schema.string().
|
|
15
|
-
}),
|
|
10
|
+
host: Schema.string().volatile().default('').description('IMAP 服务器地址'),
|
|
11
|
+
port: Schema.number().volatile().default(993),
|
|
12
|
+
secure: Schema.boolean().volatile().default(true),
|
|
13
|
+
user: Schema.string().volatile().default('').description('IMAP 登录账号'),
|
|
14
|
+
password: Schema.string().volatile().default('').description('IMAP 密码/授权码'),
|
|
15
|
+
}).description('IMAP 收信'),
|
|
16
16
|
smtp: Schema.object({
|
|
17
|
-
host: Schema.string().
|
|
18
|
-
port: Schema.number().default(465),
|
|
19
|
-
secure: Schema.boolean().default(true),
|
|
20
|
-
user: Schema.string().
|
|
21
|
-
password: Schema.string().
|
|
22
|
-
}),
|
|
17
|
+
host: Schema.string().volatile().default('').description('SMTP 服务器地址'),
|
|
18
|
+
port: Schema.number().volatile().default(465),
|
|
19
|
+
secure: Schema.boolean().volatile().default(true),
|
|
20
|
+
user: Schema.string().volatile().default('').description('SMTP 登录账号'),
|
|
21
|
+
password: Schema.string().volatile().default('').description('SMTP 密码/授权码'),
|
|
22
|
+
}).description('SMTP 发信'),
|
|
23
23
|
allowedAddresses: Schema.array(Schema.string())
|
|
24
|
+
.volatile()
|
|
24
25
|
.default([])
|
|
25
26
|
.description('允许与 agent 通信的邮箱地址'),
|
|
26
27
|
});
|
|
27
28
|
export function apply(ctx, config) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
auth: { user: config.smtp.user, pass: config.smtp.password },
|
|
34
|
-
});
|
|
35
|
-
const imap = new ImapFlow({
|
|
36
|
-
host: config.imap.host,
|
|
37
|
-
port: config.imap.port,
|
|
38
|
-
secure: config.imap.secure,
|
|
39
|
-
auth: { user: config.imap.user, pass: config.imap.password },
|
|
40
|
-
logger: false,
|
|
29
|
+
const imapConfig = () => ({
|
|
30
|
+
host: config.imap.host.get(),
|
|
31
|
+
port: config.imap.port.get(),
|
|
32
|
+
secure: config.imap.secure.get(),
|
|
33
|
+
auth: { user: config.imap.user.get(), pass: config.imap.password.get() },
|
|
41
34
|
});
|
|
35
|
+
const imap = new ImapFlow({ ...imapConfig(), logger: false });
|
|
36
|
+
// SMTP 传输按当前配置缓存,设置变更后自动重建
|
|
37
|
+
let smtp;
|
|
38
|
+
let smtpKey = '';
|
|
39
|
+
const getSmtp = () => {
|
|
40
|
+
const conf = {
|
|
41
|
+
host: config.smtp.host.get(),
|
|
42
|
+
port: config.smtp.port.get(),
|
|
43
|
+
secure: config.smtp.secure.get(),
|
|
44
|
+
auth: { user: config.smtp.user.get(), pass: config.smtp.password.get() },
|
|
45
|
+
};
|
|
46
|
+
const key = JSON.stringify(conf);
|
|
47
|
+
if (smtpKey !== key) {
|
|
48
|
+
smtp = nodemailer.createTransport(conf);
|
|
49
|
+
smtpKey = key;
|
|
50
|
+
}
|
|
51
|
+
return smtp;
|
|
52
|
+
};
|
|
42
53
|
// sessionId -> agent handle
|
|
43
54
|
const handles = new Map();
|
|
44
55
|
// sessionId -> 最近一封来信的信息(用于回复)
|
|
45
56
|
const lastMail = new Map();
|
|
46
57
|
// `${sessionId}:${turn}` -> 该 turn 内累积的 assistant 文本
|
|
47
58
|
const turnBuffers = new Map();
|
|
48
|
-
ctx.effect(
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
ctx.effect(() => {
|
|
60
|
+
let closed = false;
|
|
61
|
+
let lock;
|
|
51
62
|
imap.on('exists', async () => {
|
|
52
63
|
try {
|
|
53
64
|
const mb = imap.mailbox;
|
|
54
65
|
if (mb === false)
|
|
55
66
|
return;
|
|
56
|
-
const
|
|
57
|
-
if (!seq)
|
|
58
|
-
return;
|
|
59
|
-
const fetched = await imap.fetchOne(seq, { source: true });
|
|
67
|
+
const fetched = await imap.fetchOne(mb.exists, { source: true });
|
|
60
68
|
if (fetched === false || !fetched.source)
|
|
61
69
|
return;
|
|
62
70
|
const parsed = await simpleParser(fetched.source);
|
|
@@ -64,13 +72,16 @@ export function apply(ctx, config) {
|
|
|
64
72
|
if (!from)
|
|
65
73
|
return;
|
|
66
74
|
// 白名单:不在名单内直接丢弃
|
|
75
|
+
const allowed = new Set(config.allowedAddresses.get().map(a => a.toLowerCase().trim()));
|
|
67
76
|
if (!allowed.has(from))
|
|
68
77
|
return;
|
|
69
78
|
const sessionId = `mail-${from}`;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
lastMail.set(sessionId, {
|
|
80
|
+
from,
|
|
81
|
+
subject: parsed.subject || '(no subject)',
|
|
82
|
+
messageId: parsed.messageId,
|
|
83
|
+
});
|
|
84
|
+
// 同一发件人同一会话
|
|
74
85
|
let handle = handles.get(sessionId);
|
|
75
86
|
if (!handle) {
|
|
76
87
|
handle = await getOrCreateAgent(ctx, sessionId);
|
|
@@ -81,19 +92,25 @@ export function apply(ctx, config) {
|
|
|
81
92
|
handleSessionEvent(sessionId, event);
|
|
82
93
|
});
|
|
83
94
|
}
|
|
84
|
-
|
|
85
|
-
content: [{ type: 'text', text:
|
|
95
|
+
handle.agent.followup(createUserMessage({
|
|
96
|
+
content: [{ type: 'text', text: parsed.text || '' }],
|
|
86
97
|
source: { kind: 'plugin', plugin: name },
|
|
87
|
-
});
|
|
88
|
-
handle.agent.followup(msg);
|
|
98
|
+
}));
|
|
89
99
|
}
|
|
90
100
|
catch (err) {
|
|
91
|
-
|
|
101
|
+
console.error('[mail-bot] 收信失败', err);
|
|
92
102
|
}
|
|
93
103
|
});
|
|
94
|
-
|
|
104
|
+
void (async () => {
|
|
105
|
+
await imap.connect();
|
|
106
|
+
if (closed)
|
|
107
|
+
return void imap.logout();
|
|
108
|
+
lock = await imap.getMailboxLock('INBOX');
|
|
109
|
+
})().catch(err => console.error('[mail-bot] IMAP 连接失败', err));
|
|
110
|
+
return () => {
|
|
111
|
+
closed = true;
|
|
95
112
|
try {
|
|
96
|
-
lock
|
|
113
|
+
lock?.release();
|
|
97
114
|
}
|
|
98
115
|
catch { }
|
|
99
116
|
for (const h of handles.values()) {
|
|
@@ -105,12 +122,13 @@ export function apply(ctx, config) {
|
|
|
105
122
|
handles.clear();
|
|
106
123
|
turnBuffers.clear();
|
|
107
124
|
lastMail.clear();
|
|
108
|
-
|
|
125
|
+
void imap.logout().catch(() => { });
|
|
109
126
|
};
|
|
110
127
|
});
|
|
111
128
|
function handleSessionEvent(sessionId, event) {
|
|
112
129
|
const data = event?.data ?? {};
|
|
113
130
|
const turn = data.turn;
|
|
131
|
+
const key = `${sessionId}:${turn}`;
|
|
114
132
|
if (event.type === 'assistant/message') {
|
|
115
133
|
const text = (data.message?.content ?? [])
|
|
116
134
|
.filter((b) => b.type === 'text')
|
|
@@ -118,31 +136,27 @@ export function apply(ctx, config) {
|
|
|
118
136
|
.join('');
|
|
119
137
|
if (!text)
|
|
120
138
|
return;
|
|
121
|
-
|
|
122
|
-
const buf = turnBuffers.get(key) ?? [];
|
|
123
|
-
buf.push(text);
|
|
124
|
-
turnBuffers.set(key, buf);
|
|
139
|
+
turnBuffers.set(key, [...(turnBuffers.get(key) ?? []), text]);
|
|
125
140
|
return;
|
|
126
141
|
}
|
|
142
|
+
// 一轮完整结束后才回复;一个 turn 可能含多个 step
|
|
127
143
|
if (event.type === 'turn/end' && data.reason?.kind === 'completed') {
|
|
128
|
-
const
|
|
129
|
-
const parts = turnBuffers.get(key) ?? [];
|
|
144
|
+
const replyText = (turnBuffers.get(key) ?? []).join('\n\n').trim();
|
|
130
145
|
turnBuffers.delete(key);
|
|
131
|
-
const replyText = parts.join('\n\n').trim();
|
|
132
146
|
if (!replyText)
|
|
133
147
|
return;
|
|
134
148
|
const mail = lastMail.get(sessionId);
|
|
135
149
|
if (!mail)
|
|
136
150
|
return;
|
|
137
151
|
const subject = mail.subject.startsWith('Re:') ? mail.subject : `Re: ${mail.subject}`;
|
|
138
|
-
|
|
139
|
-
from: config.smtp.user,
|
|
152
|
+
getSmtp().sendMail({
|
|
153
|
+
from: config.smtp.user.get(),
|
|
140
154
|
to: mail.from,
|
|
141
155
|
subject,
|
|
142
156
|
text: replyText,
|
|
143
157
|
inReplyTo: mail.messageId,
|
|
144
158
|
references: mail.messageId,
|
|
145
|
-
}).catch(err =>
|
|
159
|
+
}).catch(err => console.error('[mail-bot] 回信失败', err));
|
|
146
160
|
}
|
|
147
161
|
}
|
|
148
162
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mail-sender",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "DSH mail channel: inbound IMAP mail is delivered to an agent, whose reply is sent back over SMTP.",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "lib/index.js",
|
|
6
7
|
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
7
15
|
"files": ["lib", "cordis.patch.yml"],
|
|
16
|
+
"keywords": ["dsh-plugin", "deepseek-harness", "mail", "imap", "smtp"],
|
|
8
17
|
"dsh": {
|
|
9
|
-
"bundle": { "patch": "./cordis.patch.yml" }
|
|
10
|
-
"client": {
|
|
11
|
-
"inject": ["mail"],
|
|
12
|
-
"platform": "web"
|
|
13
|
-
}
|
|
18
|
+
"bundle": { "patch": "./cordis.patch.yml" }
|
|
14
19
|
},
|
|
15
20
|
"engines": {
|
|
16
21
|
"dsh": ">=0.2.1-rc.2 <0.3.0-0"
|
|
@@ -37,4 +42,4 @@
|
|
|
37
42
|
"@types/nodemailer": "^6.4.17",
|
|
38
43
|
"typescript": "^5.6.0"
|
|
39
44
|
}
|
|
40
|
-
}
|
|
45
|
+
}
|
package/readme.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# dsh-mail-sender
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DeepSeek Harness (DSH) 邮件通道插件:用户发邮件 → DSH 接收并交给 agent → agent 的回复自动用邮件发回去。
|
|
4
4
|
|
|
5
5
|
- **入站**:IMAP IDLE 实时监听收件箱
|
|
6
6
|
- **会话**:同一发件人 → 同一个 agent 会话(`sessionId = mail-<地址>`)
|
|
7
|
-
- **出站**:订阅 `session/event`,一轮 turn
|
|
8
|
-
- **白名单**:`allowedAddresses`
|
|
7
|
+
- **出站**:订阅 `session/event`,一轮 turn 正常结束后自动 SMTP 回信(`Re:` 主题 + `In-Reply-To`)
|
|
8
|
+
- **白名单**:`allowedAddresses` 同时控制入站与出站
|
|
9
|
+
- **配置界面**:Host 从导出的 `Config` schema 自动渲染,无需自写前端
|
|
9
10
|
|
|
10
11
|
## 工作原理
|
|
11
12
|
|
|
@@ -24,4 +25,94 @@ ctx.agents.create / resume (sessionId = `mail-${from}`)
|
|
|
24
25
|
│ turn/end (reason.kind === 'completed') → 取出本轮文本
|
|
25
26
|
▼
|
|
26
27
|
SMTP 回复 (Re: 原主题, In-Reply-To: 原 Message-ID)
|
|
27
|
-
```
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 安装
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# npm
|
|
34
|
+
dsh plugin --profile web add dsh-mail-sender
|
|
35
|
+
|
|
36
|
+
# GitHub(需仓库内已提交 lib/)
|
|
37
|
+
dsh plugin --profile web add github:<你的用户名>/dsh-mail-sender
|
|
38
|
+
|
|
39
|
+
# 本地源码
|
|
40
|
+
npm install && npm run build
|
|
41
|
+
dsh plugin --profile web add link:/path/to/dsh-mail-sender
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
安装后重启 `dsh web`,用 `dsh --profile web --dump-config` 确认插件行已进入配置树。
|
|
45
|
+
|
|
46
|
+
## 在设置页填写配置
|
|
47
|
+
|
|
48
|
+
打开 **Settings(设置)→ Plugins(插件)→ dsh-mail-sender**,表单由 Host 根据插件导出的 `Config` schema 自动生成。
|
|
49
|
+
|
|
50
|
+
> 该表单依赖两点,本插件已满足:
|
|
51
|
+
> 1. patch 行 `id` 与包名一致(`dsh-mail-sender`),设置表单按行 id 定位;
|
|
52
|
+
> 2. 每个可编辑字段声明为 `.volatile()`,DSH 才会把它投影成可编辑项并把改动写回 profile 的 `cordis.patch.yml`。
|
|
53
|
+
|
|
54
|
+
| 字段 | 说明 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `imap.host` | IMAP 服务器地址(如 `imap.qq.com`) |
|
|
57
|
+
| `imap.port` | 端口,默认 `993` |
|
|
58
|
+
| `imap.secure` | 是否 TLS,默认 `true` |
|
|
59
|
+
| `imap.user` | IMAP 登录账号 |
|
|
60
|
+
| `imap.password` | IMAP 密码或授权码 |
|
|
61
|
+
| `smtp.host` | SMTP 服务器地址(如 `smtp.qq.com`) |
|
|
62
|
+
| `smtp.port` | 端口,默认 `465` |
|
|
63
|
+
| `smtp.secure` | 是否 TLS,默认 `true` |
|
|
64
|
+
| `smtp.user` | SMTP 登录账号(也是回信 From) |
|
|
65
|
+
| `smtp.password` | SMTP 密码或授权码 |
|
|
66
|
+
| `allowedAddresses` | 允许与 agent 通信的邮箱地址列表 |
|
|
67
|
+
|
|
68
|
+
白名单同时控制两个方向:非名单内发件人的来信直接丢弃;agent 的回复目标就是来信人,因此也天然限定在名单内。
|
|
69
|
+
|
|
70
|
+
配置为 `.volatile()`:白名单与 SMTP 设置在保存后即时生效;IMAP 连接参数变更后需重载插件(重启 `dsh web`)才重连。
|
|
71
|
+
|
|
72
|
+
## 包结构
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
.
|
|
76
|
+
├── package.json # dsh.bundle 声明(无 dsh.client,纯 Host 插件)
|
|
77
|
+
├── cordis.patch.yml # 顶层 YAML 数组:插入插件行
|
|
78
|
+
├── src/index.ts
|
|
79
|
+
├── tsconfig.json
|
|
80
|
+
└── .npmrc # legacy-peer-deps=true(本地开发)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`cordis.patch.yml` 必须是**顶层数组**的 loader patch 条目:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
- insert:
|
|
87
|
+
- id: dsh-mail-sender
|
|
88
|
+
name: dsh-mail-sender
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 发布
|
|
92
|
+
|
|
93
|
+
### npm
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm run build # 产出 lib/
|
|
97
|
+
npm publish # scoped 包用 --access public
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`files` 已包含 `lib` 与 `cordis.patch.yml`——漏掉 patch 文件是最常见的“装上了但没进配置树”原因。
|
|
101
|
+
|
|
102
|
+
### GitHub
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git init && git add . && git commit -m "feat: dsh-mail-sender"
|
|
106
|
+
git branch -M main
|
|
107
|
+
git remote add origin https://github.com/<用户名>/dsh-mail-sender.git
|
|
108
|
+
git push -u origin main
|
|
109
|
+
git tag v0.1.3 && git push origin v0.1.3
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
从 git 安装时 pnpm 需要执行构建,请在 profile 的 `pnpm-workspace.yaml` 里加 `allowBuilds: { dsh-mail-sender: true }`;更省事的方式是发布到 npm(发布产物已构建好)。
|
|
113
|
+
|
|
114
|
+
## 依赖
|
|
115
|
+
|
|
116
|
+
运行时:`imapflow`、`mailparser`、`nodemailer`。
|
|
117
|
+
|
|
118
|
+
Peer(由 DSH host 提供):`@deepseek-ai/cordis`、`@deepseek-ai/dsh-agent`、`@deepseek-ai/dsh-llm`、`@deepseek-ai/schemastery`。
|