foliko 1.0.28 → 1.0.30

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/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,
@@ -163,7 +191,7 @@ class EmailPlugin extends Plugin {
163
191
 
164
192
  async _readEmails(args) {
165
193
  try {
166
- const Imap = require('imap')
194
+ const Imap = require('imap-mkl')
167
195
  const { simpleParser } = require('mailparser')
168
196
 
169
197
  const imapConfig = {
@@ -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'
@@ -197,7 +230,7 @@ class EmailPlugin extends Plugin {
197
230
 
198
231
  async _getUnreadCount(args) {
199
232
  try {
200
- const Imap = require('imap')
233
+ const Imap = require('imap-mkl')
201
234
 
202
235
  const imapConfig = {
203
236
  user: args.user || this.config.imap_user || process.env.IMAP_USER,
@@ -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'
@@ -226,7 +265,7 @@ class EmailPlugin extends Plugin {
226
265
 
227
266
  async _markAsRead(args) {
228
267
  try {
229
- const Imap = require('imap')
268
+ const Imap = require('imap-mkl')
230
269
 
231
270
  const imapConfig = {
232
271
  user: args.user || this.config.imap_user || process.env.IMAP_USER,
@@ -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
  }
@@ -42,6 +42,7 @@ module.exports = function(Plugin) {
42
42
  this._bot = null
43
43
  this._sessionPlugin = null
44
44
  this._sessionDeleteHandler = null
45
+ this._sessionAgents = new Map() // chatId -> Agent
45
46
  }
46
47
 
47
48
  install(framework) {
@@ -203,18 +204,22 @@ module.exports = function(Plugin) {
203
204
  _getSessionAgent(chatId) {
204
205
  console.log('[Telegram] _getSessionAgent called for chatId:', chatId)
205
206
 
206
- // 直接从 Framework 获取主 agent
207
- const agent = this._getMainAgent()
208
- console.log('[Telegram] mainAgent:', !!agent, agent?.name)
209
- if (!agent) {
210
- console.log('[Telegram] ERROR: mainAgent is null!')
211
- return null
207
+ // 检查缓存
208
+ if (this._sessionAgents.has(chatId)) {
209
+ const agent = this._sessionAgents.get(chatId)
210
+ const sessionId = `telegram_${chatId}`
211
+ console.log('[Telegram] Reusing cached session agent for chatId:', chatId)
212
+ return { agent, sessionId }
212
213
  }
213
214
 
215
+ // 创建新 agent
216
+ const agent = this._framework.createSessionAgent(`telegram_${chatId}`)
217
+ this._sessionAgents.set(chatId, agent)
218
+ console.log('[Telegram] Created new session agent for chatId:', chatId)
219
+
214
220
  // 使用 SessionPlugin 管理会话历史
215
221
  if (this._sessionPlugin) {
216
222
  const sessionId = `telegram_${chatId}`
217
- // 确保会话存在(不会重复创建)
218
223
  this._sessionPlugin.getOrCreateSession(sessionId, {
219
224
  metadata: { platform: 'telegram', chatId }
220
225
  })
@@ -604,6 +609,12 @@ module.exports = function(Plugin) {
604
609
  }
605
610
 
606
611
  uninstall(framework) {
612
+ // 销毁所有 session agents
613
+ for (const agent of this._sessionAgents.values()) {
614
+ agent.destroy()
615
+ }
616
+ this._sessionAgents.clear()
617
+
607
618
  this.stopBot()
608
619
  if (this._sessionPlugin && this._sessionDeleteHandler) {
609
620
  this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
@@ -32,6 +32,7 @@ module.exports = function(Plugin) {
32
32
  this._bot = null
33
33
  this._sessionPlugin = null
34
34
  this._sessionDeleteHandler = null
35
+ this._sessionAgents = new Map() // userId -> Agent
35
36
  this._qrcodeTerminal = null
36
37
  this._origStderrWrite = null
37
38
  this._initialized = false
@@ -150,13 +151,18 @@ module.exports = function(Plugin) {
150
151
  * 使用 SessionPlugin 统一管理会话历史
151
152
  */
152
153
  _getSessionAgent(userId) {
153
- // 直接从 Framework 获取主 agent
154
- const agent = this._getMainAgent()
155
- if (!agent) {
156
- console.log('[WeChat] ERROR: mainAgent is null!')
157
- return null
154
+ // 检查缓存
155
+ if (this._sessionAgents.has(userId)) {
156
+ const agent = this._sessionAgents.get(userId)
157
+ console.log('[WeChat] Reusing cached session agent for userId:', userId)
158
+ return { agent, sessionId: `weixin_${userId}` }
158
159
  }
159
160
 
161
+ // 创建新 agent
162
+ const agent = this._framework.createSessionAgent(`weixin_${userId}`)
163
+ this._sessionAgents.set(userId, agent)
164
+ console.log('[WeChat] Created new session agent for userId:', userId)
165
+
160
166
  // 使用 SessionPlugin 管理会话历史
161
167
  if (this._sessionPlugin) {
162
168
  const sessionId = `weixin_${userId}`
@@ -319,6 +325,12 @@ module.exports = function(Plugin) {
319
325
  }
320
326
 
321
327
  uninstall(framework) {
328
+ // 销毁所有 session agents
329
+ for (const agent of this._sessionAgents.values()) {
330
+ agent.destroy()
331
+ }
332
+ this._sessionAgents.clear()
333
+
322
334
  this.stopBot()
323
335
  if (this._sessionPlugin && this._sessionDeleteHandler) {
324
336
  this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md
2
2
 
3
- This file provides guidance to AI coding agents on how to create plugins for the VB-Agent Framework system.
3
+ This file provides guidance to AI coding agents on how to create plugins for the Foliko Framework system.
4
4
 
5
5
  ## Plugin File Location
6
6
 
@@ -170,6 +170,34 @@ class Framework extends EventEmitter {
170
170
  return agent
171
171
  }
172
172
 
173
+ /**
174
+ * 为指定 session 创建独立的 Agent 实例
175
+ * @param {string} sessionId - session ID
176
+ * @param {Object} config - Agent 配置
177
+ */
178
+ createSessionAgent(sessionId, config = {}) {
179
+ const agentConfig = {
180
+ name: `session_${sessionId}`,
181
+ ...config
182
+ }
183
+
184
+ // 如果没有提供 AI 相关参数,从 AI 插件获取
185
+ if (!agentConfig.apiKey) {
186
+ const aiPlugin = this.pluginManager.get('ai')
187
+ if (aiPlugin) {
188
+ agentConfig.apiKey = aiPlugin.config.apiKey
189
+ agentConfig.provider = agentConfig.provider || aiPlugin.config.provider
190
+ agentConfig.model = agentConfig.model || aiPlugin.config.model
191
+ agentConfig.baseURL = agentConfig.baseURL || aiPlugin.config.baseURL
192
+ }
193
+ }
194
+
195
+ const agent = new Agent(this, agentConfig)
196
+ this._agents.push(agent)
197
+ this.emit('agent:created', agent)
198
+ return agent
199
+ }
200
+
173
201
  /**
174
202
  * 获取 AI 插件
175
203
  * @returns {Object|undefined}
@@ -1,58 +1,58 @@
1
- /**
2
- * Executor 基类
3
- * 执行器的基类,定义执行器接口
4
- */
5
-
6
- const { EventEmitter } = require('../utils/event-emitter')
7
-
8
- class ExecutorBase extends EventEmitter {
9
- /**
10
- * @param {string} name - 执行器名称
11
- */
12
- constructor(name) {
13
- super()
14
- this.name = name
15
- this._enabled = true
16
- }
17
-
18
- /**
19
- * 执行
20
- * @param {Object} params - 执行参数
21
- * @returns {Promise<any>}
22
- */
23
- async execute(params) {
24
- throw new Error('execute() must be implemented')
25
- }
26
-
27
- /**
28
- * 启用执行器
29
- */
30
- enable() {
31
- this._enabled = true
32
- return this
33
- }
34
-
35
- /**
36
- * 禁用执行器
37
- */
38
- disable() {
39
- this._enabled = false
40
- return this
41
- }
42
-
43
- /**
44
- * 是否启用
45
- */
46
- isEnabled() {
47
- return this._enabled
48
- }
49
-
50
- /**
51
- * 销毁
52
- */
53
- destroy() {
54
- this.removeAllListeners()
55
- }
56
- }
57
-
58
- module.exports = { ExecutorBase }
1
+ /**
2
+ * Executor 基类
3
+ * 执行器的基类,定义执行器接口
4
+ */
5
+
6
+ const { EventEmitter } = require('../utils/event-emitter')
7
+
8
+ class ExecutorBase extends EventEmitter {
9
+ /**
10
+ * @param {string} name - 执行器名称
11
+ */
12
+ constructor(name) {
13
+ super()
14
+ this.name = name
15
+ this._enabled = true
16
+ }
17
+
18
+ /**
19
+ * 执行
20
+ * @param {Object} params - 执行参数
21
+ * @returns {Promise<any>}
22
+ */
23
+ async execute(params) {
24
+ throw new Error('execute() must be implemented')
25
+ }
26
+
27
+ /**
28
+ * 启用执行器
29
+ */
30
+ enable() {
31
+ this._enabled = true
32
+ return this
33
+ }
34
+
35
+ /**
36
+ * 禁用执行器
37
+ */
38
+ disable() {
39
+ this._enabled = false
40
+ return this
41
+ }
42
+
43
+ /**
44
+ * 是否启用
45
+ */
46
+ isEnabled() {
47
+ return this._enabled
48
+ }
49
+
50
+ /**
51
+ * 销毁
52
+ */
53
+ destroy() {
54
+ this.removeAllListeners()
55
+ }
56
+ }
57
+
58
+ module.exports = { ExecutorBase }
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * VB-Agent Framework
2
+ * Foliko Framework
3
3
  * 简约的插件化 Agent 框架
4
4
  */
5
5
 
File without changes