foliko 1.0.53 → 1.0.55
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 +141 -131
- package/CLAUDE.md +106 -0
- package/Dockerfile +2 -2
- package/cli/src/index.js +6 -3
- package/cli/src/ui/chat-ui.js +1 -1
- package/examples/ambient-example.js +196 -0
- package/package.json +1 -1
- package/plugins/ambient-agent-plugin.js +1134 -0
- package/plugins/default-plugins.js +38 -32
- package/plugins/email.js +484 -25
- package/plugins/feishu-plugin.js +2 -0
- package/plugins/file-system-plugin.js +57 -1
- package/plugins/python-executor-plugin.js +1 -1
- package/plugins/python-plugin-loader.js +2 -2
- package/plugins/subagent-plugin.js +25 -25
- package/plugins/telegram-plugin.js +3 -0
- package/plugins/weixin-plugin.js +2 -0
- package/src/capabilities/skill-manager.js +230 -2
- package/src/core/agent.js +19 -14
- package/src/core/plugin-manager.js +2 -2
- package/src/core/provider.js +0 -1
package/plugins/email.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Email 插件
|
|
3
|
-
* 邮件收发插件 -
|
|
3
|
+
* 邮件收发插件 - 支持读取和发送电子邮件、监控新邮件
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { Plugin } = require('../src/core/plugin-base')
|
|
@@ -10,16 +10,27 @@ class EmailPlugin extends Plugin {
|
|
|
10
10
|
constructor(config = {}) {
|
|
11
11
|
super()
|
|
12
12
|
this.name = 'email'
|
|
13
|
-
this.version = '1.
|
|
14
|
-
this.description = `邮件收发插件 -
|
|
13
|
+
this.version = '1.1.0'
|
|
14
|
+
this.description = `邮件收发插件 - 支持读取和发送电子邮件、监控新邮件、自动回复,配置已经预设,不用获取配置
|
|
15
|
+
功能:
|
|
16
|
+
- 发送邮件支持附件(本地文件、远程URL、Base64)
|
|
17
|
+
- 读取邮件(IMAP协议)
|
|
18
|
+
- 监控新邮件(支持IMAP IDLE推送和定时轮询)
|
|
19
|
+
- 自动回复邮件(AI分析内容并发送回复,无需用户确认)
|
|
15
20
|
发送邮件支持附件:
|
|
16
21
|
- attachments.path: 本地文件路径
|
|
17
22
|
- attachments.url: 远程图片/文件URL(自动下载)
|
|
18
23
|
- attachments.content: Base64内容
|
|
19
24
|
- attachments.cid: 嵌入式图片CID(HTML中用 <img src="cid:xxx"> 引用)`
|
|
20
25
|
this.priority = 10
|
|
21
|
-
// 默认不启用,需要在 plugins.json 中设置 enabled: true
|
|
22
26
|
this.enabled = false
|
|
27
|
+
|
|
28
|
+
// 邮件监控状态
|
|
29
|
+
this._watchInterval = null
|
|
30
|
+
this._lastSeenUid = null
|
|
31
|
+
this._watchConfig = null
|
|
32
|
+
this._watchEnabled = false
|
|
33
|
+
this._checking = false // 防止并发检查
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
install(framework) {
|
|
@@ -28,6 +39,41 @@ class EmailPlugin extends Plugin {
|
|
|
28
39
|
return this
|
|
29
40
|
}
|
|
30
41
|
|
|
42
|
+
start(framework) {
|
|
43
|
+
// 自动启动邮件监控(如果配置了 IMAP 且尚未运行)
|
|
44
|
+
if (this._watchEnabled) {
|
|
45
|
+
console.log('[Email] Email watch already running, skipping auto-start')
|
|
46
|
+
return this
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// if (process.env.IMAP_USER && process.env.IMAP_PASS) {
|
|
50
|
+
// console.log('[Email] Auto-starting email watch...')
|
|
51
|
+
// this._startEmailWatch({
|
|
52
|
+
// interval: 60,
|
|
53
|
+
// host: process.env.IMAP_HOST,
|
|
54
|
+
// port: parseInt(process.env.IMAP_PORT) || 993,
|
|
55
|
+
// user: process.env.IMAP_USER,
|
|
56
|
+
// password: process.env.IMAP_PASS,
|
|
57
|
+
// box: 'INBOX'
|
|
58
|
+
// })
|
|
59
|
+
// } else {
|
|
60
|
+
// console.log('[Email] IMAP credentials not configured, skipping auto-start')
|
|
61
|
+
// }
|
|
62
|
+
return this
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 发送邮件事件
|
|
67
|
+
*/
|
|
68
|
+
_emitEmailReceived(email) {
|
|
69
|
+
if (this._framework) {
|
|
70
|
+
this._framework.emit('email:received', {
|
|
71
|
+
email,
|
|
72
|
+
timestamp: new Date()
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
31
77
|
_registerTools() {
|
|
32
78
|
// 发送邮件工具
|
|
33
79
|
this._framework.registerTool({
|
|
@@ -105,26 +151,413 @@ class EmailPlugin extends Plugin {
|
|
|
105
151
|
})
|
|
106
152
|
|
|
107
153
|
// 配置邮箱连接
|
|
154
|
+
// this._framework.registerTool({
|
|
155
|
+
// name: 'email_configure',
|
|
156
|
+
// description: '查看邮箱配置(实际配置通过环境变量设置)',
|
|
157
|
+
// inputSchema: z.object({}),
|
|
158
|
+
// execute: async () => {
|
|
159
|
+
// return {
|
|
160
|
+
// success: true,
|
|
161
|
+
// message: '邮箱配置通过环境变量设置',
|
|
162
|
+
// config: {
|
|
163
|
+
// smtp_host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
|
164
|
+
// smtp_port: process.env.SMTP_PORT || 587,
|
|
165
|
+
// smtp_secure: process.env.SMTP_SECURE || 'false',
|
|
166
|
+
// imap_host: process.env.IMAP_HOST || 'imap.gmail.com',
|
|
167
|
+
// imap_port: process.env.IMAP_PORT || 993,
|
|
168
|
+
// from_email: process.env.FROM_EMAIL || '(未设置)',
|
|
169
|
+
// client_name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
|
|
170
|
+
// client_version: process.env.IMAP_CLIENT_VERSION || '1.0.0'
|
|
171
|
+
// }
|
|
172
|
+
// }
|
|
173
|
+
// }
|
|
174
|
+
// })
|
|
175
|
+
|
|
176
|
+
// 监控新邮件
|
|
108
177
|
this._framework.registerTool({
|
|
109
|
-
name: '
|
|
110
|
-
description: '
|
|
111
|
-
inputSchema: z.object({
|
|
112
|
-
|
|
178
|
+
name: 'email_watch',
|
|
179
|
+
description: '启动/停止邮件监控,检测新邮件并发送事件通知',
|
|
180
|
+
inputSchema: z.object({
|
|
181
|
+
action: z.enum(['start', 'stop', 'status']).describe('操作:启动监控、停止监控、查看状态'),
|
|
182
|
+
interval: z.number().optional().describe('轮询间隔(秒),默认60秒,适用于不支持IDLE的服务器'),
|
|
183
|
+
// host: z.string().optional().describe('IMAP服务器地址'),
|
|
184
|
+
// port: z.number().optional().describe('IMAP端口'),
|
|
185
|
+
// user: z.string().optional().describe('邮箱用户名'),
|
|
186
|
+
// password: z.string().optional().describe('邮箱密码'),
|
|
187
|
+
box: z.string().optional().describe('邮箱文件夹,默认INBOX')
|
|
188
|
+
}),
|
|
189
|
+
execute: async (args) => {
|
|
190
|
+
|
|
191
|
+
return this._handleEmailWatch({
|
|
192
|
+
...args,
|
|
193
|
+
host: process.env.IMAP_HOST,
|
|
194
|
+
port: parseInt(process.env.IMAP_PORT) || 993,
|
|
195
|
+
user: process.env.IMAP_USER,
|
|
196
|
+
password: process.env.IMAP_PASS,
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// 自动回复邮件
|
|
202
|
+
this._framework.registerTool({
|
|
203
|
+
name: 'email_auto_reply',
|
|
204
|
+
description: '自动分析邮件内容并发送回复(无需用户确认)',
|
|
205
|
+
inputSchema: z.object({
|
|
206
|
+
to: z.string().describe('收件人邮箱地址'),
|
|
207
|
+
subject: z.string().describe('原始邮件主题'),
|
|
208
|
+
body: z.string().describe('原始邮件内容'),
|
|
209
|
+
from: z.string().optional().describe('发件人邮箱地址(可选)')
|
|
210
|
+
}),
|
|
211
|
+
execute: async (args) => {
|
|
212
|
+
return this._handleAutoReply(args)
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 处理自动回复
|
|
219
|
+
*/
|
|
220
|
+
async _handleAutoReply(args) {
|
|
221
|
+
// 支持两种模式:
|
|
222
|
+
// 1. 直接传参数: { to, subject, body, from }
|
|
223
|
+
// 2. 从 _event 提取: { _event } (来自 ambient agent 的事件触发)
|
|
224
|
+
let { to, subject, body, from, _event } = args
|
|
225
|
+
|
|
226
|
+
// 如果没有直接参数,尝试从 _event 提取
|
|
227
|
+
if (!to && !subject && !body && _event) {
|
|
228
|
+
const email = _event.data?.email || _event.email || {}
|
|
229
|
+
from = email.from?.text || email.from || ''
|
|
230
|
+
to = email.to?.text || email.to || ''
|
|
231
|
+
subject = email.subject || ''
|
|
232
|
+
body = email.text || email.body || ''
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 检查必要参数
|
|
236
|
+
if (!from && !to) {
|
|
237
|
+
return { success: false, error: '缺少收件人地址' }
|
|
238
|
+
}
|
|
239
|
+
if (!body) {
|
|
240
|
+
return { success: false, error: '缺少邮件内容' }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
// 获取活跃的 Agent
|
|
245
|
+
const agent = this._getActiveAgent()
|
|
246
|
+
if (!agent) {
|
|
247
|
+
return { success: false, error: 'No active agent found' }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 构建提示让 LLM 生成回复
|
|
251
|
+
const prompt = `你是一封邮件自动回复助手。请根据以下邮件内容,生成一封专业的回复邮件。
|
|
252
|
+
|
|
253
|
+
【原始邮件】
|
|
254
|
+
发件人: ${from || to}
|
|
255
|
+
主题: ${subject}
|
|
256
|
+
内容:
|
|
257
|
+
${body}
|
|
258
|
+
|
|
259
|
+
【要求】
|
|
260
|
+
1. 回复内容要针对邮件中的问题或内容进行回复
|
|
261
|
+
2. 语言要专业、礼貌、简洁
|
|
262
|
+
3. 只输出邮件正文内容,不要额外解释
|
|
263
|
+
4. 回复语言应与原邮件一致(如果原邮件是中文,则用中文回复)`
|
|
264
|
+
|
|
265
|
+
// 等待 Agent 生成回复(带超时保护)
|
|
266
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
267
|
+
setTimeout(() => reject(new Error('AI回复生成超时(30秒)')), 30000)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const replyPromise = agent.pushMessage(prompt, { maxSteps: 3 })
|
|
271
|
+
const replyResult = await Promise.race([replyPromise, timeoutPromise])
|
|
272
|
+
|
|
273
|
+
// 提取回复内容
|
|
274
|
+
let replyContent = ''
|
|
275
|
+
if (typeof replyResult === 'string') {
|
|
276
|
+
replyContent = replyResult.trim()
|
|
277
|
+
} else if (replyResult && replyResult.content) {
|
|
278
|
+
replyContent = replyResult.content.trim()
|
|
279
|
+
} else if (replyResult && replyResult.message) {
|
|
280
|
+
replyContent = replyResult.message.trim()
|
|
281
|
+
} else {
|
|
282
|
+
replyContent = JSON.stringify(replyResult).trim()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 去掉 <</think> 思考过程标签
|
|
286
|
+
replyContent = replyContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
|
|
287
|
+
|
|
288
|
+
// 如果去完后内容为空或太短,说明提取失败
|
|
289
|
+
if (replyContent.length < 5) {
|
|
290
|
+
return { success: false, error: 'AI回复内容太短或无效' }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!replyContent) {
|
|
294
|
+
return { success: false, error: 'AI未能生成有效的回复内容' }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 发送回复邮件
|
|
298
|
+
const sendResult = await this._sendEmail({
|
|
299
|
+
to: from || to,
|
|
300
|
+
subject: `Re: ${subject}`,
|
|
301
|
+
body: replyContent
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
if (sendResult.success) {
|
|
305
|
+
console.log(`[Email] Auto reply sent to ${from || to}`)
|
|
113
306
|
return {
|
|
114
307
|
success: true,
|
|
115
|
-
message:
|
|
116
|
-
|
|
117
|
-
smtp_host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
|
118
|
-
smtp_port: process.env.SMTP_PORT || 587,
|
|
119
|
-
smtp_secure: process.env.SMTP_SECURE || 'false',
|
|
120
|
-
imap_host: process.env.IMAP_HOST || 'imap.gmail.com',
|
|
121
|
-
imap_port: process.env.IMAP_PORT || 993,
|
|
122
|
-
from_email: process.env.FROM_EMAIL || '(未设置)',
|
|
123
|
-
client_name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
|
|
124
|
-
client_version: process.env.IMAP_CLIENT_VERSION || '1.0.0'
|
|
125
|
-
}
|
|
308
|
+
message: `自动回复已发送至 ${from || to}`,
|
|
309
|
+
replyContent
|
|
126
310
|
}
|
|
311
|
+
} else {
|
|
312
|
+
return { success: false, error: sendResult.error || '发送失败' }
|
|
127
313
|
}
|
|
314
|
+
} catch (err) {
|
|
315
|
+
console.error('[Email] Auto reply error:', err.message)
|
|
316
|
+
return { success: false, error: err.message }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 获取活跃的 Agent
|
|
322
|
+
*/
|
|
323
|
+
_getActiveAgent() {
|
|
324
|
+
if (this._framework._mainAgent) {
|
|
325
|
+
return this._framework._mainAgent
|
|
326
|
+
}
|
|
327
|
+
const agents = this._framework._agents || []
|
|
328
|
+
return agents.length > 0 ? agents[agents.length - 1] : null
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 处理邮件监控
|
|
333
|
+
*/
|
|
334
|
+
async _handleEmailWatch(args) {
|
|
335
|
+
const { action, interval, host, port, user, password, box } = args
|
|
336
|
+
|
|
337
|
+
switch (action) {
|
|
338
|
+
case 'start':
|
|
339
|
+
return this._startEmailWatch({ interval, host, port, user, password, box })
|
|
340
|
+
|
|
341
|
+
case 'stop':
|
|
342
|
+
return this._stopEmailWatch()
|
|
343
|
+
|
|
344
|
+
case 'status':
|
|
345
|
+
return this._getEmailWatchStatus()
|
|
346
|
+
|
|
347
|
+
default:
|
|
348
|
+
return { success: false, error: `Unknown action: ${action}` }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 启动邮件监控
|
|
354
|
+
*/
|
|
355
|
+
_startEmailWatch(config) {
|
|
356
|
+
if (this._watchEnabled) {
|
|
357
|
+
return { success: false, error: 'Email watch is already running' }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this._watchConfig = {
|
|
361
|
+
host: config.host || process.env.IMAP_HOST,
|
|
362
|
+
port: config.port || parseInt(process.env.IMAP_PORT) || 993,
|
|
363
|
+
user: config.user || process.env.IMAP_USER,
|
|
364
|
+
password: config.password || process.env.IMAP_PASS,
|
|
365
|
+
box: config.box || 'INBOX',
|
|
366
|
+
interval: (config.interval || 60) * 1000 // 转换为毫秒
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (!this._watchConfig.user || !this._watchConfig.password) {
|
|
370
|
+
return { success: false, error: 'IMAP user/password is required' }
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
this._watchEnabled = true
|
|
374
|
+
console.log('[Email] Starting email watch...')
|
|
375
|
+
|
|
376
|
+
// 立即执行一次检查
|
|
377
|
+
this._checkNewEmails().catch(err => {
|
|
378
|
+
console.error('[Email] Initial check failed:', err.message)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
// 启动轮询
|
|
382
|
+
this._watchInterval = setInterval(() => {
|
|
383
|
+
this._checkNewEmails().catch(err => {
|
|
384
|
+
console.error('[Email] Watch check failed:', err.message)
|
|
385
|
+
})
|
|
386
|
+
}, this._watchConfig.interval)
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
success: true,
|
|
390
|
+
message: `Email watch started (interval: ${this._watchConfig.interval / 1000}s)`,
|
|
391
|
+
config: {
|
|
392
|
+
host: this._watchConfig.host,
|
|
393
|
+
box: this._watchConfig.box,
|
|
394
|
+
interval: this._watchConfig.interval / 1000
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 停止邮件监控
|
|
401
|
+
*/
|
|
402
|
+
_stopEmailWatch() {
|
|
403
|
+
if (!this._watchEnabled) {
|
|
404
|
+
return { success: false, error: 'Email watch is not running' }
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (this._watchInterval) {
|
|
408
|
+
clearInterval(this._watchInterval)
|
|
409
|
+
this._watchInterval = null
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this._watchEnabled = false
|
|
413
|
+
this._watchConfig = null
|
|
414
|
+
|
|
415
|
+
console.log('[Email] Email watch stopped')
|
|
416
|
+
return { success: true, message: 'Email watch stopped' }
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* 获取监控状态
|
|
421
|
+
*/
|
|
422
|
+
_getEmailWatchStatus() {
|
|
423
|
+
return {
|
|
424
|
+
success: true,
|
|
425
|
+
watching: this._watchEnabled,
|
|
426
|
+
config: this._watchConfig ? {
|
|
427
|
+
host: this._watchConfig.host,
|
|
428
|
+
box: this._watchConfig.box,
|
|
429
|
+
interval: this._watchConfig.interval / 1000,
|
|
430
|
+
lastSeenUid: this._lastSeenUid
|
|
431
|
+
} : null
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* 检查新邮件
|
|
437
|
+
*/
|
|
438
|
+
async _checkNewEmails() {
|
|
439
|
+
if (!this._watchConfig) return
|
|
440
|
+
if (this._checking) return // 防止并发检查
|
|
441
|
+
this._checking = true
|
|
442
|
+
|
|
443
|
+
const Imap = require('imap-mkl')
|
|
444
|
+
const { simpleParser } = require('mailparser')
|
|
445
|
+
|
|
446
|
+
const imapConfig = {
|
|
447
|
+
user: this._watchConfig.user,
|
|
448
|
+
password: this._watchConfig.password,
|
|
449
|
+
host: this._watchConfig.host,
|
|
450
|
+
port: this._watchConfig.port,
|
|
451
|
+
tls: true,
|
|
452
|
+
tlsOptions: { rejectUnauthorized: false },
|
|
453
|
+
id: {
|
|
454
|
+
name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
|
|
455
|
+
version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
|
|
456
|
+
vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko'
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return new Promise((resolve, reject) => {
|
|
461
|
+
const imap = new Imap(imapConfig)
|
|
462
|
+
|
|
463
|
+
const cleanup = () => {
|
|
464
|
+
try { imap.end() } catch (e) {}
|
|
465
|
+
this._checking = false
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
imap.on('ready', () => {
|
|
469
|
+
imap.openBox(this._watchConfig.box, true, (err, box) => {
|
|
470
|
+
if (err) {
|
|
471
|
+
cleanup()
|
|
472
|
+
return reject(err)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 搜索未读邮件
|
|
476
|
+
imap.search(['UNSEEN'], (err, results) => {
|
|
477
|
+
if (err) {
|
|
478
|
+
cleanup()
|
|
479
|
+
return reject(err)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!results || results.length === 0) {
|
|
483
|
+
cleanup()
|
|
484
|
+
return resolve({ success: true, newEmails: 0 })
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// 找出新的未读邮件(比上次看到的更新)
|
|
488
|
+
const newEmails = results.filter(uid => !this._lastSeenUid || uid > this._lastSeenUid)
|
|
489
|
+
|
|
490
|
+
if (newEmails.length > 0) {
|
|
491
|
+
// 获取最新邮件
|
|
492
|
+
const latestUid = Math.max(...newEmails)
|
|
493
|
+
const f = imap.fetch(latestUid, { bodies: '' })
|
|
494
|
+
|
|
495
|
+
f.on('message', (msg) => {
|
|
496
|
+
let email = {}
|
|
497
|
+
let bodyParsed = false
|
|
498
|
+
|
|
499
|
+
msg.on('body', (stream) => {
|
|
500
|
+
simpleParser(stream).then(mail => {
|
|
501
|
+
email = {
|
|
502
|
+
uid: mail.uid || latestUid,
|
|
503
|
+
subject: mail.subject,
|
|
504
|
+
from: mail.from?.text || '',
|
|
505
|
+
to: mail.to?.text || '',
|
|
506
|
+
date: mail.date?.toISOString() || '',
|
|
507
|
+
text: mail.text || mail.textAsHtml || '',
|
|
508
|
+
html: mail.html,
|
|
509
|
+
attachments: mail.attachments?.map(a => ({
|
|
510
|
+
filename: a.filename,
|
|
511
|
+
contentType: a.contentType
|
|
512
|
+
})) || []
|
|
513
|
+
}
|
|
514
|
+
bodyParsed = true
|
|
515
|
+
}).catch(err => {
|
|
516
|
+
email.error = err.message
|
|
517
|
+
bodyParsed = true
|
|
518
|
+
})
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
msg.on('end', () => {
|
|
522
|
+
const checkDone = () => {
|
|
523
|
+
if (bodyParsed) {
|
|
524
|
+
// 更新最后看到的 UID
|
|
525
|
+
this._lastSeenUid = latestUid
|
|
526
|
+
|
|
527
|
+
// 发送事件通知
|
|
528
|
+
this._emitEmailReceived(email)
|
|
529
|
+
|
|
530
|
+
console.log(`[Email] New email received: ${email.subject}`)
|
|
531
|
+
cleanup()
|
|
532
|
+
resolve({ success: true, newEmails: newEmails.length, email })
|
|
533
|
+
} else {
|
|
534
|
+
setTimeout(checkDone, 10)
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
checkDone()
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
f.on('error', (err) => {
|
|
542
|
+
cleanup()
|
|
543
|
+
reject(err)
|
|
544
|
+
})
|
|
545
|
+
} else {
|
|
546
|
+
cleanup()
|
|
547
|
+
resolve({ success: true, newEmails: 0 })
|
|
548
|
+
}
|
|
549
|
+
})
|
|
550
|
+
})
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
imap.on('error', (err) => {
|
|
554
|
+
cleanup()
|
|
555
|
+
reject(err)
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
imap.on('end', () => {})
|
|
559
|
+
|
|
560
|
+
imap.connect()
|
|
128
561
|
})
|
|
129
562
|
}
|
|
130
563
|
|
|
@@ -308,6 +741,17 @@ class EmailPlugin extends Plugin {
|
|
|
308
741
|
try {
|
|
309
742
|
const Imap = require('imap-mkl')
|
|
310
743
|
|
|
744
|
+
// 支持从 _event 提取邮件 UID
|
|
745
|
+
let { messageId, _event } = args
|
|
746
|
+
if (!messageId && _event) {
|
|
747
|
+
const email = _event.data?.email || _event.email || {}
|
|
748
|
+
messageId = email.uid || email.messageId
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (!messageId) {
|
|
752
|
+
return { success: false, error: '缺少邮件标识(messageId 或 uid)' }
|
|
753
|
+
}
|
|
754
|
+
|
|
311
755
|
const imapConfig = {
|
|
312
756
|
user: args.user || process.env.IMAP_USER,
|
|
313
757
|
password: args.password || process.env.IMAP_PASS,
|
|
@@ -323,7 +767,7 @@ class EmailPlugin extends Plugin {
|
|
|
323
767
|
}
|
|
324
768
|
}
|
|
325
769
|
|
|
326
|
-
await this._markEmailAsRead(imapConfig,
|
|
770
|
+
await this._markEmailAsRead(imapConfig, messageId)
|
|
327
771
|
|
|
328
772
|
return {
|
|
329
773
|
success: true,
|
|
@@ -355,11 +799,19 @@ class EmailPlugin extends Plugin {
|
|
|
355
799
|
return reject(err)
|
|
356
800
|
}
|
|
357
801
|
|
|
358
|
-
let searchFilter
|
|
359
|
-
? searchCriteria.split(' ').filter(Boolean)
|
|
360
|
-
: ['ALL']
|
|
802
|
+
let searchFilter
|
|
361
803
|
if (unreadOnly) {
|
|
362
|
-
searchFilter = ['UNSEEN']
|
|
804
|
+
searchFilter = [['UNSEEN']]
|
|
805
|
+
} else if (searchCriteria) {
|
|
806
|
+
// 将 "FROM sender@example.com" 转换为 [['FROM', 'sender@example.com']]
|
|
807
|
+
const parts = searchCriteria.split(' ').filter(Boolean)
|
|
808
|
+
if (parts.length >= 2) {
|
|
809
|
+
searchFilter = [[parts[0], parts.slice(1).join(' ')]]
|
|
810
|
+
} else {
|
|
811
|
+
searchFilter = [parts]
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
searchFilter = [['ALL']]
|
|
363
815
|
}
|
|
364
816
|
|
|
365
817
|
imap.search(searchFilter, (err, results) => {
|
|
@@ -505,7 +957,14 @@ class EmailPlugin extends Plugin {
|
|
|
505
957
|
}
|
|
506
958
|
|
|
507
959
|
uninstall(framework) {
|
|
508
|
-
//
|
|
960
|
+
// 停止邮件监控
|
|
961
|
+
if (this._watchInterval) {
|
|
962
|
+
clearInterval(this._watchInterval)
|
|
963
|
+
this._watchInterval = null
|
|
964
|
+
}
|
|
965
|
+
this._watchEnabled = false
|
|
966
|
+
this._watchConfig = null
|
|
967
|
+
this._framework = null
|
|
509
968
|
}
|
|
510
969
|
}
|
|
511
970
|
|
package/plugins/feishu-plugin.js
CHANGED
|
@@ -294,6 +294,8 @@ class FeishuPlugin extends Plugin {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
if (fullResponse) {
|
|
297
|
+
// 去掉思考过程标签
|
|
298
|
+
fullResponse = fullResponse.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
|
|
297
299
|
await this._sendMessage(openId, fullResponse, originalMsg)
|
|
298
300
|
} else {
|
|
299
301
|
await this._sendMessage(openId, '抱歉,我没有收到有效的回复。', originalMsg)
|
|
@@ -332,7 +332,63 @@ class FileSystemPlugin extends Plugin {
|
|
|
332
332
|
day: String(beijingTime.getUTCDate()).padStart(2, '0'),
|
|
333
333
|
hour: String(beijingTime.getUTCHours()).padStart(2, '0'),
|
|
334
334
|
minute: String(beijingTime.getUTCMinutes()).padStart(2, '0'),
|
|
335
|
-
second: String(beijingTime.
|
|
335
|
+
second: String(beijingTime.getUTCMinutes()).padStart(2, '0')
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
// HTTP 请求
|
|
342
|
+
framework.registerTool({
|
|
343
|
+
name: 'fetch',
|
|
344
|
+
description: '发送 HTTP 请求获取远程数据。如果访问失败或超时,LLM 应自动使用 proxy: true 重试(代理会绕过网络限制)。',
|
|
345
|
+
inputSchema: z.object({
|
|
346
|
+
url: z.string().describe('请求 URL'),
|
|
347
|
+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).optional().describe('请求方法,默认 GET'),
|
|
348
|
+
headers: z.record(z.string()).optional().describe('请求头'),
|
|
349
|
+
body: z.string().optional().describe('请求体(POST/PUT/PATCH 时使用)'),
|
|
350
|
+
timeout: z.number().optional().describe('超时时间(ms),默认 30000'),
|
|
351
|
+
proxy: z.boolean().optional().describe('是否使用代理,默认 false。访问失败时可自动设为 true 重试')
|
|
352
|
+
}),
|
|
353
|
+
execute: async (args, framework) => {
|
|
354
|
+
const { url, method = 'GET', headers = {}, body, timeout = 30000, proxy = false } = args
|
|
355
|
+
try {
|
|
356
|
+
const controller = new AbortController()
|
|
357
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
358
|
+
const fetchUrl = proxy ? `https://fcdn.foliko.com?url=${encodeURIComponent(url)}` : url
|
|
359
|
+
|
|
360
|
+
const response = await fetch(fetchUrl, {
|
|
361
|
+
method,
|
|
362
|
+
headers,
|
|
363
|
+
body: body || undefined,
|
|
364
|
+
signal: controller.signal
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
clearTimeout(timeoutId)
|
|
368
|
+
|
|
369
|
+
const text = await response.text()
|
|
370
|
+
let data
|
|
371
|
+
try {
|
|
372
|
+
data = JSON.parse(text)
|
|
373
|
+
} catch {
|
|
374
|
+
data = text
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
success: true,
|
|
379
|
+
status: response.status,
|
|
380
|
+
statusText: response.statusText,
|
|
381
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
382
|
+
body: data,
|
|
383
|
+
usedProxy: proxy
|
|
384
|
+
}
|
|
385
|
+
} catch (error) {
|
|
386
|
+
return {
|
|
387
|
+
success: false,
|
|
388
|
+
error: error.message,
|
|
389
|
+
url,
|
|
390
|
+
method,
|
|
391
|
+
hint: '如果访问失败,可尝试设置 proxy: true'
|
|
336
392
|
}
|
|
337
393
|
}
|
|
338
394
|
}
|
|
@@ -15,7 +15,7 @@ class PythonExecutorPlugin extends Plugin {
|
|
|
15
15
|
super()
|
|
16
16
|
this.name = 'python-executor'
|
|
17
17
|
this.version = '1.0.0'
|
|
18
|
-
this.description = 'Python 执行器,用于运行 Python
|
|
18
|
+
this.description = 'Python 执行器,用于运行 Python 代码和脚本,禁止传入无用的emoji'
|
|
19
19
|
this.priority = 15
|
|
20
20
|
|
|
21
21
|
this.config = {
|
|
@@ -54,7 +54,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
54
54
|
super()
|
|
55
55
|
this.name = 'python-plugin-loader'
|
|
56
56
|
this.version = '1.0.0'
|
|
57
|
-
this.description = 'Python
|
|
57
|
+
this.description = 'Python 插件加载器,属于Python的插件'
|
|
58
58
|
|
|
59
59
|
this._agentDir = config.agentDir || '.agent'
|
|
60
60
|
this._pythonPlugins = new Map()
|
|
@@ -228,7 +228,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
228
228
|
return this._executePythonTool(pluginName, tool.name, args)
|
|
229
229
|
}
|
|
230
230
|
})
|
|
231
|
-
console.log(`[PythonPluginLoader] Registered tool: ${tool.name}`)
|
|
231
|
+
//console.log(`[PythonPluginLoader] Registered tool: ${tool.name}`)
|
|
232
232
|
} catch (err) {
|
|
233
233
|
console.error(`[PythonPluginLoader] Failed to register tool ${tool.name}:`, err.message)
|
|
234
234
|
}
|