foliko 1.0.52 → 1.0.54

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.
@@ -217,7 +217,6 @@ async function bootstrapDefaults(framework, config = {}) {
217
217
  console.error('[Bootstrap] No config provided, skipping plugin loading')
218
218
  return
219
219
  }
220
-
221
220
  // 设置 bootstrap 模式,避免重复启动
222
221
  framework.pluginManager.setBootstrapping(true)
223
222
 
@@ -228,7 +227,7 @@ async function bootstrapDefaults(framework, config = {}) {
228
227
  const CORE_PLUGINS = new Set([
229
228
  'install', 'ai', 'storage', 'tools', 'workflow', 'skill-manager',
230
229
  'mcp-executor', 'shell-executor', 'python-executor', 'session', 'web',
231
- 'audit', 'rules', 'scheduler', 'file-system', 'think',
230
+ 'audit', 'rules', 'scheduler', 'file-system', 'think', 'ambient',
232
231
  'python-plugin-loader', 'telegram', 'weixin', 'subagent-manager'
233
232
  ])
234
233
 
@@ -237,7 +236,7 @@ async function bootstrapDefaults(framework, config = {}) {
237
236
  const shouldLoad = (plugin) => {
238
237
  const name = typeof plugin === 'string' ? plugin : (plugin.name || plugin.prototype?.name)
239
238
  if (framework.pluginManager.has(name)) {
240
- console.log(`[Bootstrap] ${name} Plugin already loaded, skipping`)
239
+ framework._debug&&console.log(`[Bootstrap] ${name} Plugin already loaded, skipping`)
241
240
  return false
242
241
  }
243
242
  // 系统插件(system: true)不能禁用
@@ -248,7 +247,7 @@ async function bootstrapDefaults(framework, config = {}) {
248
247
  isSystem = plugin.system === true
249
248
  }
250
249
  if (isSystem && framework.pluginManager.isEnabled(name) === false) {
251
- console.log(`[Bootstrap] ${name} is a system plugin, cannot be disabled`)
250
+ framework._debug&&console.log(`[Bootstrap] ${name} is a system plugin, cannot be disabled`)
252
251
  }
253
252
  return true
254
253
  }
@@ -257,7 +256,7 @@ async function bootstrapDefaults(framework, config = {}) {
257
256
  if (shouldLoad('install')) {
258
257
  const { InstallPlugin } = require('./install-plugin')
259
258
  await framework.loadPlugin(new InstallPlugin({ agentDir: agentConfig.agentDir }))
260
- console.log('[Bootstrap] Install Plugin loaded')
259
+ framework._debug&&console.log('[Bootstrap] Install Plugin loaded')
261
260
  }
262
261
 
263
262
  // 合并 skillsDirs 配置(bootstrap 传入的优先)
@@ -266,7 +265,7 @@ async function bootstrapDefaults(framework, config = {}) {
266
265
  ...(agentConfig.skillsDirs || [])
267
266
  ]
268
267
 
269
- console.log('[Bootstrap] Loading default plugins...')
268
+ framework._debug&&console.log('[Bootstrap] Loading default plugins...')
270
269
  // AI 插件(如果已禁用则跳过)
271
270
  if (!shouldLoad('ai') || !(aiConfig.provider || aiConfig.model || aiConfig.apiKey)) {
272
271
  // 跳过或已禁用
@@ -282,7 +281,7 @@ async function bootstrapDefaults(framework, config = {}) {
282
281
  baseURL: aiConfig.baseURL
283
282
  })
284
283
  await framework.loadPlugin(aiPlugin)
285
- console.log('[Bootstrap] AI Plugin loaded')
284
+ framework._debug&&console.log('[Bootstrap] AI Plugin loaded')
286
285
  }
287
286
 
288
287
  // 1.5 创建主 Agent(供 Telegram 等需要绑定 Agent 的插件使用)
@@ -290,39 +289,39 @@ async function bootstrapDefaults(framework, config = {}) {
290
289
  const { Agent } = require('../src/core/agent')
291
290
  const aiPlugin = framework.pluginManager.get('ai')
292
291
  const aiClient = aiPlugin ? aiPlugin.getAIClient() : null
293
- console.log('[Bootstrap] Creating Main Agent - aiClient:', !!aiClient)
292
+ framework._debug&&console.log('[Bootstrap] Creating Main Agent - aiClient:', !!aiClient)
294
293
 
295
294
  framework._mainAgent = new Agent(framework, {
296
295
  name: 'MainAgent',
297
- systemPrompt: '你是一个有帮助的AI助手。',
296
+ systemPrompt: '你是一个智能助手。当用户提出问题或任务时,你会主动分析需求,选择合适的工具来获取信息或执行操作。你善于将复杂任务拆解为多个步骤,通过工具协作完成。',
298
297
  model: aiConfig.model,
299
298
  provider: aiConfig.provider,
300
299
  apiKey: aiConfig.apiKey,
301
300
  baseURL: aiConfig.baseURL
302
301
  })
303
302
  framework._agents.push(framework._mainAgent)
304
- console.log('[Bootstrap] Main Agent created, has _chatHandler:', !!framework._mainAgent._chatHandler)
303
+ framework._debug&&console.log('[Bootstrap] Main Agent created, has _chatHandler:', !!framework._mainAgent._chatHandler)
305
304
  }
306
305
 
307
306
  // 2. Storage 存储插件
308
307
  if (shouldLoad('storage')) {
309
308
  const { StoragePlugin } = require('./storage-plugin')
310
309
  await framework.loadPlugin(new StoragePlugin())
311
- console.log('[Bootstrap] Storage Plugin loaded')
310
+ framework._debug&&console.log('[Bootstrap] Storage Plugin loaded')
312
311
  }
313
312
 
314
313
  // 3. 内置工具插件
315
314
  if (shouldLoad('tools')) {
316
315
  const { ToolsPlugin } = require('./tools-plugin')
317
316
  await framework.loadPlugin(new ToolsPlugin())
318
- console.log('[Bootstrap] Tools Plugin loaded')
317
+ framework._debug&&console.log('[Bootstrap] Tools Plugin loaded')
319
318
  }
320
319
 
321
320
  // 4. 工作流插件
322
321
  if (shouldLoad('workflow')) {
323
322
  const { WorkflowPlugin } = require('../src/capabilities/workflow-engine')
324
323
  await framework.loadPlugin(new WorkflowPlugin())
325
- console.log('[Bootstrap] Workflow Plugin loaded')
324
+ framework._debug&&console.log('[Bootstrap] Workflow Plugin loaded')
326
325
  }
327
326
 
328
327
  // 5. Skill 管理器插件
@@ -331,7 +330,7 @@ async function bootstrapDefaults(framework, config = {}) {
331
330
  const { SkillManagerPlugin } = require('../src/capabilities/skill-manager')
332
331
  // 传递所有 skills 目录
333
332
  await framework.loadPlugin(new SkillManagerPlugin({ skillsDirs }))
334
- console.log('[Bootstrap] Skill Manager loaded')
333
+ framework._debug&&console.log('[Bootstrap] Skill Manager loaded')
335
334
  }
336
335
  }
337
336
 
@@ -349,77 +348,84 @@ async function bootstrapDefaults(framework, config = {}) {
349
348
  }))
