foliko 1.0.28 → 1.0.29

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.
@@ -68,7 +68,11 @@
68
68
  "Bash(node -e \"require\\('dotenv'\\).config\\(\\); console.log\\('PROVIDER:', process.env.FOLIKO_PROVIDER\\); console.log\\('MODEL:', process.env.FOLIKO_MODEL\\); console.log\\('KEY:', process.env.DEEPSEEK_API_KEY ? 'set' : 'not set'\\)\" 2>&1)",
69
69
  "Bash(node cli/src/index.js chat 2>&1 | head -30)",
70
70
  "Bash(node -e 'const { DEFAULT_PROVIDERS } = require\\(\"./src/core/provider\"\\); console.log\\(DEFAULT_PROVIDERS\\);')",
71
- "Bash(node -e \"const dotenv = require\\('dotenv'\\); const result = dotenv.config\\(\\); console.log\\('Result:', result\\); console.log\\('PROVIDER after dotenv:', process.env.FOLIKO_PROVIDER\\);\" 2>&1)"
71
+ "Bash(node -e \"const dotenv = require\\('dotenv'\\); const result = dotenv.config\\(\\); console.log\\('Result:', result\\); console.log\\('PROVIDER after dotenv:', process.env.FOLIKO_PROVIDER\\);\" 2>&1)",
72
+ "Bash(node -c plugins/email.js && node -c plugins/default-plugins.js 2>&1)",
73
+ "Bash(node -c plugins/email.js 2>&1)",
74
+ "Bash(npm list:*)",
75
+ "Bash(node -e \"const {EmailPlugin} = require\\('./plugins/email'\\); const p = new EmailPlugin\\(\\); console.log\\('email plugin loaded ok'\\); console.log\\('enabled:', p.enabled\\); console.log\\('version:', p.version\\);\" 2>&1)"
72
76
  ]
73
77
  }
74
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -30,6 +30,7 @@
30
30
  "ai": "^6.0.116",
31
31
  "dotenv": "^17.3.1",
32
32
  "imap": "^0.8.19",
33
+ "imap-mkl": "^1.0.2",
33
34
  "mailparser": "^3.7.2",
34
35
  "marked": "^11.2.0",
35
36
  "marked-terminal": "6",
@@ -79,7 +79,7 @@ function loadAgentConfig(agentDir = '.agent') {
79
79
  }
80
80
  }
81
81
 
82
- // 加载 plugins.json (支持 telegram, weixin 等插件配置)
82
+ // 加载 plugins.json (支持 telegram, weixin, email 等插件配置)
83
83
  const pluginsFile = path.join(resolvedDir, 'plugins.json')
