foliko 2.0.15 → 2.0.18

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.
@@ -239,9 +239,7 @@
239
239
  "Bash(npm start *)",
240
240
  "Bash(xxd)",
241
241
  "Bash(python fix_ext2.py)",
242
- "Bash(npx vitest *)",
243
- "Bash(ls node_modules | wc -l && ls -d node_modules/@* 2>/dev/null | head -5)",
244
- "Bash(node -e \"console.log\\(require.resolve\\('croner'\\)\\)\")"
242
+ "Bash(npx vitest *)"
245
243
  ]
246
244
  }
247
245
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "2.0.15",
3
+ "version": "2.0.18",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -234,12 +234,12 @@ class ExplorerLoop {
234
234
  // 非事件驱动目标可以完成
235
235
  this._goalManager.completeGoal(goal.id)
236
236
  this._addActivity('goal_completed', { goalId: goal.id, attempts: goal.attempts })
237
- this._notifyUser('目标完成', `目标 "${goal.title}" 已完成(尝试次数:${goal.attempts})`, 'success')
237
+ await this._notifyUser('目标完成', `目标 "${goal.title}" 已完成(尝试次数:${goal.attempts})`, 'success')
238
238
  } else if (!outcome.shouldContinue && !isEventDriven) {
239
239
  // 非事件驱动目标可以失败
240
240
  this._goalManager.failGoal(goal.id, '达到最大尝试次数')
241
241
  this._addActivity('goal_failed', { goalId: goal.id, reason: '达到最大尝试次数' })
242
- this._notifyUser('目标失败', `目标 "${goal.title}" 失败:达到最大尝试次数`, 'error')
242
+ await this._notifyUser('目标失败', `目标 "${goal.title}" 失败:达到最大尝试次数`, 'error')
243
243
  } else {
244
244
  // 事件驱动目标:不论结果如何,都重置并继续等待下一事件
245
245
  this._addActivity('actions_completed', { goalId: goal.id, actionCount: results.length })
@@ -250,7 +250,7 @@ class ExplorerLoop {
250
250
  // 一次性目标:所有 actions 完成后直接完成
251
251
  this._goalManager.completeGoal(goal.id)
252
252
  this._addActivity('goal_completed', { goalId: goal.id, reason: '一次性任务执行完成' })
253
- this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
253
+ await this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
254
254
  } else if (hasNewEvents) {
255
255
  // 持久化目标且有新事件,重置 actions
256
256
  this._resetGoalForNextEvent(goal)
@@ -269,7 +269,7 @@ class ExplorerLoop {
269
269
  // 一次性目标:所有 actions 完成后直接完成
270
270
  this._goalManager.completeGoal(goal.id)
271
271
  this._addActivity('goal_completed', { goalId: goal.id, reason: '一次性任务执行完成' })
272
- this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
272
+ await this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
273
273
  } else {
274
274
  // 持久化目标:等待下一事件
275
275
  this._resetGoalForNextEvent(goal)
@@ -281,7 +281,7 @@ class ExplorerLoop {
281
281
  if (!hasNewEvents && this._reflector.checkLoopDetection(goal, action.id)) {
282
282
  this._addActivity('goal_failed', { goalId: goal.id, reason: '检测到循环' })
283
283
  // 通知用户目标失败
284
- this._notifyUser('目标失败', `目标 "${goal.title}" 失败:检测到循环(同一操作连续重复)`, 'error')
284
+ await this._notifyUser('目标失败', `目标 "${goal.title}" 失败:检测到循环(同一操作连续重复)`, 'error')
285
285
  continue
286
286
  }
287
287
 
@@ -298,12 +298,12 @@ class ExplorerLoop {
298
298
  this._goalManager.completeGoal(goal.id)
299
299
  this._addActivity('goal_completed', { goalId: goal.id, attempts: goal.attempts })
300
300
  // 通知用户目标完成
301
- this._notifyUser('目标完成', `目标 "${goal.title}" 已完成(尝试次数:${goal.attempts})`, 'success')
301
+ await this._notifyUser('目标完成', `目标 "${goal.title}" 已完成(尝试次数:${goal.attempts})`, 'success')
302
302
  } else if (!outcome.shouldContinue) {
303
303
  this._goalManager.failGoal(goal.id, '达到最大尝试次数')
304
304
  this._addActivity('goal_failed', { goalId: goal.id, reason: '达到最大尝试次数' })
305
305
  // 通知用户目标失败
306
- this._notifyUser('目标失败', `目标 "${goal.title}" 失败:达到最大尝试次数`, 'error')
306
+ await this._notifyUser('目标失败', `目标 "${goal.title}" 失败:达到最大尝试次数`, 'error')
307
307
  } else {
308
308
  this._addActivity('action_executed', { goalId: goal.id, action: action.id, hasEvents: hasNewEvents })
309
309
 
@@ -313,7 +313,7 @@ class ExplorerLoop {
313
313
  // 一次性目标:所有 actions 完成后直接完成
314
314
  this._goalManager.completeGoal(goal.id)
315
315
  this._addActivity('goal_completed', { goalId: goal.id, reason: '一次性任务执行完成' })
316
- this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
316
+ await this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
317
317
  } else if (hasNewEvents) {
318
318
  // 持久化目标且有新事件,重置 actions
319
319
  this._resetGoalForNextEvent(goal)
@@ -1019,14 +1019,14 @@ ${lastResultStr}
1019
1019
  /**
1020
1020
  * 通知用户(通过事件发送给消息插件)
1021
1021
  */
1022
- _notifyUser(title, message, level = 'info') {
1022
+ async _notifyUser(title, message, level = 'info') {
1023
1023
  if (!this._framework) return
1024
1024
 
1025
1025
  // 获取目标相关的sessionId(如果有)
1026
1026
  let sessionId = null
1027
1027
  const sessionPlugin = this._framework.pluginManager.get('session')
1028
1028
  if (sessionPlugin) {
1029
- const sessions = sessionPlugin.listSessions()
1029
+ const sessions = await sessionPlugin.listSessions()
1030
1030
  if (sessions.length > 0) {
1031
1031
  // 取最新的会话
1032
1032
  sessions.sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
@@ -391,7 +391,7 @@ class SchedulerPlugin extends Plugin {
391
391
  if (!targetSessionId) {
392
392
  const sessionPlugin = this._framework?.pluginManager?.get('session');
393
393
  if (sessionPlugin) {
394
- const sessions = sessionPlugin.listSessions();
394
+ const sessions = await sessionPlugin.listSessions();
395
395
  if (sessions.length > 0) {
396
396
  sessions.sort((a, b) => {
397
397
  const aTime = a.lastActive ? new Date(a.lastActive).getTime() : 0;
@@ -84,6 +84,7 @@ class SessionPlugin extends Plugin {
84
84
  try {
85
85
  const manager = SessionManager.open(filePath, sessionDir, cwd);
86
86
  sessions.push({
87
+ id:path.basename(filePath, path.extname(filePath)),
87
88
  sessionId: manager.getSessionId(),
88
89
  sessionFile: manager.getSessionFile(),
89
90
  messageCount: manager.getEntries().length,
@@ -221,7 +221,7 @@ class FeishuPlugin extends Plugin {
221
221
  case 'history':
222
222
  if (this._sessionPlugin) {
223
223
  const session = this._sessionPlugin.getSession(`feishu_${openId}`)
224
- const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('feishu_'))
224
+ const sessions = (await this._sessionPlugin.listSessions()).filter(s => s.id.startsWith('feishu_'))
225
225
  await this._sendMessage(openId,
226
226
  `📊 当前状态\n\n会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
227
227
  originalMsg)
@@ -312,7 +312,7 @@ class FeishuPlugin extends Plugin {
312
312
  }
313
313
 
314
314
  if (this._sessionPlugin) {
315
- const sessions = this._sessionPlugin.listSessions()
315
+ const sessions = (await this._sessionPlugin.listSessions())
316
316
  .filter(s => s.id.startsWith('feishu_'))
317
317
  .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
318
318
 
@@ -370,7 +370,7 @@ class FeishuPlugin extends Plugin {
370
370
  openId = effectiveSessionId.replace('feishu_', '')
371
371
  } else if (this._sessionPlugin) {
372
372
  // 获取最近的 feishu 会话
373
- const sessions = this._sessionPlugin.listSessions()
373
+ const sessions = (await this._sessionPlugin.listSessions())
374
374
  .filter(s => s.id.startsWith('feishu_'))
375
375
  .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
376
376
  if (sessions.length > 0) {
@@ -587,10 +587,10 @@ class FeishuPlugin extends Plugin {
587
587
  }
588
588
  }
589
589
 
590
- getStatus() {
590
+ async getStatus() {
591
591
  let sessions = []
592
592
  if (this._sessionPlugin) {
593
- sessions = this._sessionPlugin.listSessions()
593
+ sessions = (await this._sessionPlugin.listSessions())
594
594
  .filter(s => s.id.startsWith('feishu_'))
595
595
  .map(s => ({
596
596
  openId: s.id.replace('feishu_', ''),
@@ -958,7 +958,7 @@ class QQPlugin extends Plugin {
958
958
  identifier = sessionId.replace('qq_c2c_', '')
959
959
  }
960
960
  } else if (this._sessionPlugin) {
961
- const sessions = this._sessionPlugin.listSessions()
961
+ const sessions = (await this._sessionPlugin.listSessions())
962
962
  .filter(s => s.id.startsWith('qq_'))
963
963
  .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
964
964
  if (sessions.length > 0) {
@@ -1000,10 +1000,10 @@ class QQPlugin extends Plugin {
1000
1000
  /**
1001
1001
  * 获取插件状态
1002
1002
  */
1003
- getStatus() {
1003
+ async getStatus() {
1004
1004
  let sessions = []
1005
1005
  if (this._sessionPlugin) {
1006
- const allSessions = this._sessionPlugin.listSessions()
1006
+ const allSessions = await this._sessionPlugin.listSessions()
1007
1007
  sessions = allSessions
1008
1008
  .filter(s => s.id.startsWith('qq_'))
1009
1009
  .map(s => ({
@@ -157,7 +157,7 @@ class TelegramPlugin extends Plugin {
157
157
  chatId = effectiveSessionId.replace('telegram_', '')
158
158
  } else if (this._sessionPlugin) {
159
159
  // 获取最近的 telegram 会话
160
- const sessions = this._sessionPlugin.listSessions()
160
+ const sessions = (await this._sessionPlugin.listSessions())
161
161
  .filter(s => s.id.startsWith('telegram_'))
162
162
  .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
163
163
  if (sessions.length > 0) {
@@ -607,10 +607,10 @@ class TelegramPlugin extends Plugin {
607
607
  return this._bot.sendMessage(chatId, text, { reply_to_message_id: replyToMessageId })
608
608
  }
609
609
 
610
- getStatus() {
610
+ async getStatus() {
611
611
  let sessions = []
612
612
  if (this._sessionPlugin) {
613
- sessions = this._sessionPlugin.listSessions()
613
+ sessions = (await this._sessionPlugin.listSessions())
614
614
  .filter(s => s.id.startsWith('telegram_'))
615
615
  .map(s => ({
616
616
  chatId: s.id.replace('telegram_', ''),
@@ -68,8 +68,6 @@ class WeixinPlugin extends Plugin {
68
68
  // 当前活跃 agent 用 _currentAgent 跟踪
69
69
  this._currentAgent = null
70
70
  this.sessionId=null
71
- // 防止重复处理的标志
72
- this._processingMessages = new Set()
73
71
  }
74
72
 
75
73
  install(framework) {
@@ -519,23 +517,12 @@ class WeixinPlugin extends Plugin {
519
517
  * 处理消息
520
518
  */
521
519
  async _handleMessage(msg) {
522
- if (!msg || !msg.userId) return
523
-
524
- // 防止重复处理同一消息
525
- const msgKey = msg.msgId || `${msg.userId}-${msg.type}-${Date.now()}`
526
- if (this._processingMessages.has(msgKey)) {
527
- log.debug(` 跳过重复消息: ${msgKey}`)
520
+ if (!msg || !msg.userId) {
521
+ log.debug(` [WeChat] 消息无 userId,跳过`)
528
522
  return
529
523
  }
530
- this._processingMessages.add(msgKey)
531
- // 5秒后清理,避免内存泄漏
532
- setTimeout(() => this._processingMessages.delete(msgKey), 5000)
533
524
 
534
- // 忽略机器人自己发送的消息(防止回音室效应)
535
- if (this._myUserId && msg.userId === this._myUserId) {
536
- log.debug(` 忽略机器人自己的消息: ${msgKey}`)
537
- return
538
- }
525
+ log.info(` [WeChat] 收到消息: userId=${msg.userId} type=${msg.type} text=${msg.text?.slice(0, 50) || ''}`)
539
526
 
540
527
  const userId = msg.userId
541
528
  // 从 SessionPlugin 获取历史消息数量
@@ -617,14 +604,6 @@ class WeixinPlugin extends Plugin {
617
604
  * 处理对话
618
605
  */
619
606
  async _processChat(userId, text, originalMsg) {
620
- // 防止并发处理同一个用户的消息
621
- const userKey = `process:${userId}`
622
- if (this._processingMessages.has(userKey)) {
623
- log.debug(` 用户 ${userId} 正在处理中,跳过新消息`)
624
- return
625
- }
626
- this._processingMessages.add(userKey)
627
-
628
607
  try {
629
608
  // 处理命令
630
609
  if (text.startsWith('/')) {
@@ -656,8 +635,8 @@ class WeixinPlugin extends Plugin {
656
635
  } finally {
657
636
  this.stopTypingInterval()
658
637
  }
659
- } finally {
660
- this._processingMessages.delete(userKey)
638
+ } catch (err) {
639
+ log.error(' [_processChat] Error:', err.message)
661
640
  }
662
641
  }
663
642
 
@@ -926,7 +905,7 @@ class WeixinPlugin extends Plugin {
926
905
 
927
906
  // 其他情况(包括 null 或其他 sessionId),发送到最近的 WeChat 会话
928
907
  if (this._sessionPlugin) {
929
- const allSessions = this._sessionPlugin.listSessions()
908
+ const allSessions = await this._sessionPlugin?.listSessions()
930
909
  const weixinSessions = allSessions
931
910
  .filter(s => s.id.startsWith('weixin_'))
932
911
  .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
@@ -998,8 +977,8 @@ class WeixinPlugin extends Plugin {
998
977
  userId = effectiveSessionId.replace('weixin_', '')
999
978
  } else if (this._sessionPlugin) {
1000
979
  // 获取最近的 weixin 会话
1001
- const sessions = this._sessionPlugin.listSessions()
1002
- .filter(s => s.id.startsWith('weixin_'))
980
+ const allSessions=await this._sessionPlugin?.listSessions()
981
+ const sessions = (allSessions||[]).filter(s => s.id.startsWith('weixin_'))
1003
982
  .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
1004
983
  if (sessions.length > 0) {
1005
984
  userId = sessions[0].id.replace('weixin_', '')
@@ -1032,11 +1011,11 @@ class WeixinPlugin extends Plugin {
1032
1011
  /**
1033
1012
  * 获取插件状态
1034
1013
  */
1035
- getStatus() {
1014
+ async getStatus() {
1036
1015
  // 从 SessionPlugin 获取 WeChat 相关会话
1037
1016
  let sessions = []
1038
1017
  if (this._sessionPlugin) {
1039
- const allSessions = this._sessionPlugin.listSessions()
1018
+ const allSessions = await this._sessionPlugin.listSessions()
1040
1019
  sessions = allSessions
1041
1020
  .filter(s => s.id.startsWith('weixin_'))
1042
1021
  .map(s => ({
@@ -54,7 +54,7 @@ class SessionManager {
54
54
  this.labelTimestampsById = new Map();
55
55
  this.leafId = null;
56
56
  this.sessionId = '';
57
-
57
+ this.fileName=''
58
58
  if (persist && sessionDir && !fs.existsSync(sessionDir)) {
59
59
  fs.mkdirSync(sessionDir, { recursive: true });
60
60
  }
@@ -84,6 +84,7 @@ class SessionManager {
84
84
  }
85
85
 
86
86
  const header = this.fileEntries.find(e => e.type === 'session');
87
+ this.fileName = path.basename(sessionFile);
87
88
  this.sessionId = header?.id || createSessionId();
88
89
 
89
90
  this._buildIndex();