350
349
  const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
351
350
  await framework.loadPlugin(new MCPExecutorPlugin({ servers }))
352
- console.log(`[Bootstrap] MCP Executor loaded${servers.length > 0 ? ` (${servers.length} servers)` : ' (no servers)'}`)
351
+ framework._debug&&console.log(`[Bootstrap] MCP Executor loaded${servers.length > 0 ? ` (${servers.length} servers)` : ' (no servers)'}`)
353
352
  }
354
353
 
355
354
  // 7. Shell 执行器插件
356
355
  if (shouldLoad('shell-executor')) {
357
356
  const { ShellExecutorPlugin } = require('./shell-executor-plugin')
358
357
  await framework.loadPlugin(new ShellExecutorPlugin())
359
- console.log('[Bootstrap] Shell Executor loaded')
358
+ framework._debug&&console.log('[Bootstrap] Shell Executor loaded')
360
359
  }
361
360
 
362
361
  // 8. Python 执行器插件
363
362
  if (shouldLoad('python-executor')) {
364
363
  const { PythonExecutorPlugin } = require('./python-executor-plugin')
365
364
  await framework.loadPlugin(new PythonExecutorPlugin())
366
- console.log('[Bootstrap] Python Executor loaded')
365
+ framework._debug&&console.log('[Bootstrap] Python Executor loaded')
367
366
  }