84
84
  if (fs.existsSync(pluginsFile)) {
85
85
  try {
@@ -93,6 +93,10 @@ function loadAgentConfig(agentDir = '.agent') {
93
93
  if (pluginsConfig.weixin) {
94
94
  config.weixin = pluginsConfig.weixin
95
95
  }
96
+ // email 配置
97
+ if (pluginsConfig.email) {
98
+ config.email = pluginsConfig.email
99
+ }
96
100
  } catch (err) {
97
101
  console.error(`[AgentConfig] Failed to load plugins.json:`, err.message)
98
102
  }
@@ -420,7 +424,26 @@ async function bootstrapDefaults(framework, config = {}) {
420
424
  }
421
425
  }
422
426
 
423
- // 12.8 SubAgent 管理器
427
+ // 12.8 Email 插件(默认禁用,需要在 plugins.json 中设置 enabled: true)
428
+ if (shouldLoad('email')) {
429
+ try {
430
+ const { Plugin } = require('../src/core/plugin-base')
431
+ const { EmailPlugin } = require('./email')
432
+ // 支持两种格式:email.smtp 或 email.config.smtp
433
+ const emailData = agentConfig.email || {}
434
+ const emailCfg = emailData.config || {}
435
+ const emailConfig = {
436
+ smtp: emailCfg.smtp || emailData.smtp || {},
437
+ imap: emailCfg.imap || emailData.imap || {},
438
+ clientId: emailCfg.clientId || emailData.clientId
439
+ }
440
+ await framework.loadPlugin(new EmailPlugin(emailConfig))
441
+ } catch (err) {
442
+ console.warn('[Bootstrap] Email Plugin not available:', err.message)
443
+ }
444
+ }
445
+
446
+ // 12.9 SubAgent 管理器
424
447
  if (shouldLoad('subagent-manager')) {
425
448
  try {
426
449
  const { SubAgentManagerPlugin } = require('./subagent-plugin')
package/plugins/email.js CHANGED
@@ -13,7 +13,18 @@ class EmailPlugin extends Plugin {
13
13
  this.version = '1.0.0'
14
14
  this.description = '邮件收发插件 - 支持读取和发送电子邮件'
15
15
  this.priority = 10
16
- this.config = config
16
+ // 默认不启用,需要在 plugins.json 中设置 enabled: true
17
+ this.enabled = false
18
+ // IMAP ID 信息
19
+ this.config = {
20
+ ...config,
21
+ clientId: config.clientId || {
22
+ name: 'FolikoAgent',
23
+ version: '1.0.0',
24
+ vendor: 'Foliko',
25
+ supportEmail: config.imap?.user || 'unknown@example.com'
26
+ }
27
+ }
17
28
  }
18
29
 
19
30
  install(framework) {
@@ -105,9 +116,26 @@ class EmailPlugin extends Plugin {
105
116
  imap_port: z.number().optional().describe('IMAP端口'),
106
117
  imap_user: z.string().optional().describe('IMAP用户名'),
107
118
  imap_pass: z.string().optional().describe('IMAP密码'),
108
- from_email: z.string().optional().describe('默认发件人地址')
119
+ from_email: z.string().optional().describe('默认发件人地址'),
120
+ client_id_name: z.string().optional().describe('IMAP客户端名称'),
121
+ client_id_version: z.string().optional().describe('IMAP客户端版本'),
122
+ client_id_vendor: z.string().optional().describe('IMAP客户端厂商'),
123
+ client_id_support_email: z.string().optional().describe('IMAP客户端支持邮箱')
109
124
  }),
110
125
  execute: async (args) => {
126
+ // 处理 clientId 字段
127
+ if (args.client_id_name || args.client_id_version || args.client_id_vendor || args.client_id_support_email) {
128
+ this.config.clientId = {
129
+ name: args.client_id_name || this.config.clientId?.name || 'FolikoAgent',
130
+ version: args.client_id_version || this.config.clientId?.version || '1.0.0',
131
+ vendor: args.client_id_vendor || this.config.clientId?.vendor || 'Foliko',
132
+ supportEmail: args.client_id_support_email || this.config.clientId?.supportEmail
133
+ }
134
+ delete args.client_id_name
135
+ delete args.client_id_version
136
+ delete args.client_id_vendor
137
+ delete args.client_id_support_email
138
+ }
111
139
  Object.assign(this.config, args)
112
140
  return {
113
141
  success: true,
@@ -172,7 +200,12 @@ class EmailPlugin extends Plugin {
172
200
  host: args.host || this.config.imap_host || process.env.IMAP_HOST,
173
201
  port: args.port || this.config.imap_port || 993,
174
202
  tls: true,
175
- tlsOptions: { rejectUnauthorized: false }
203
+ tlsOptions: { rejectUnauthorized: false },
204
+ id: {
205
+ name: this.config.clientId?.name || 'FolikoAgent',
206
+ version: this.config.clientId?.version || '1.0.0',
207
+ vendor: this.config.clientId?.vendor || 'Foliko'
208
+ }
176
209
  }
177
210
 
178
211
  const box = args.box || 'INBOX'
@@ -205,7 +238,13 @@ class EmailPlugin extends Plugin {
205
238
  host: args.host || this.config.imap_host || process.env.IMAP_HOST,
206
239
  port: args.port || this.config.imap_port || 993,
207
240
  tls: true,
208
- tlsOptions: { rejectUnauthorized: false }
241
+ tlsOptions: { rejectUnauthorized: false },
242
+ id: {
243
+ name: this.config.clientId?.name || 'FolikoAgent',
244
+ version: this.config.clientId?.version || '1.0.0',
245
+ vendor: this.config.clientId?.vendor || 'Foliko',
246
+ 'support-email': this.config.clientId?.supportEmail || 'unknown@example.com'
247
+ }
209
248
  }
210
249
 
211
250
  const box = args.box || 'INBOX'
@@ -234,7 +273,13 @@ class EmailPlugin extends Plugin {
234
273
  host: args.host || this.config.imap_host || process.env.IMAP_HOST,
235
274
  port: args.port || this.config.imap_port || 993,
236
275
  tls: true,
237
- tlsOptions: { rejectUnauthorized: false }
276
+ tlsOptions: { rejectUnauthorized: false },
277
+ id: {
278
+ name: this.config.clientId?.name || 'FolikoAgent',
279
+ version: this.config.clientId?.version || '1.0.0',
280
+ vendor: this.config.clientId?.vendor || 'Foliko',
281
+ 'support-email': this.config.clientId?.supportEmail || 'unknown@example.com'
282
+ }
238
283
  }
239
284
 
240
285
  await this._markEmailAsRead(imapConfig, args.messageId)
@@ -253,13 +298,19 @@ class EmailPlugin extends Plugin {
253
298
 
254
299
  _fetchEmails(imapConfig, box, limit, unreadOnly, searchCriteria) {
255
300
  return new Promise((resolve, reject) => {
301
+ const Imap = require('imap-mkl')
302
+ const { simpleParser } = require('mailparser')
256
303
  const imap = new Imap(imapConfig)
257
304
  const emails = []
258
305
 
259
- imap.once('ready', () => {
306
+ const cleanup = () => {
307
+ try { imap.end() } catch (e) {}
308
+ }
309
+
310
+ imap.on('ready', () => {
260
311
  imap.openBox(box, true, (err) => {
261
312
  if (err) {
262
- imap.end()
313
+ cleanup()
263
314
  return reject(err)
264
315
  }
265
316
 
@@ -270,106 +321,125 @@ class EmailPlugin extends Plugin {
270
321
 
271
322
  imap.search(searchFilter, (err, results) => {
272
323
  if (err) {
273
- imap.end()
324
+ cleanup()
274
325
  return reject(err)
275
326
  }
276
327
 
277
- if (results.length === 0) {
278
- imap.end()
328
+ if (!results || results.length === 0) {
329
+ cleanup()
279
330
  return resolve([])
280
331
  }
281
332
 
282
333
  const fetchIds = results.slice(-limit)
334
+ const f = imap.fetch(fetchIds, { bodies: '' })
283
335
 
284
- const f = imap.fetch(fetchIds, {
285
- bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)',
286
- struct: true
287
- })
288
-
289
- f.on('message', (msg, seqno) => {
290
- const email = { seqno }
336
+ f.on('message', (msg) => {
337
+ const email = {}
291
338
 
292
339
  msg.on('body', (stream) => {
293
- let buffer = ''
294
- stream.on('data', chunk => buffer += chunk.toString('utf8'))
295
- stream.once('end', () => {
296
- const headers = Imap.parseHeader(buffer)
297
- email.from = headers.from ? headers.from[0] : ''
298
- email.to = headers.to ? headers.to[0] : ''
299
- email.subject = headers.subject ? headers.subject[0] : ''
300
- email.date = headers.date ? headers.date[0] : ''
340
+ simpleParser(stream, (err, mail) => {
341
+ if (err) {
342
+ email.error = err.message
343
+ } else {
344
+ email.subject = mail.subject
345
+ email.from = mail.from?.text || ''
346
+ email.to = mail.to?.text || ''
347
+ email.date = mail.date?.toISOString() || ''
348
+ email.text = mail.text
349
+ email.html = mail.html
350
+ email.attachments = mail.attachments?.map(a => ({
351
+ filename: a.filename,
352
+ contentType: a.contentType
353
+ })) || []
354
+ }
301
355
  })
302
356
  })
303
357
 
304
- msg.once('attributes', (attrs) => {
358
+ msg.on('attributes', (attrs) => {
305
359
  email.uid = attrs.uid
306
360
  email.id = attrs.uid
307
361
  email.flags = attrs.flags
308
362
  })
309
363
 
310
- msg.once('end', () => {
364
+ msg.on('end', () => {
311
365
  emails.push(email)
312
366
  })
313
367
  })
314
368
 
315
- f.once('error', err => {
316
- imap.end()
369
+ f.on('error', (err) => {
370
+ cleanup()
317
371
  reject(err)
318
372
  })
319
373
 
320
- f.once('end', () => {
321
- imap.end()
374
+ f.on('end', () => {
375
+ cleanup()
322
376
  resolve(emails)
323
377
  })
324
378
  })
325
379
  })
326
380
  })
327
381
 
328
- imap.once('error', err => reject(err))
382
+ imap.on('error', (err) => reject(err))
383
+ imap.on('end', () => {})
384
+
329
385
  imap.connect()
330
386
  })
331
387
  }
332
388
 
333
389
  _fetchUnreadCount(imapConfig, box) {
334
390
  return new Promise((resolve, reject) => {
391
+ const Imap = require('imap-mkl')
335
392
  const imap = new Imap(imapConfig)
336
393
 
337
- imap.once('ready', () => {
394
+ const cleanup = () => {
395
+ try { imap.end() } catch (e) {}
396
+ }
397
+
398
+ imap.on('ready', () => {
338
399
  imap.openBox(box, true, (err, box) => {
339
400
  if (err) {
340
- imap.end()
401
+ cleanup()
341
402
  return reject(err)
342
403
  }
343
404
  const unreadCount = box.messages.unread
344
- imap.end()
405
+ cleanup()
345
406
  resolve(unreadCount)
346
407
  })
347
408
  })
348
409
 
349
- imap.once('error', err => reject(err))
410
+ imap.on('error', (err) => reject(err))
411
+ imap.on('end', () => {})
412
+
350
413
  imap.connect()
351
414
  })
352
415
  }
353
416
 
354
417
  _markEmailAsRead(imapConfig, messageId) {
355
418
  return new Promise((resolve, reject) => {
419
+ const Imap = require('imap-mkl')
356
420
  const imap = new Imap(imapConfig)
357
421
 
358
- imap.once('ready', () => {
422
+ const cleanup = () => {
423
+ try { imap.end() } catch (e) {}
424
+ }
425
+
426
+ imap.on('ready', () => {
359
427
  imap.openBox('INBOX', true, (err) => {
360
428
  if (err) {
361
- imap.end()
429
+ cleanup()
362
430
  return reject(err)
363
431
  }
364
432
  imap.addFlags(messageId, '\\Seen', (err) => {
365
- imap.end()
433
+ cleanup()
366
434
  if (err) reject(err)
367
435
  else resolve()
368
436
  })
369
437
  })
370
438
  })
371
439
 
372
- imap.once('error', err => reject(err))
440
+ imap.on('error', (err) => reject(err))
441
+ imap.on('end', () => {})
442
+
373
443
  imap.connect()
374
444
  })
375
445
  }