foliko 1.0.75 → 1.0.76
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/.claude/settings.local.json +159 -157
- package/cli/bin/foliko.js +12 -12
- package/cli/src/commands/chat.js +143 -143
- package/cli/src/commands/list.js +93 -93
- package/cli/src/index.js +75 -75
- package/cli/src/ui/chat-ui.js +201 -201
- package/cli/src/utils/ansi.js +40 -40
- package/cli/src/utils/markdown.js +292 -292
- package/examples/ambient-example.js +194 -194
- package/examples/basic.js +115 -115
- package/examples/bootstrap.js +121 -121
- package/examples/mcp-example.js +56 -56
- package/examples/skill-example.js +49 -49
- package/examples/test-chat.js +137 -137
- package/examples/test-mcp.js +85 -85
- package/examples/test-reload.js +59 -59
- package/examples/test-telegram.js +50 -50
- package/examples/test-tg-bot.js +45 -45
- package/examples/test-tg-simple.js +47 -47
- package/examples/test-tg.js +62 -62
- package/examples/test-think.js +43 -43
- package/examples/test-web-plugin.js +103 -103
- package/examples/test-weixin-feishu.js +103 -103
- package/examples/workflow.js +158 -158
- package/package.json +1 -1
- package/plugins/ai-plugin.js +102 -102
- package/plugins/ambient-agent/EventWatcher.js +113 -113
- package/plugins/ambient-agent/ExplorerLoop.js +640 -640
- package/plugins/ambient-agent/GoalManager.js +197 -197
- package/plugins/ambient-agent/Reflector.js +95 -95
- package/plugins/ambient-agent/StateStore.js +90 -90
- package/plugins/ambient-agent/constants.js +101 -101
- package/plugins/ambient-agent/index.js +579 -579
- package/plugins/audit-plugin.js +187 -187
- package/plugins/default-plugins.js +662 -662
- package/plugins/email/constants.js +64 -64
- package/plugins/email/handlers.js +461 -461
- package/plugins/email/index.js +278 -278
- package/plugins/email/monitor.js +269 -269
- package/plugins/email/parser.js +138 -138
- package/plugins/email/reply.js +151 -151
- package/plugins/email/utils.js +124 -124
- package/plugins/feishu-plugin.js +481 -481
- package/plugins/file-system-plugin.js +826 -826
- package/plugins/install-plugin.js +199 -199
- package/plugins/python-executor-plugin.js +367 -367
- package/plugins/python-plugin-loader.js +481 -481
- package/plugins/rules-plugin.js +294 -294
- package/plugins/scheduler-plugin.js +691 -691
- package/plugins/session-plugin.js +369 -369
- package/plugins/shell-executor-plugin.js +197 -197
- package/plugins/storage-plugin.js +240 -240
- package/plugins/subagent-plugin.js +845 -845
- package/plugins/telegram-plugin.js +482 -482
- package/plugins/think-plugin.js +345 -345
- package/plugins/tools-plugin.js +196 -196
- package/plugins/web-plugin.js +606 -606
- package/plugins/weixin-plugin.js +545 -545
- package/src/capabilities/index.js +11 -11
- package/src/capabilities/skill-manager.js +609 -609
- package/src/capabilities/workflow-engine.js +1109 -1109
- package/src/core/agent-chat.js +882 -882
- package/src/core/agent.js +892 -892
- package/src/core/framework.js +465 -465
- package/src/core/index.js +19 -19
- package/src/core/plugin-base.js +219 -219
- package/src/core/plugin-manager.js +863 -863
- package/src/core/provider.js +114 -114
- package/src/core/sub-agent-config.js +264 -264
- package/src/core/system-prompt-builder.js +120 -120
- package/src/core/tool-registry.js +517 -517
- package/src/core/tool-router.js +297 -297
- package/src/executors/executor-base.js +58 -58
- package/src/executors/mcp-executor.js +741 -741
- package/src/index.js +25 -25
- package/src/utils/circuit-breaker.js +301 -301
- package/src/utils/error-boundary.js +363 -363
- package/src/utils/error.js +374 -374
- package/src/utils/event-emitter.js +97 -97
- package/src/utils/id.js +133 -133
- package/src/utils/index.js +217 -217
- package/src/utils/logger.js +181 -181
- package/src/utils/plugin-helpers.js +90 -90
- package/src/utils/retry.js +122 -122
- package/src/utils/sandbox.js +292 -292
- package/test/tool-registry-validation.test.js +218 -218
- package/website/script.js +136 -136
- package/foliko-1.0.75.tgz +0 -0
package/plugins/email/parser.js
CHANGED
|
@@ -1,138 +1,138 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Email 插件 - 邮件解析器
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { simpleParser } = require('mailparser')
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 解析邮件内容
|
|
9
|
-
* @param {Stream} stream - 邮件流
|
|
10
|
-
* @returns {Promise<Object>} 解析后的邮件对象
|
|
11
|
-
*/
|
|
12
|
-
async function parseEmail(stream) {
|
|
13
|
-
return await simpleParser(stream)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 将邮件对象标准化
|
|
18
|
-
* @param {Object} mail - mailparser 解析结果
|
|
19
|
-
* @param {number} defaultUid - 默认 UID
|
|
20
|
-
* @returns {Object} 标准化的邮件对象
|
|
21
|
-
*/
|
|
22
|
-
function normalizeEmail(mail, defaultUid) {
|
|
23
|
-
return {
|
|
24
|
-
uid: mail.uid || defaultUid,
|
|
25
|
-
messageId: mail.messageId,
|
|
26
|
-
subject: mail.subject,
|
|
27
|
-
from: mail.from?.text || '',
|
|
28
|
-
fromName: mail.from?.value?.[0]?.name || '',
|
|
29
|
-
fromAddress: mail.from?.value?.[0]?.address || '',
|
|
30
|
-
to: mail.to?.text || '',
|
|
31
|
-
toName: mail.to?.value?.[0]?.name || '',
|
|
32
|
-
toAddress: mail.to?.value?.[0]?.address || '',
|
|
33
|
-
date: mail.date?.toISOString() || '',
|
|
34
|
-
text: mail.text || mail.textAsHtml || '',
|
|
35
|
-
html: mail.html,
|
|
36
|
-
attachments: parseAttachments(mail.attachments),
|
|
37
|
-
headers: parseHeaders(mail.headers)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 解析附件列表
|
|
43
|
-
* @param {Array} attachments - 附件数组
|
|
44
|
-
* @returns {Array} 标准化的附件列表
|
|
45
|
-
*/
|
|
46
|
-
function parseAttachments(attachments) {
|
|
47
|
-
if (!attachments || !Array.isArray(attachments)) {
|
|
48
|
-
return []
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return attachments.map(att => ({
|
|
52
|
-
filename: att.filename,
|
|
53
|
-
contentType: att.contentType,
|
|
54
|
-
contentId: att.contentId,
|
|
55
|
-
size: att.size,
|
|
56
|
-
headers: att.headers
|
|
57
|
-
}))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 解析邮件头
|
|
62
|
-
* @param {Map|Object} headers - 邮件头
|
|
63
|
-
* @returns {Object} 标准化的头信息
|
|
64
|
-
*/
|
|
65
|
-
function parseHeaders(headers) {
|
|
66
|
-
if (!headers) return {}
|
|
67
|
-
|
|
68
|
-
const result = {}
|
|
69
|
-
if (headers instanceof Map) {
|
|
70
|
-
for (const [key, value] of headers) {
|
|
71
|
-
result[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
75
|
-
result[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return result
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* 检测邮件编码
|
|
84
|
-
* @param {string} charset - 字符集
|
|
85
|
-
* @returns {string} 标准化的字符集名称
|
|
86
|
-
*/
|
|
87
|
-
function normalizeCharset(charset) {
|
|
88
|
-
if (!charset) return 'utf-8'
|
|
89
|
-
|
|
90
|
-
const charsetMap = {
|
|
91
|
-
'gb2312': 'gb18030',
|
|
92
|
-
'gbk': 'gb18030',
|
|
93
|
-
'windows-1252': 'cp1252'
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return charsetMap[charset.toLowerCase()] || charset.toLowerCase()
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 清理邮件文本内容
|
|
101
|
-
* @param {string} text - 原始文本
|
|
102
|
-
* @returns {string} 清理后的文本
|
|
103
|
-
*/
|
|
104
|
-
function cleanText(text) {
|
|
105
|
-
if (!text) return ''
|
|
106
|
-
|
|
107
|
-
return text
|
|
108
|
-
.replace(/\r\n/g, '\n') // 统一换行符
|
|
109
|
-
.replace(/\t/g, ' ') // Tab 转为空格
|
|
110
|
-
.replace(/ +/g, ' ') // 多个空格合并为一个
|
|
111
|
-
.replace(/\n{3,}/g, '\n\n') // 超过两个连续换行合并为两个
|
|
112
|
-
.trim()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 从文本中提取邮箱地址
|
|
117
|
-
* @param {string} text - 文本内容
|
|
118
|
-
* @returns {Array<string>} 邮箱地址列表
|
|
119
|
-
*/
|
|
120
|
-
function extractEmails(text) {
|
|
121
|
-
if (!text) return []
|
|
122
|
-
|
|
123
|
-
const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g
|
|
124
|
-
const matches = text.match(emailRegex) || []
|
|
125
|
-
|
|
126
|
-
// 去重
|
|
127
|
-
return [...new Set(matches)]
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
module.exports = {
|
|
131
|
-
parseEmail,
|
|
132
|
-
normalizeEmail,
|
|
133
|
-
parseAttachments,
|
|
134
|
-
parseHeaders,
|
|
135
|
-
normalizeCharset,
|
|
136
|
-
cleanText,
|
|
137
|
-
extractEmails
|
|
138
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Email 插件 - 邮件解析器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { simpleParser } = require('mailparser')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 解析邮件内容
|
|
9
|
+
* @param {Stream} stream - 邮件流
|
|
10
|
+
* @returns {Promise<Object>} 解析后的邮件对象
|
|
11
|
+
*/
|
|
12
|
+
async function parseEmail(stream) {
|
|
13
|
+
return await simpleParser(stream)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 将邮件对象标准化
|
|
18
|
+
* @param {Object} mail - mailparser 解析结果
|
|
19
|
+
* @param {number} defaultUid - 默认 UID
|
|
20
|
+
* @returns {Object} 标准化的邮件对象
|
|
21
|
+
*/
|
|
22
|
+
function normalizeEmail(mail, defaultUid) {
|
|
23
|
+
return {
|
|
24
|
+
uid: mail.uid || defaultUid,
|
|
25
|
+
messageId: mail.messageId,
|
|
26
|
+
subject: mail.subject,
|
|
27
|
+
from: mail.from?.text || '',
|
|
28
|
+
fromName: mail.from?.value?.[0]?.name || '',
|
|
29
|
+
fromAddress: mail.from?.value?.[0]?.address || '',
|
|
30
|
+
to: mail.to?.text || '',
|
|
31
|
+
toName: mail.to?.value?.[0]?.name || '',
|
|
32
|
+
toAddress: mail.to?.value?.[0]?.address || '',
|
|
33
|
+
date: mail.date?.toISOString() || '',
|
|
34
|
+
text: mail.text || mail.textAsHtml || '',
|
|
35
|
+
html: mail.html,
|
|
36
|
+
attachments: parseAttachments(mail.attachments),
|
|
37
|
+
headers: parseHeaders(mail.headers)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 解析附件列表
|
|
43
|
+
* @param {Array} attachments - 附件数组
|
|
44
|
+
* @returns {Array} 标准化的附件列表
|
|
45
|
+
*/
|
|
46
|
+
function parseAttachments(attachments) {
|
|
47
|
+
if (!attachments || !Array.isArray(attachments)) {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return attachments.map(att => ({
|
|
52
|
+
filename: att.filename,
|
|
53
|
+
contentType: att.contentType,
|
|
54
|
+
contentId: att.contentId,
|
|
55
|
+
size: att.size,
|
|
56
|
+
headers: att.headers
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 解析邮件头
|
|
62
|
+
* @param {Map|Object} headers - 邮件头
|
|
63
|
+
* @returns {Object} 标准化的头信息
|
|
64
|
+
*/
|
|
65
|
+
function parseHeaders(headers) {
|
|
66
|
+
if (!headers) return {}
|
|
67
|
+
|
|
68
|
+
const result = {}
|
|
69
|
+
if (headers instanceof Map) {
|
|
70
|
+
for (const [key, value] of headers) {
|
|
71
|
+
result[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
75
|
+
result[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 检测邮件编码
|
|
84
|
+
* @param {string} charset - 字符集
|
|
85
|
+
* @returns {string} 标准化的字符集名称
|
|
86
|
+
*/
|
|
87
|
+
function normalizeCharset(charset) {
|
|
88
|
+
if (!charset) return 'utf-8'
|
|
89
|
+
|
|
90
|
+
const charsetMap = {
|
|
91
|
+
'gb2312': 'gb18030',
|
|
92
|
+
'gbk': 'gb18030',
|
|
93
|
+
'windows-1252': 'cp1252'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return charsetMap[charset.toLowerCase()] || charset.toLowerCase()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 清理邮件文本内容
|
|
101
|
+
* @param {string} text - 原始文本
|
|
102
|
+
* @returns {string} 清理后的文本
|
|
103
|
+
*/
|
|
104
|
+
function cleanText(text) {
|
|
105
|
+
if (!text) return ''
|
|
106
|
+
|
|
107
|
+
return text
|
|
108
|
+
.replace(/\r\n/g, '\n') // 统一换行符
|
|
109
|
+
.replace(/\t/g, ' ') // Tab 转为空格
|
|
110
|
+
.replace(/ +/g, ' ') // 多个空格合并为一个
|
|
111
|
+
.replace(/\n{3,}/g, '\n\n') // 超过两个连续换行合并为两个
|
|
112
|
+
.trim()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 从文本中提取邮箱地址
|
|
117
|
+
* @param {string} text - 文本内容
|
|
118
|
+
* @returns {Array<string>} 邮箱地址列表
|
|
119
|
+
*/
|
|
120
|
+
function extractEmails(text) {
|
|
121
|
+
if (!text) return []
|
|
122
|
+
|
|
123
|
+
const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g
|
|
124
|
+
const matches = text.match(emailRegex) || []
|
|
125
|
+
|
|
126
|
+
// 去重
|
|
127
|
+
return [...new Set(matches)]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
parseEmail,
|
|
132
|
+
normalizeEmail,
|
|
133
|
+
parseAttachments,
|
|
134
|
+
parseHeaders,
|
|
135
|
+
normalizeCharset,
|
|
136
|
+
cleanText,
|
|
137
|
+
extractEmails
|
|
138
|
+
}
|
package/plugins/email/reply.js
CHANGED
|
@@ -1,151 +1,151 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Email 插件 - 自动回复
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { EMAIL_DEFAULTS } = require('./constants')
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 处理自动回复
|
|
9
|
-
* @param {Object} emailPlugin - EmailPlugin 实例
|
|
10
|
-
* @param {Object} args - 回复参数
|
|
11
|
-
* @returns {Promise<Object>} 回复结果
|
|
12
|
-
*/
|
|
13
|
-
async function handleAutoReply(emailPlugin, args) {
|
|
14
|
-
let { to, subject, body, from, _event, prompt, messageId, inReplyTo } = args
|
|
15
|
-
|
|
16
|
-
// 如果没有直接参数,尝试从 _event 提取
|
|
17
|
-
// 支持多种事件结构:{ email, timestamp } 或 { data: { email } } 或 { data: { ... } }
|
|
18
|
-
if (!to && !subject && !body && _event) {
|
|
19
|
-
// 兼容多种事件结构
|
|
20
|
-
const email = _event.email || _event.data?.email || _event.data || {}
|
|
21
|
-
from = email.from?.text || email.from || ''
|
|
22
|
-
to = email.to?.text || email.to || ''
|
|
23
|
-
subject = email.subject || ''
|
|
24
|
-
body = email.text || email.body || ''
|
|
25
|
-
// 优先使用 _event 中的 uid/messageId
|
|
26
|
-
if (!messageId) {
|
|
27
|
-
messageId = email.messageId || email.uid || _event.data?.messageId || _event.data?.uid
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 如果 _event 没有有效数据,不允许自动读取所有邮件
|
|
32
|
-
if (!_event || (!_event.email && !_event.data)) {
|
|
33
|
-
return {
|
|
34
|
-
success: false,
|
|
35
|
-
error: '缺少邮件数据:_event 中没有有效的邮件信息,无法自动回复。请确认是否在收到新邮件事件时调用此工具。'
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 优先使用传入的 inReplyTo,否则用 messageId
|
|
40
|
-
const replyTo = inReplyTo || messageId
|
|
41
|
-
|
|
42
|
-
// 检查必要参数
|
|
43
|
-
if (!from && !to) {
|
|
44
|
-
return { success: false, error: '缺少收件人地址' }
|
|
45
|
-
}
|
|
46
|
-
if (!body) {
|
|
47
|
-
return { success: false, error: '缺少邮件内容' }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
// 使用子 Agent 生成回复
|
|
52
|
-
const replyAgent = emailPlugin._framework.createSubAgent({
|
|
53
|
-
name: 'email_replier',
|
|
54
|
-
role: '邮件自动回复助手,专注于生成专业、礼貌、简洁的邮件回复',
|
|
55
|
-
parentTools: []
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// 构建提示让 LLM 生成回复
|
|
59
|
-
const finalPrompt = prompt || `你是一封邮件自动回复助手。请根据以下邮件内容,生成一封专业的回复邮件。
|
|
60
|
-
|
|
61
|
-
【原始邮件】
|
|
62
|
-
发件人: ${from || to}
|
|
63
|
-
主题: ${subject}
|
|
64
|
-
内容:
|
|
65
|
-
${body}
|
|
66
|
-
|
|
67
|
-
【要求】
|
|
68
|
-
1. 回复内容要针对邮件中的问题或内容进行回复
|
|
69
|
-
2. 语言要专业、礼貌、简洁
|
|
70
|
-
3. 只输出邮件正文内容,不要额外解释
|
|
71
|
-
4. 回复语言应与原邮件一致(如果原邮件是中文,则用中文回复)`
|
|
72
|
-
|
|
73
|
-
// 等待 Agent 生成回复(带超时保护)
|
|
74
|
-
const timeoutMs = args.timeout || EMAIL_DEFAULTS.timeout
|
|
75
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
76
|
-
setTimeout(() => reject(new Error(`AI回复生成超时(${timeoutMs / 1000}秒)`)), timeoutMs)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const replyPromise = replyAgent.chat(finalPrompt, { maxSteps: 3 })
|
|
80
|
-
const replyResult = await Promise.race([replyPromise, timeoutPromise])
|
|
81
|
-
|
|
82
|
-
// 提取回复内容
|
|
83
|
-
let replyContent = ''
|
|
84
|
-
if (typeof replyResult === 'string') {
|
|
85
|
-
replyContent = replyResult.trim()
|
|
86
|
-
} else if (replyResult && replyResult.content) {
|
|
87
|
-
replyContent = replyResult.content.trim()
|
|
88
|
-
} else if (replyResult && replyResult.message) {
|
|
89
|
-
replyContent = replyResult.message.trim()
|
|
90
|
-
} else {
|
|
91
|
-
replyContent = JSON.stringify(replyResult).trim()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 去掉思考过程标签
|
|
95
|
-
replyContent = replyContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
|
|
96
|
-
|
|
97
|
-
if (!replyContent || replyContent.length < 5) {
|
|
98
|
-
return { success: false, error: 'AI未能生成有效的回复内容' }
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 发送回复邮件
|
|
102
|
-
const { sendEmail } = require('./handlers')
|
|
103
|
-
const sendResult = await sendEmail(emailPlugin, {
|
|
104
|
-
to: from || to,
|
|
105
|
-
subject: `Re: ${subject}`,
|
|
106
|
-
body: replyContent,
|
|
107
|
-
inReplyTo: replyTo,
|
|
108
|
-
references: replyTo ? [replyTo] : undefined
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
if (sendResult.success) {
|
|
112
|
-
// 发送成功后,标记为已处理
|
|
113
|
-
const emailData = _event?.email || _event?.data?.email || _event?.data || {}
|
|
114
|
-
const msgId = messageId || emailData.messageId || emailData.uid
|
|
115
|
-
if (msgId) {
|
|
116
|
-
emailPlugin._processedEmails.add(msgId)
|
|
117
|
-
emailPlugin._log.info(`邮件已处理: ${msgId}`)
|
|
118
|
-
|
|
119
|
-
// 24小时后从已处理列表中移除
|
|
120
|
-
setTimeout(() => {
|
|
121
|
-
emailPlugin._processedEmails.delete(msgId)
|
|
122
|
-
}, EMAIL_DEFAULTS.processedEmailTTL)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 自动标记原邮件为已读
|
|
126
|
-
const uid = emailData.uid || msgId
|
|
127
|
-
if (uid) {
|
|
128
|
-
try {
|
|
129
|
-
const { markAsRead } = require('./handlers')
|
|
130
|
-
await markAsRead(emailPlugin, { uid: uid }) // 使用 uid 而非 messageId
|
|
131
|
-
} catch (err) {
|
|
132
|
-
emailPlugin._log.warn(`自动标记已读失败: ${err.message}`)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
success: true,
|
|
138
|
-
message: `自动回复已发送至 ${from || to}`,
|
|
139
|
-
replyContent
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
return { success: false, error: sendResult.error || '发送失败' }
|
|
143
|
-
}
|
|
144
|
-
} catch (err) {
|
|
145
|
-
return { success: false, error: err.message }
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
module.exports = {
|
|
150
|
-
handleAutoReply
|
|
151
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Email 插件 - 自动回复
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { EMAIL_DEFAULTS } = require('./constants')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 处理自动回复
|
|
9
|
+
* @param {Object} emailPlugin - EmailPlugin 实例
|
|
10
|
+
* @param {Object} args - 回复参数
|
|
11
|
+
* @returns {Promise<Object>} 回复结果
|
|
12
|
+
*/
|
|
13
|
+
async function handleAutoReply(emailPlugin, args) {
|
|
14
|
+
let { to, subject, body, from, _event, prompt, messageId, inReplyTo } = args
|
|
15
|
+
|
|
16
|
+
// 如果没有直接参数,尝试从 _event 提取
|
|
17
|
+
// 支持多种事件结构:{ email, timestamp } 或 { data: { email } } 或 { data: { ... } }
|
|
18
|
+
if (!to && !subject && !body && _event) {
|
|
19
|
+
// 兼容多种事件结构
|
|
20
|
+
const email = _event.email || _event.data?.email || _event.data || {}
|
|
21
|
+
from = email.from?.text || email.from || ''
|
|
22
|
+
to = email.to?.text || email.to || ''
|
|
23
|
+
subject = email.subject || ''
|
|
24
|
+
body = email.text || email.body || ''
|
|
25
|
+
// 优先使用 _event 中的 uid/messageId
|
|
26
|
+
if (!messageId) {
|
|
27
|
+
messageId = email.messageId || email.uid || _event.data?.messageId || _event.data?.uid
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 如果 _event 没有有效数据,不允许自动读取所有邮件
|
|
32
|
+
if (!_event || (!_event.email && !_event.data)) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
error: '缺少邮件数据:_event 中没有有效的邮件信息,无法自动回复。请确认是否在收到新邮件事件时调用此工具。'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 优先使用传入的 inReplyTo,否则用 messageId
|
|
40
|
+
const replyTo = inReplyTo || messageId
|
|
41
|
+
|
|
42
|
+
// 检查必要参数
|
|
43
|
+
if (!from && !to) {
|
|
44
|
+
return { success: false, error: '缺少收件人地址' }
|
|
45
|
+
}
|
|
46
|
+
if (!body) {
|
|
47
|
+
return { success: false, error: '缺少邮件内容' }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// 使用子 Agent 生成回复
|
|
52
|
+
const replyAgent = emailPlugin._framework.createSubAgent({
|
|
53
|
+
name: 'email_replier',
|
|
54
|
+
role: '邮件自动回复助手,专注于生成专业、礼貌、简洁的邮件回复',
|
|
55
|
+
parentTools: []
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// 构建提示让 LLM 生成回复
|
|
59
|
+
const finalPrompt = prompt || `你是一封邮件自动回复助手。请根据以下邮件内容,生成一封专业的回复邮件。
|
|
60
|
+
|
|
61
|
+
【原始邮件】
|
|
62
|
+
发件人: ${from || to}
|
|
63
|
+
主题: ${subject}
|
|
64
|
+
内容:
|
|
65
|
+
${body}
|
|
66
|
+
|
|
67
|
+
【要求】
|
|
68
|
+
1. 回复内容要针对邮件中的问题或内容进行回复
|
|
69
|
+
2. 语言要专业、礼貌、简洁
|
|
70
|
+
3. 只输出邮件正文内容,不要额外解释
|
|
71
|
+
4. 回复语言应与原邮件一致(如果原邮件是中文,则用中文回复)`
|
|
72
|
+
|
|
73
|
+
// 等待 Agent 生成回复(带超时保护)
|
|
74
|
+
const timeoutMs = args.timeout || EMAIL_DEFAULTS.timeout
|
|
75
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
76
|
+
setTimeout(() => reject(new Error(`AI回复生成超时(${timeoutMs / 1000}秒)`)), timeoutMs)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const replyPromise = replyAgent.chat(finalPrompt, { maxSteps: 3 })
|
|
80
|
+
const replyResult = await Promise.race([replyPromise, timeoutPromise])
|
|
81
|
+
|
|
82
|
+
// 提取回复内容
|
|
83
|
+
let replyContent = ''
|
|
84
|
+
if (typeof replyResult === 'string') {
|
|
85
|
+
replyContent = replyResult.trim()
|
|
86
|
+
} else if (replyResult && replyResult.content) {
|
|
87
|
+
replyContent = replyResult.content.trim()
|
|
88
|
+
} else if (replyResult && replyResult.message) {
|
|
89
|
+
replyContent = replyResult.message.trim()
|
|
90
|
+
} else {
|
|
91
|
+
replyContent = JSON.stringify(replyResult).trim()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 去掉思考过程标签
|
|
95
|
+
replyContent = replyContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
|
|
96
|
+
|
|
97
|
+
if (!replyContent || replyContent.length < 5) {
|
|
98
|
+
return { success: false, error: 'AI未能生成有效的回复内容' }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 发送回复邮件
|
|
102
|
+
const { sendEmail } = require('./handlers')
|
|
103
|
+
const sendResult = await sendEmail(emailPlugin, {
|
|
104
|
+
to: from || to,
|
|
105
|
+
subject: `Re: ${subject}`,
|
|
106
|
+
body: replyContent,
|
|
107
|
+
inReplyTo: replyTo,
|
|
108
|
+
references: replyTo ? [replyTo] : undefined
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
if (sendResult.success) {
|
|
112
|
+
// 发送成功后,标记为已处理
|
|
113
|
+
const emailData = _event?.email || _event?.data?.email || _event?.data || {}
|
|
114
|
+
const msgId = messageId || emailData.messageId || emailData.uid
|
|
115
|
+
if (msgId) {
|
|
116
|
+
emailPlugin._processedEmails.add(msgId)
|
|
117
|
+
emailPlugin._log.info(`邮件已处理: ${msgId}`)
|
|
118
|
+
|
|
119
|
+
// 24小时后从已处理列表中移除
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
emailPlugin._processedEmails.delete(msgId)
|
|
122
|
+
}, EMAIL_DEFAULTS.processedEmailTTL)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 自动标记原邮件为已读
|
|
126
|
+
const uid = emailData.uid || msgId
|
|
127
|
+
if (uid) {
|
|
128
|
+
try {
|
|
129
|
+
const { markAsRead } = require('./handlers')
|
|
130
|
+
await markAsRead(emailPlugin, { uid: uid }) // 使用 uid 而非 messageId
|
|
131
|
+
} catch (err) {
|
|
132
|
+
emailPlugin._log.warn(`自动标记已读失败: ${err.message}`)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
message: `自动回复已发送至 ${from || to}`,
|
|
139
|
+
replyContent
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
return { success: false, error: sendResult.error || '发送失败' }
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
return { success: false, error: err.message }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
handleAutoReply
|
|
151
|
+
}
|