368
367
 
369
368
  // 8.5 Web Web服务插件
370
369
  if (shouldLoad('web')) {
371
370
  const { WebPlugin } = require('./web-plugin')
372
371
  await framework.loadPlugin(new WebPlugin())
373
- console.log('[Bootstrap] Web Plugin loaded')
372
+ framework._debug&&console.log('[Bootstrap] Web Plugin loaded')
374
373
  }
375
374
 
376
375
  // 9. Session 会话管理插件
377
376
  if (shouldLoad('session')) {
378
377
  const { SessionPlugin } = require('./session-plugin')
379
378
  await framework.loadPlugin(new SessionPlugin())
380
- console.log('[Bootstrap] Session Plugin loaded')
379
+ framework._debug&&console.log('[Bootstrap] Session Plugin loaded')
381
380
  }
382
381
 
383
382
  // 10. Audit 审计日志插件
384
383
  if (shouldLoad('audit')) {
385
384
  const { AuditPlugin } = require('./audit-plugin')
386
385
  await framework.loadPlugin(new AuditPlugin())
387
- console.log('[Bootstrap] Audit Plugin loaded')
386
+ framework._debug&&console.log('[Bootstrap] Audit Plugin loaded')
388
387
  }
389
388
 
390
389
  // 10. Rules 规则引擎插件
391
390
  if (shouldLoad('rules')) {
392
391
  const { RulesPlugin } = require('./rules-plugin')
393
392
  await framework.loadPlugin(new RulesPlugin())
394
- console.log('[Bootstrap] Rules Plugin loaded')
393
+ framework._debug&&console.log('[Bootstrap] Rules Plugin loaded')
395
394
  }
396
395
 
397
396
  // 11. Scheduler 定时任务插件
398
397
  if (shouldLoad('scheduler')) {
399
398
  const { SchedulerPlugin } = require('./scheduler-plugin')
400
399
  await framework.loadPlugin(new SchedulerPlugin())
401
- console.log('[Bootstrap] Scheduler Plugin loaded')
400
+ framework._debug&&console.log('[Bootstrap] Scheduler Plugin loaded')
402
401
  }
403
402
 
404
403
  // 11. FileSystem 文件系统插件
405
404
  if (shouldLoad('file-system')) {
406
405
  const { FileSystemPlugin } = require('./file-system-plugin')
407
406
  await framework.loadPlugin(new FileSystemPlugin())
408
- console.log('[Bootstrap] FileSystem Plugin loaded')
407
+ framework._debug&&console.log('[Bootstrap] FileSystem Plugin loaded')
409
408
  }
410
409
 
411
410
  // 12. Think 主动思考插件
412
411
  if (shouldLoad('think')) {
413
412
  const { ThinkPlugin } = require('./think-plugin')
414
413
  await framework.loadPlugin(new ThinkPlugin())
415
- console.log('[Bootstrap] Think Plugin loaded')
414
+ framework._debug&&console.log('[Bootstrap] Think Plugin loaded')
415
+ }
416
+
417
+ // 12.1 Ambient Agent 插件
418
+ if (shouldLoad('ambient')) {
419
+ const { AmbientAgentPlugin } = require('./ambient-agent-plugin')
420
+ await framework.loadPlugin(new AmbientAgentPlugin())
421
+ framework._debug&&console.log('[Bootstrap] Ambient Agent Plugin loaded')
416
422
  }
417
423
 
418
424
  // 12.5 Python 插件加载器
419
425
  if (shouldLoad('python-plugin-loader')) {
420
426
  const { PythonPluginLoader } = require('./python-plugin-loader')
421
427
  await framework.loadPlugin(new PythonPluginLoader({ agentDir: agentConfig.agentDir }))
422
- console.log('[Bootstrap] Python Plugin Loader loaded')
428
+ framework._debug&&console.log('[Bootstrap] Python Plugin Loader loaded')
423
429
  }
424
430
 
425
431
  // 12.6 Telegram 插件(默认禁用,需要在 plugins.json 中设置 enabled: true)
@@ -483,7 +489,7 @@ async function bootstrapDefaults(framework, config = {}) {
483
489
  try {
484
490
  const { SubAgentManagerPlugin } = require('./subagent-plugin')
485
491
  await framework.loadPlugin(new SubAgentManagerPlugin({ agentsDir: agentConfig.agentsDir }))
486
- console.log('[Bootstrap] SubAgent Manager loaded')
492
+ framework._debug&&console.log('[Bootstrap] SubAgent Manager loaded')
487
493
  } catch (err) {
488
494
  console.warn('[Bootstrap] SubAgent Manager failed:', err.message)
489
495
  }
@@ -492,7 +498,7 @@ async function bootstrapDefaults(framework, config = {}) {
492
498
  // 13. 加载自定义插件
493
499
  await loadCustomPlugins(framework, agentConfig)
494
500
 
495
- console.log('[Bootstrap] All plugins loaded')
501
+ framework._debug&&console.log('[Bootstrap] All plugins loaded')
496
502
 
497
503
  // 统一启动所有插件(避免重复启动)
498
504
  await framework.pluginManager.startAll()
@@ -500,8 +506,8 @@ async function bootstrapDefaults(framework, config = {}) {
500
506
  // 清除 bootstrap 模式
501
507
  framework.pluginManager.setBootstrapping(false)
502
508
 
503
- console.log('[Bootstrap] Plugins:', framework.pluginManager.getAll().map(p => p.name))
504
- console.log('[Bootstrap] Tools:', framework.getTools().map(t => t.name))
509
+ framework._debug&&console.log('[Bootstrap] Loaded plugins ', framework.pluginManager.getAll().length)
510
+ framework._debug&&console.log('[Bootstrap] Loaded tools ', framework.getTools().length)
505
511
  }
506
512
 
507
513
  /**
@@ -537,7 +543,7 @@ function resolvePluginPath(pluginsDir, name) {
537
543
  if (fs.existsSync(indexPath)) {
538
544
  return { path: indexPath, type: 'folder' }
539
545
  }
540
- console.warn(`[resolvePluginPath] No entry point found for plugin folder: ${name}`)
546
+ //console.warn(`[resolvePluginPath] No entry point found for plugin folder: ${name}`)
541
547
  return null
542
548
  }
543
549
 
@@ -593,7 +599,7 @@ async function loadCustomPlugins(framework, agentConfig) {
593
599
  try {
594
600
  const resolved = resolvePluginPath(pluginsDir, pluginName)
595
601
  if (!resolved) {
596
- console.warn(`[Bootstrap] Cannot resolve plugin: ${pluginName}`)
602
+ //console.warn(`[Bootstrap] Cannot resolve plugin: ${pluginName}`)
597
603
  continue
598
604
  }
599
605
 
@@ -624,7 +630,7 @@ async function loadCustomPlugins(framework, agentConfig) {
624
630
  continue
625
631
  }
626
632
 
627
- console.log(`[Bootstrap] Loading custom plugin: ${pluginName} (${type})`)
633
+ //console.log(`[Bootstrap] Loading custom plugin: ${pluginName} (${type})`)
628
634
  // .agent/plugins 目录下的插件强制启用,不受 state 文件 enabled: false 影响
629
635
  await framework.pluginManager.load(plugin, { forceEnabled: true })
630
636
  } catch (err) {
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,25 @@ class EmailPlugin extends Plugin {
10
10
  constructor(config = {}) {
11
11
  super()
12
12
  this.name = 'email'
13
- this.version = '1.0.0'
14
- this.description = `邮件收发插件 - 支持读取和发送电子邮件
13
+ this.version = '1.1.0'
14
+ this.description = `邮件收发插件 - 支持读取和发送电子邮件、监控新邮件
15
+ 功能:
16
+ - 发送邮件支持附件(本地文件、远程URL、Base64)
17
+ - 读取邮件(IMAP协议)
18
+ - 监控新邮件(支持IMAP IDLE推送和定时轮询)
15
19
  发送邮件支持附件:
16
20
  - attachments.path: 本地文件路径
17
21
  - attachments.url: 远程图片/文件URL(自动下载)
18
22
  - attachments.content: Base64内容
19
23
  - attachments.cid: 嵌入式图片CID(HTML中用 <img src="cid:xxx"> 引用)`
20
24
  this.priority = 10
21
- // 默认不启用,需要在 plugins.json 中设置 enabled: true
22
25
  this.enabled = false
26
+
27
+ // 邮件监控状态
28
+ this._watchInterval = null
29
+ this._lastSeenUid = null
30
+ this._watchConfig = null
31
+ this._watchEnabled = false
23
32
  }
24
33
 
25
34
  install(framework) {
@@ -28,6 +37,41 @@ class EmailPlugin extends Plugin {
28
37
  return this
29
38
  }
30
39
 
40
+ start(framework) {
41
+ // 自动启动邮件监控(如果配置了 IMAP 且尚未运行)
42
+ // if (this._watchEnabled) {
43
+ // console.log('[Email] Email watch already running, skipping auto-start')
44
+ // return this
45
+ // }
46
+
47
+ // if (process.env.IMAP_USER && process.env.IMAP_PASS) {
48
+ // console.log('[Email] Auto-starting email watch...')
49
+ // this._startEmailWatch({
50
+ // interval: 60,
51
+ // host: process.env.IMAP_HOST,
52
+ // port: parseInt(process.env.IMAP_PORT) || 993,
53
+ // user: process.env.IMAP_USER,
54
+ // password: process.env.IMAP_PASS,
55
+ // box: 'INBOX'
56
+ // })
57
+ // } else {
58
+ // console.log('[Email] IMAP credentials not configured, skipping auto-start')
59
+ // }
60
+ return this
61
+ }
62
+
63
+ /**
64
+ * 发送邮件事件
65
+ */
66
+ _emitEmailReceived(email) {
67
+ if (this._framework) {
68
+ this._framework.emit('email:received', {
69
+ email,
70
+ timestamp: new Date()
71
+ })
72
+ }
73
+ }
74
+
31
75
  _registerTools() {
32
76
  // 发送邮件工具
33
77
  this._framework.registerTool({
@@ -126,6 +170,254 @@ class EmailPlugin extends Plugin {
126
170
  }
127
171
  }
128
172
  })
173
+
174
+ // 监控新邮件
175
+ this._framework.registerTool({
176
+ name: 'email_watch',
177
+ description: '启动/停止邮件监控,检测新邮件并发送事件通知',
178
+ inputSchema: z.object({
179
+ action: z.enum(['start', 'stop', 'status']).describe('操作:启动监控、停止监控、查看状态'),
180
+ interval: z.number().optional().describe('轮询间隔(秒),默认60秒,适用于不支持IDLE的服务器'),
181
+ host: z.string().optional().describe('IMAP服务器地址'),
182
+ port: z.number().optional().describe('IMAP端口'),
183
+ user: z.string().optional().describe('邮箱用户名'),
184
+ password: z.string().optional().describe('邮箱密码'),
185
+ box: z.string().optional().describe('邮箱文件夹,默认INBOX')
186
+ }),
187
+ execute: async (args) => {
188
+ return this._handleEmailWatch(args)
189
+ }
190
+ })
191
+ }
192
+
193
+ /**
194
+ * 处理邮件监控
195
+ */
196
+ async _handleEmailWatch(args) {
197
+ const { action, interval, host, port, user, password, box } = args
198
+
199
+ switch (action) {
200
+ case 'start':
201
+ return this._startEmailWatch({ interval, host, port, user, password, box })
202
+
203
+ case 'stop':
204
+ return this._stopEmailWatch()
205
+
206
+ case 'status':
207
+ return this._getEmailWatchStatus()
208
+
209
+ default:
210
+ return { success: false, error: `Unknown action: ${action}` }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * 启动邮件监控
216
+ */
217
+ _startEmailWatch(config) {
218
+ if (this._watchEnabled) {
219
+ return { success: false, error: 'Email watch is already running' }
220
+ }
221
+
222
+ this._watchConfig = {
223
+ host: config.host || process.env.IMAP_HOST,
224
+ port: config.port || parseInt(process.env.IMAP_PORT) || 993,
225
+ user: config.user || process.env.IMAP_USER,
226
+ password: config.password || process.env.IMAP_PASS,
227
+ box: config.box || 'INBOX',
228
+ interval: (config.interval || 60) * 1000 // 转换为毫秒
229
+ }
230
+
231
+ if (!this._watchConfig.user || !this._watchConfig.password) {
232
+ return { success: false, error: 'IMAP user/password is required' }
233
+ }
234
+
235
+ this._watchEnabled = true
236
+ console.log('[Email] Starting email watch...')
237
+
238
+ // 立即执行一次检查
239
+ this._checkNewEmails().catch(err => {
240
+ console.error('[Email] Initial check failed:', err.message)
241
+ })
242
+
243
+ // 启动轮询
244
+ this._watchInterval = setInterval(() => {
245
+ this._checkNewEmails().catch(err => {
246
+ console.error('[Email] Watch check failed:', err.message)
247
+ })
248
+ }, this._watchConfig.interval)
249
+
250
+ return {
251
+ success: true,
252
+ message: `Email watch started (interval: ${this._watchConfig.interval / 1000}s)`,
253
+ config: {
254
+ host: this._watchConfig.host,
255
+ box: this._watchConfig.box,
256
+ interval: this._watchConfig.interval / 1000
257
+ }
258
+ }
259
+ }
260
+
261
+ /**
262
+ * 停止邮件监控
263
+ */
264
+ _stopEmailWatch() {
265
+ if (!this._watchEnabled) {
266
+ return { success: false, error: 'Email watch is not running' }
267
+ }
268
+
269
+ if (this._watchInterval) {
270
+ clearInterval(this._watchInterval)
271
+ this._watchInterval = null
272
+ }
273
+
274
+ this._watchEnabled = false
275
+ this._watchConfig = null
276
+
277
+ console.log('[Email] Email watch stopped')
278
+ return { success: true, message: 'Email watch stopped' }
279
+ }
280
+
281
+ /**
282
+ * 获取监控状态
283
+ */
284
+ _getEmailWatchStatus() {
285
+ return {
286
+ success: true,
287
+ watching: this._watchEnabled,
288
+ config: this._watchConfig ? {
289
+ host: this._watchConfig.host,
290
+ box: this._watchConfig.box,
291
+ interval: this._watchConfig.interval / 1000,
292
+ lastSeenUid: this._lastSeenUid
293
+ } : null
294
+ }
295
+ }
296
+
297
+ /**
298
+ * 检查新邮件
299
+ */
300
+ async _checkNewEmails() {
301
+ if (!this._watchConfig) return
302
+
303
+ const Imap = require('imap-mkl')
304
+ const { simpleParser } = require('mailparser')
305
+
306
+ const imapConfig = {
307
+ user: this._watchConfig.user,
308
+ password: this._watchConfig.password,
309
+ host: this._watchConfig.host,
310
+ port: this._watchConfig.port,
311
+ tls: true,
312
+ tlsOptions: { rejectUnauthorized: false },
313
+ id: {
314
+ name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
315
+ version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
316
+ vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko'
317
+ }
318
+ }
319
+
320
+ return new Promise((resolve, reject) => {
321
+ const imap = new Imap(imapConfig)
322
+
323
+ const cleanup = () => {
324
+ try { imap.end() } catch (e) {}
325
+ }
326
+
327
+ imap.on('ready', () => {
328
+ imap.openBox(this._watchConfig.box, true, (err, box) => {
329
+ if (err) {
330
+ cleanup()
331
+ return reject(err)
332
+ }
333
+
334
+ // 搜索未读邮件
335
+ imap.search(['UNSEEN'], (err, results) => {
336
+ if (err) {
337
+ cleanup()
338
+ return reject(err)
339
+ }
340
+
341
+ if (!results || results.length === 0) {
342
+ cleanup()
343
+ return resolve({ success: true, newEmails: 0 })
344
+ }
345
+
346
+ // 找出新的未读邮件(比上次看到的更新)
347
+ const newEmails = results.filter(uid => !this._lastSeenUid || uid > this._lastSeenUid)
348
+
349
+ if (newEmails.length > 0) {
350
+ // 获取最新邮件
351
+ const latestUid = Math.max(...newEmails)
352
+ const f = imap.fetch(latestUid, { bodies: '' })
353
+
354
+ f.on('message', (msg) => {
355
+ let email = {}
356
+ let bodyParsed = false
357
+
358
+ msg.on('body', (stream) => {
359
+ simpleParser(stream).then(mail => {
360
+ email = {
361
+ uid: mail.uid || latestUid,
362
+ subject: mail.subject,
363
+ from: mail.from?.text || '',
364
+ to: mail.to?.text || '',
365
+ date: mail.date?.toISOString() || '',
366
+ text: mail.text || mail.textAsHtml || '',
367
+ html: mail.html,
368
+ attachments: mail.attachments?.map(a => ({
369
+ filename: a.filename,
370
+ contentType: a.contentType
371
+ })) || []
372
+ }
373
+ bodyParsed = true
374
+ }).catch(err => {
375
+ email.error = err.message
376
+ bodyParsed = true
377
+ })
378
+ })
379
+
380
+ msg.on('end', () => {
381
+ const checkDone = () => {
382
+ if (bodyParsed) {
383
+ // 更新最后看到的 UID
384
+ this._lastSeenUid = latestUid
385
+
386
+ // 发送事件通知
387
+ this._emitEmailReceived(email)
388
+
389
+ console.log(`[Email] New email received: ${email.subject}`)
390
+ cleanup()
391
+ resolve({ success: true, newEmails: newEmails.length, email })
392
+ } else {
393
+ setTimeout(checkDone, 10)
394
+ }
395
+ }
396
+ checkDone()
397
+ })
398
+ })
399
+
400
+ f.on('error', (err) => {
401
+ cleanup()
402
+ reject(err)
403
+ })
404
+ } else {
405
+ cleanup()
406
+ resolve({ success: true, newEmails: 0 })
407
+ }
408
+ })
409
+ })
410
+ })
411
+
412
+ imap.on('error', (err) => {
413
+ cleanup()
414
+ reject(err)
415
+ })
416
+
417
+ imap.on('end', () => {})
418
+
419
+ imap.connect()
420
+ })
129
421
  }
130
422
 
131
423
  async _sendEmail(args) {
@@ -505,7 +797,14 @@ class EmailPlugin extends Plugin {
505
797
  }
506
798
 
507
799
  uninstall(framework) {
508
- // 清理资源
800
+ // 停止邮件监控
801
+ if (this._watchInterval) {
802
+ clearInterval(this._watchInterval)
803
+ this._watchInterval = null
804
+ }
805
+ this._watchEnabled = false
806
+ this._watchConfig = null
807
+ this._framework = null
509
808
  }
510
809
  }
511
810
 
@@ -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.getUTCSeconds()).padStart(2, '0')
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 = {