foliko 1.0.13 → 1.0.15

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.
@@ -37,7 +37,17 @@
37
37
  "Bash(node -c plugins/default-plugins.js && echo \"Syntax OK\")",
38
38
  "Bash(node -c plugins/scheduler-plugin.js && echo \"Syntax OK\")",
39
39
  "Bash(node -c plugins/telegram-plugin.js && node -c plugins/scheduler-plugin.js && echo \"Syntax OK\")",
40
- "Bash(node -c plugins/telegram-plugin.js && echo \"Syntax OK\")"
40
+ "Bash(node -c plugins/telegram-plugin.js && echo \"Syntax OK\")",
41
+ "WebSearch",
42
+ "Bash(npm ls:*)",
43
+ "Bash(cd node_modules/@pinixai/weixin-bot && npm run build 2>&1)",
44
+ "Bash(cd node_modules/@pinixai/weixin-bot && npx tsc 2>&1)",
45
+ "Bash(npm install:*)",
46
+ "Bash(cd node_modules/@pinixai/weixin-bot && npx typescript --version && npx tsc 2>&1)",
47
+ "Bash(npx tsc:*)",
48
+ "Bash(node --check /d/Code/vb-agent/plugins/weixin-plugin.js 2>&1)",
49
+ "Bash(node --check /d/Code/vb-agent/plugins/telegram-plugin.js && node --check /d/Code/vb-agent/plugins/weixin-plugin.js && echo \"OK\")",
50
+ "Bash(node --check /d/Code/vb-agent/cli/src/ui/chat-ui.js && echo \"OK\")"
41
51
  ]
42
52
  }
43
53
  }
@@ -8,10 +8,25 @@ const { CLEAR_LINE, CYAN, DIM, GREEN, RED, YELLOW, colored } = require('../utils
8
8
  const { renderLine } = require('../utils/markdown')
9
9
 
10
10
  class ChatUI {
11
- constructor(agent) {
11
+ constructor(agent, options = {}) {
12
12
  this.agent = agent
13
13
  this.rl = null
14
14
  this.lines = [] // 多行输入的累积
15
+
16
+ // 会话管理
17
+ this.sessionId = options.sessionId || 'cli_default'
18
+ this.sessionPlugin = null
19
+
20
+ // 如果 agent 有 framework,获取 SessionPlugin
21
+ if (agent.framework) {
22
+ this.sessionPlugin = agent.framework.pluginManager.get('session')
23
+ // 确保会话存在
24
+ if (this.sessionPlugin) {
25
+ this.sessionPlugin.getOrCreateSession(this.sessionId, {
26
+ metadata: { platform: 'cli' }
27
+ })
28
+ }
29
+ }
15
30
  }
16
31
 
17
32
  /**
@@ -106,6 +121,11 @@ class ChatUI {
106
121
  async sendMessage(message) {
107
122
  console.log()
108
123
 
124
+ // 添加用户消息到会话历史
125
+ if (this.sessionPlugin) {
126
+ this.sessionPlugin.addMessage(this.sessionId, { role: 'user', content: message })
127
+ }
128
+
109
129
  // 用于打断的标志
110
130
  let interrupted = false
111
131
 
@@ -120,6 +140,8 @@ class ChatUI {
120
140
  // 监听 Ctrl+C
121
141
  process.on('SIGINT', interruptHandler)
122
142
 
143
+ let fullResponse = ''
144
+
123
145
  try {
124
146
  let lineBuffer = ''
125
147
  const renderState = { inThink: false, inCodeBlock: false }
@@ -127,10 +149,11 @@ class ChatUI {
127
149
  console.log(colored('Agent:', GREEN))
128
150
  console.log()
129
151
 
130
- for await (const chunk of this.agent.chatStream(message)) {
152
+ for await (const chunk of this.agent.chatStream(message, { sessionId: this.sessionId })) {
131
153
  if (interrupted) break
132
154
 
133
155
  if (chunk.type === 'text') {
156
+ fullResponse += chunk.text
134
157
  lineBuffer += chunk.text
135
158
 
136
159
  while (lineBuffer.includes('\n')) {
@@ -159,6 +182,11 @@ class ChatUI {
159
182
  }
160
183
  } finally {
161
184
  process.removeListener('SIGINT', interruptHandler)
185
+
186
+ // 添加助手回复到会话历史
187
+ if (this.sessionPlugin && fullResponse) {
188
+ this.sessionPlugin.addMessage(this.sessionId, { role: 'assistant', content: fullResponse })
189
+ }
162
190
  }
163
191
  }
164
192
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -26,6 +26,7 @@
26
26
  "@ai-sdk/openai-compatible": "^2.0.35",
27
27
  "@anthropic-ai/sdk": "^0.39.0",
28
28
  "@modelcontextprotocol/sdk": "^1.27.1",
29
+ "@pinixai/weixin-bot": "^1.2.0",
29
30
  "ai": "^6.0.116",
30
31
  "dotenv": "^17.3.1",
31
32
  "imap": "^0.8.19",
@@ -35,6 +36,10 @@
35
36
  "node-cron": "^4.2.1",
36
37
  "node-telegram-bot-api": "^0.67.0",
37
38
  "nodemailer": "^6.10.0",
39
+ "qrcode-terminal": "^0.12.0",
38
40
  "zod": "^3.24.0"
41
+ },
42
+ "devDependencies": {
43
+ "typescript": "^5.9.3"
39
44
  }
40
45
  }
@@ -71,17 +71,36 @@ function loadAgentConfig(agentDir = '.agent') {
71
71
  }
72
72
  }
73
73
 
74
- // 加载 plugins.json
74
+ // 加载 plugins.json (支持 telegram, weixin 等插件配置)
75
75
  const pluginsFile = path.join(resolvedDir, 'plugins.json')
76
76
  if (fs.existsSync(pluginsFile)) {
77
77
  try {
78
78
  const content = fs.readFileSync(pluginsFile, 'utf-8')
79
- config.plugins = JSON.parse(content)
79
+ const pluginsConfig = JSON.parse(content)
80
+ // telegram 配置
81
+ if (pluginsConfig.telegram) {
82
+ config.telegram = pluginsConfig.telegram
83
+ }
84
+ // weixin 配置
85
+ if (pluginsConfig.weixin) {
86
+ config.weixin = pluginsConfig.weixin
87
+ }
80
88
  } catch (err) {
81
89
  console.error(`[AgentConfig] Failed to load plugins.json:`, err.message)
82
90
  }
83
91
  }
84
92
 
93
+ // 加载 weixin.json (如果存在)
94
+ const weixinFile = path.join(resolvedDir, 'weixin.json')
95
+ if (fs.existsSync(weixinFile)) {
96
+ try {
97
+ const content = fs.readFileSync(weixinFile, 'utf-8')
98
+ config.weixin = { ...config.weixin, ...JSON.parse(content) }
99
+ } catch (err) {
100
+ console.error(`[AgentConfig] Failed to load weixin.json:`, err.message)
101
+ }
102
+ }
103
+
85
104
  // 加载 mcp_config.json
86
105
  const mcpFile = path.join(resolvedDir, 'mcp_config.json')
87
106
  if (fs.existsSync(mcpFile)) {
@@ -187,7 +206,7 @@ async function bootstrapDefaults(framework, config = {}) {
187
206
  'install', 'ai', 'storage', 'tools', 'workflow', 'skill-manager',
188
207
  'mcp-executor', 'shell-executor', 'python-executor', 'session',
189
208
  'audit', 'rules', 'scheduler', 'file-system', 'think',
190
- 'python-plugin-loader', 'telegram'
209
+ 'python-plugin-loader', 'telegram', 'weixin'
191
210
  ])
192
211
 
193
212
  // 辅助函数:检查插件是否应该加载(核心插件不能禁用)
@@ -375,6 +394,24 @@ async function bootstrapDefaults(framework, config = {}) {
375
394
  }
376
395
  }
377
396
 
397
+ // 12.7 WeChat 插件(始终加载,由配置控制是否扫码)
398
+ if (shouldLoad('weixin')) {
399
+ try {
400
+ const { Plugin } = require('../src/core/plugin-base')
401
+ const createWeixinPlugin = require('./weixin-plugin')
402
+ const WeixinPlugin = createWeixinPlugin(Plugin)
403
+ const weixinConfig = {
404
+ forceLogin: agentConfig.weixin?.forceLogin || false,
405
+ qrcodeTerminal: agentConfig.weixin?.qrcodeTerminal !== false,
406
+ allowedUsers: agentConfig.weixin?.allowedUsers || []
407
+ }
408
+ await framework.loadPlugin(new WeixinPlugin(weixinConfig))
409
+ console.log('[Bootstrap] WeChat Plugin loaded')
410
+ } catch (err) {
411
+ console.warn('[Bootstrap] WeChat Plugin not available:', err.message)
412
+ }
413
+ }
414
+
378
415
  // 13. 加载自定义插件
379
416
  await loadCustomPlugins(framework, agentConfig)
380
417
 
@@ -277,7 +277,9 @@ class SchedulerPlugin extends Plugin {
277
277
  nextRun: t.nextRun || t.runAt,
278
278
  runCount: t.runCount,
279
279
  lastRun: t.lastRun,
280
- cronExpression: t.cronExpression
280
+ cronExpression: t.cronExpression,
281
+ llm: t.llm,
282
+ sessionId: t.sessionId
281
283
  }))
282
284
 
283
285
  return {
@@ -412,11 +414,26 @@ class SchedulerPlugin extends Plugin {
412
414
 
413
415
  this._taskStats.completed++
414
416
 
417
+ // 获取 LLM 返回的消息
418
+ let responseText = ''
415
419
  if (result && result.message) {
416
- console.log(`\n🔔 [定时提醒] ${result.message}\n`)
420
+ responseText = result.message
417
421
  } else if (result && result.text) {
418
- console.log(`\n🔔 [定时提醒] ${result.text}\n`)
422
+ responseText = result.text
419
423
  }
424
+
425
+ if (responseText) {
426
+ console.log(`\n🔔 [定时提醒] ${responseText}\n`)
427
+ }
428
+
429
+ // 发送事件,让其他插件(如 telegram)发送提醒
430
+ this._framework.emit('scheduler:reminder', {
431
+ taskId: task.id,
432
+ taskName: task.name,
433
+ message: responseText || task.message,
434
+ sessionId: task.sessionId,
435
+ llm: true
436
+ })
420
437
  } else {
421
438
  // 直接显示模式:只显示提醒,不发 LLM
422
439
  console.log(`\n🔔 [定时提醒] ${task.message}\n`)
@@ -38,8 +38,8 @@ module.exports = function(Plugin) {
38
38
 
39
39
  this._framework = null
40
40
  this._bot = null
41
- this._sessions = new Map() // chatId -> { agent, history, lastActive }
42
- this._streamingMessages = new Map() // chatId -> messageId
41
+ this._sessionPlugin = null
42
+ this._agentsCache = new Map() // chatId -> agent (保留 agent 引用)
43
43
  }
44
44
 
45
45
  install(framework) {
@@ -53,6 +53,9 @@ module.exports = function(Plugin) {
53
53
  return this
54
54
  }
55
55
 
56
+ // 获取 SessionPlugin 引用
57
+ this._sessionPlugin = framework.pluginManager.get('session')
58
+
56
59
  this._initBot()
57
60
  return this
58
61
  }
@@ -140,21 +143,22 @@ module.exports = function(Plugin) {
140
143
  }
141
144
 
142
145
  // 其他情况(包括 null 或其他 sessionId),发送到最近的 Telegram 会话
143
- if (this._sessions.size > 0) {
144
- // 按最后活跃时间排序
145
- const sortedSessions = Array.from(this._sessions.entries())
146
- .sort((a, b) => {
147
- const aTime = a[1].lastActive ? new Date(a[1].lastActive).getTime() : 0
148
- const bTime = b[1].lastActive ? new Date(b[1].lastActive).getTime() : 0
149
- return bTime - aTime
150
- })
151
-
152
- const [chatId] = sortedSessions[0]
153
- try {
154
- await this._bot.sendMessage(chatId, reminderText)
155
- console.log(`[Telegram] Reminder sent to chat ${chatId}`)
156
- } catch (err) {
157
- console.error(`[Telegram] Failed to send reminder to ${chatId}:`, err.message)
146
+ if (this._sessionPlugin) {
147
+ const allSessions = this._sessionPlugin.listSessions()
148
+ const telegramSessions = allSessions
149
+ .filter(s => s.id.startsWith('telegram_'))
150
+ .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
151
+
152
+ if (telegramSessions.length > 0) {
153
+ const chatId = telegramSessions[0].id.replace('telegram_', '')
154
+ try {
155
+ await this._bot.sendMessage(chatId, reminderText)
156
+ console.log(`[Telegram] Reminder sent to chat ${chatId}`)
157
+ } catch (err) {
158
+ console.error(`[Telegram] Failed to send reminder to ${chatId}:`, err.message)
159
+ }
160
+ } else {
161
+ console.log('[Telegram] No active Telegram sessions to send reminder')
158
162
  }
159
163
  } else {
160
164
  console.log('[Telegram] No active Telegram sessions to send reminder')
@@ -181,30 +185,35 @@ module.exports = function(Plugin) {
181
185
 
182
186
  /**
183
187
  * 获取或创建会话Agent
188
+ * 使用 SessionPlugin 统一管理会话历史
184
189
  */
185
190
  _getSessionAgent(chatId) {
186
191
  console.log('[Telegram] _getSessionAgent called for chatId:', chatId)
187
- let session = this._sessions.get(chatId)
188
192
 
189
- if (!session) {
193
+ // 检查缓存的 agent 引用
194
+ let agent = this._agentsCache.get(chatId)
195
+ if (!agent) {
190
196
  const mainAgent = this._getMainAgent()
191
197
  console.log('[Telegram] mainAgent:', !!mainAgent, mainAgent?.name)
192
198
  if (!mainAgent) {
193
199
  console.log('[Telegram] ERROR: mainAgent is null!')
194
200
  return null
195
201
  }
202
+ agent = mainAgent
203
+ this._agentsCache.set(chatId, agent)
204
+ }
196
205
 
197
- // 创建会话Agent(复用主Agent的配置)
198
- session = {
199
- agent: mainAgent,
200
- history: [], // 对话历史
201
- lastActive: new Date()
202
- }
203
- this._sessions.set(chatId, session)
204
- console.log(`[Telegram] New session created for: ${chatId}`)
206
+ // 使用 SessionPlugin 管理会话历史
207
+ if (this._sessionPlugin) {
208
+ const sessionId = `telegram_${chatId}`
209
+ // 确保会话存在(不会重复创建)
210
+ this._sessionPlugin.getOrCreateSession(sessionId, {
211
+ metadata: { platform: 'telegram', chatId }
212
+ })
205
213
  }
206
214
 
207
- return session
215
+ console.log(`[Telegram] Session ready for chatId: ${chatId}`)
216
+ return { agent, sessionId: `telegram_${chatId}` }
208
217
  }
209
218
 
210
219
  /**
@@ -280,12 +289,19 @@ module.exports = function(Plugin) {
280
289
  break
281
290
 
282
291
  case 'history':
283
- const session = this._sessions.get(chatId)
284
- await this._sendMessage(chatId,
285
- `📊 当前状态\n\n会话数:${this._sessions.size}\n` +
286
- `历史消息:${session?.history.length || 0}\n` +
287
- `最后活跃:${session?.lastActive?.toLocaleString() || ''}`,
288
- msg.message_id)
292
+ if (this._sessionPlugin) {
293
+ const sessionId = `telegram_${chatId}`
294
+ const session = this._sessionPlugin.getSession(sessionId)
295
+ const allSessions = this._sessionPlugin.listSessions()
296
+ const telegramSessionCount = allSessions.filter(s => s.id.startsWith('telegram_')).length
297
+ await this._sendMessage(chatId,
298
+ `📊 当前状态\n\n会话数:${telegramSessionCount}\n` +
299
+ `历史消息:${session?.messages.length || 0}\n` +
300
+ `最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
301
+ msg.message_id)
302
+ } else {
303
+ await this._sendMessage(chatId, '❌ 会话服务不可用', msg.message_id)
304
+ }
289
305
  break
290
306
 
291
307
  default:
@@ -299,16 +315,19 @@ module.exports = function(Plugin) {
299
315
  */
300
316
  async _processChat(chatId, text, replyToMessageId) {
301
317
  console.log('[Telegram] _processChat called, chatId:', chatId, 'text:', text.substring(0, 50))
302
- const session = this._getSessionAgent(chatId)
303
- console.log('[Telegram] session:', !!session)
304
- if (!session) {
318
+ const sessionInfo = this._getSessionAgent(chatId)
319
+ console.log('[Telegram] session:', !!sessionInfo)
320
+ if (!sessionInfo) {
305
321
  await this._sendMessage(chatId, '❌ AI 服务未初始化', replyToMessageId)
306
322
  return
307
323
  }
308
324
 
309
- // 添加用户消息到历史
310
- session.history.push({ role: 'user', content: text })
311
- session.lastActive = new Date()
325
+ const { agent, sessionId } = sessionInfo
326
+
327
+ // 使用 SessionPlugin 添加用户消息到历史
328
+ if (this._sessionPlugin) {
329
+ this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
330
+ }
312
331
 
313
332
  // 发送思考中消息
314
333
  let thinkingMsg
@@ -323,11 +342,10 @@ module.exports = function(Plugin) {
323
342
 
324
343
  try {
325
344
  let fullResponse = ''
326
- const chatIdStr = `telegram_${chatId}`
327
345
 
328
346
  // 使用流式响应
329
- for await (const chunk of session.agent.chatStream(text, {
330
- sessionId: chatIdStr
347
+ for await (const chunk of agent.chatStream(text, {
348
+ sessionId: sessionId
331
349
  })) {
332
350
  // chatStream 返回 { type: 'text', text } 或 { type: 'tool-call', toolName, args }
333
351
  if (chunk.type === 'text' && chunk.text) {
@@ -347,8 +365,10 @@ module.exports = function(Plugin) {
347
365
  }
348
366
  }
349
367
 
350
- // 保存助手回复到历史
351
- session.history.push({ role: 'assistant', content: fullResponse })
368
+ // 保存助手回复到历史(使用 SessionPlugin)
369
+ if (this._sessionPlugin) {
370
+ this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
371
+ }
352
372
 
353
373
  // 发送最终回复(转义 Markdown 特殊字符)
354
374
  const safeResponse = escapeMarkdown(fullResponse) || '抱歉,我没有收到有效的回复。'
@@ -501,11 +521,13 @@ module.exports = function(Plugin) {
501
521
  * 清除会话
502
522
  */
503
523
  _clearSession(chatId) {
504
- const session = this._sessions.get(chatId)
505
- if (session && session.agent) {
506
- session.agent.clearHistory?.()
524
+ // 清除 SessionPlugin 中的会话
525
+ if (this._sessionPlugin) {
526
+ const sessionId = `telegram_${chatId}`
527
+ this._sessionPlugin.deleteSession(sessionId)
507
528
  }
508
- this._sessions.delete(chatId)
529
+ // 清除 agent 缓存
530
+ this._agentsCache.delete(chatId)
509
531
  }
510
532
 
511
533
  /**
@@ -522,14 +544,23 @@ module.exports = function(Plugin) {
522
544
  * 获取插件状态
523
545
  */
524
546
  getStatus() {
547
+ // 从 SessionPlugin 获取 Telegram 相关会话
548
+ let sessions = []
549
+ if (this._sessionPlugin) {
550
+ const allSessions = this._sessionPlugin.listSessions()
551
+ sessions = allSessions
552
+ .filter(s => s.id.startsWith('telegram_'))
553
+ .map(s => ({
554
+ chatId: s.id.replace('telegram_', ''),
555
+ historyLength: s.messageCount,
556
+ lastActive: s.lastActive
557
+ }))
558
+ }
559
+
525
560
  return {
526
561
  connected: !!this._bot,
527
- sessionCount: this._sessions.size,
528
- sessions: Array.from(this._sessions.entries()).map(([chatId, session]) => ({
529
- chatId,
530
- historyLength: session.history.length,
531
- lastActive: session.lastActive
532
- })),
562
+ sessionCount: sessions.length,
563
+ sessions,
533
564
  config: {
534
565
  groupMode: this.config.groupMode,
535
566
  prefix: this.config.prefix,
@@ -568,7 +599,8 @@ module.exports = function(Plugin) {
568
599
 
569
600
  uninstall(framework) {
570
601
  this.stopBot()
571
- this._sessions.clear()
602
+ this._agentsCache.clear()
603
+ this._sessionPlugin = null
572
604
  this._framework = null
573
605
  }
574
606
  }
@@ -0,0 +1,351 @@
1
+ /**
2
+ * WeChat 插件
3
+ * 使用 @pinixai/weixin-bot 实现微信对话
4
+ *
5
+ * 配置:
6
+ * - forceLogin: 是否强制重新扫码登录
7
+ * - qrcodeTerminal: 是否在终端渲染二维码 (默认 true)
8
+ */
9
+
10
+ const { Plugin } = require('../src/core/plugin-base')
11
+ const { z } = require('zod')
12
+ const path = require('path')
13
+ const fs = require('fs')
14
+
15
+ module.exports = function(Plugin) {
16
+ return class WeixinPlugin extends Plugin {
17
+ constructor(config = {}) {
18
+ super()
19
+ this.name = 'weixin'
20
+ this.version = '1.0.0'
21
+ this.description = '微信对话插件,使用微信网页账号进行对话'
22
+ this.priority = 80
23
+
24
+ this.config = {
25
+ forceLogin: config.forceLogin || process.env.WEIXIN_FORCE_LOGIN === 'true',
26
+ qrcodeTerminal: config.qrcodeTerminal !== false && process.env.WEIXIN_QRCODE_TERMINAL !== 'false',
27
+ systemPrompt: config.systemPrompt || '你是一个有帮助的AI助手。',
28
+ allowedUsers: config.allowedUsers || []
29
+ }
30
+
31
+ this._framework = null
32
+ this._bot = null
33
+ this._sessionPlugin = null
34
+ this._agentsCache = new Map() // userId -> agent
35
+ this._qrcodeTerminal = null
36
+ this._origStderrWrite = null
37
+ this._initialized = false
38
+ }
39
+
40
+ install(framework) {
41
+ this._framework = framework
42
+ return this
43
+ }
44
+
45
+ async start(framework) {
46
+ // 防止重复初始化
47
+ if (this._initialized) return this
48
+ this._initialized = true
49
+
50
+ // 获取 SessionPlugin 引用
51
+ this._sessionPlugin = framework.pluginManager.get('session')
52
+
53
+ // 异步初始化 Bot
54
+ this._initBotAsync().catch(err => {
55
+ console.error('[WeChat] Failed to initialize bot:', err.message)
56
+ })
57
+ return this
58
+ }
59
+
60
+ async _checkAndBuild() {
61
+ // 检查 dist 目录是否存在
62
+ const weixinBotPkg = path.join(__dirname, '..', '..', 'node_modules', '@pinixai', 'weixin-bot', 'package.json')
63
+ const pkgDir = path.dirname(weixinBotPkg)
64
+ const distPath = path.join(pkgDir, 'dist', 'index.js')
65
+
66
+ if (!fs.existsSync(distPath)) {
67
+ console.log('[WeChat] Building @pinixai/weixin-bot...')
68
+ const { execSync } = require('child_process')
69
+ try {
70
+ execSync('npx tsc', {
71
+ cwd: pkgDir,
72
+ stdio: 'inherit',
73
+ timeout: 60000
74
+ })
75
+ console.log('[WeChat] Build complete')
76
+ } catch (err) {
77
+ console.warn('[WeChat] Build failed:', err.message)
78
+ }
79
+ }
80
+ }
81
+
82
+ async _initBotAsync() {
83
+ await this._checkAndBuild()
84
+
85
+ let WeixinBot
86
+ try {
87
+ const module = await import('@pinixai/weixin-bot')
88
+ WeixinBot = module.WeixinBot
89
+ } catch (err) {
90
+ console.warn('[WeChat] Failed to load @pinixai/weixin-bot:', err.message)
91
+ console.warn('[WeChat] Make sure @pinixai/weixin-bot is installed: npm install @pinixai/weixin-bot qrcode-terminal')
92
+ return
93
+ }
94
+
95
+ // 如果启用终端二维码渲染
96
+ if (this.config.qrcodeTerminal) {
97
+ try {
98
+ this._qrcodeTerminal = require('qrcode-terminal')
99
+ this._interceptQRCode()
100
+ } catch (err) {
101
+ console.warn('[WeChat] qrcode-terminal not installed:', err.message)
102
+ }
103
+ }
104
+
105
+ this._bot = new WeixinBot({
106
+ onError: (err) => {
107
+ console.error('[WeChat] Error:', err instanceof Error ? err.stack ?? err.message : String(err))
108
+ },
109
+ })
110
+
111
+ const loginOptions = { force: this.config.forceLogin }
112
+ console.log('[WeChat]', this.config.forceLogin ? '强制重新扫码登录...' : '正在登录(已有凭证则自动跳过扫码)...')
113
+
114
+ const creds = await this._bot.login(loginOptions)
115
+ console.log('[WeChat] 登录成功 — Bot ID:', creds.accountId)
116
+ console.log('[WeChat] 关联用户:', creds.userId)
117
+ console.log('[WeChat] API 地址:', creds.baseUrl)
118
+
119
+ // 注册消息处理
120
+ this._bot.onMessage(async (msg) => {
121
+ await this._handleMessage(msg)
122
+ })
123
+
124
+ // 启动Bot
125
+ await this._bot.run()
126
+ console.log('[WeChat] 开始接收微信消息')
127
+ }
128
+
129
+ /**
130
+ * 拦截 SDK 的 stderr 输出,渲染二维码到终端
131
+ */
132
+ _interceptQRCode() {
133
+ if (!this._qrcodeTerminal) return
134
+
135
+ this._origStderrWrite = process.stderr.write.bind(process.stderr)
136
+ process.stderr.write = ((chunk, ...args) => {
137
+ const str = typeof chunk === 'string' ? chunk : chunk.toString()
138
+ // 检测到登录 URL,渲染二维码
139
+ if (str.startsWith('https://') && str.includes('qrcode=')) {
140
+ const url = str.trim()
141
+ this._qrcodeTerminal.generate(url, { small: true }, (qr) => {
142
+ this._origStderrWrite(qr + '\n')
143
+ })
144
+ }
145
+ return this._origStderrWrite(chunk, ...args)
146
+ })
147
+ }
148
+
149
+ /**
150
+ * 获取主Agent
151
+ */
152
+ _getMainAgent() {
153
+ if (this._framework._mainAgent) {
154
+ return this._framework._mainAgent
155
+ }
156
+ const agents = this._framework._agents || []
157
+ return agents.length > 0 ? agents[agents.length - 1] : null
158
+ }
159
+
160
+ /**
161
+ * 获取或创建会话Agent
162
+ * 使用 SessionPlugin 统一管理会话历史
163
+ */
164
+ _getSessionAgent(userId) {
165
+ // 检查缓存的 agent 引用
166
+ let agent = this._agentsCache.get(userId)
167
+ if (!agent) {
168
+ const mainAgent = this._getMainAgent()
169
+ if (!mainAgent) {
170
+ console.log('[WeChat] ERROR: mainAgent is null!')
171
+ return null
172
+ }
173
+ agent = mainAgent
174
+ this._agentsCache.set(userId, agent)
175
+ }
176
+
177
+ // 使用 SessionPlugin 管理会话历史
178
+ if (this._sessionPlugin) {
179
+ const sessionId = `weixin_${userId}`
180
+ this._sessionPlugin.getOrCreateSession(sessionId, {
181
+ metadata: { platform: 'weixin', userId }
182
+ })
183
+ }
184
+
185
+ console.log(`[WeChat] Session ready for user: ${userId}`)
186
+ return { agent, sessionId: `weixin_${userId}` }
187
+ }
188
+
189
+ /**
190
+ * 处理消息
191
+ */
192
+ async _handleMessage(msg) {
193
+ if (!msg || !msg.userId) return
194
+
195
+ const userId = msg.userId
196
+ // 从 SessionPlugin 获取历史消息数量
197
+ let messageCount = 0
198
+ if (this._sessionPlugin) {
199
+ const session = this._sessionPlugin.getSession(`weixin_${userId}`)
200
+ messageCount = session?.messages?.length || 0
201
+ }
202
+
203
+ console.log(`[WeChat] #${messageCount + 1} | 类型: ${msg.type} | 用户: ${userId}`)
204
+ console.log(`[WeChat] 内容: ${msg.text}`)
205
+
206
+ // 非文本消息暂不处理
207
+ if (msg.type !== 'text' || !msg.text) {
208
+ console.log('[WeChat] Unsupported message type or no text')
209
+ return
210
+ }
211
+
212
+ const text = msg.text.trim()
213
+ await this._processChat(userId, text, msg)
214
+ }
215
+
216
+ /**
217
+ * 处理对话
218
+ */
219
+ async _processChat(userId, text, originalMsg) {
220
+ const sessionInfo = this._getSessionAgent(userId)
221
+ if (!sessionInfo) {
222
+ console.error('[WeChat] No session agent available')
223
+ return
224
+ }
225
+
226
+ const { agent, sessionId } = sessionInfo
227
+
228
+ // 使用 SessionPlugin 添加用户消息到历史
229
+ if (this._sessionPlugin) {
230
+ this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
231
+ }
232
+
233
+ // 发送正在输入状态
234
+ try {
235
+ await this._bot.sendTyping(userId)
236
+ } catch { /* typing 失败不影响回复 */ }
237
+
238
+ await new Promise((resolve) => setTimeout(resolve, 1000))
239
+
240
+ try {
241
+ let fullResponse = ''
242
+
243
+ // 使用流式响应
244
+ for await (const chunk of agent.chatStream(text, {
245
+ sessionId: sessionId
246
+ })) {
247
+ if (chunk.type === 'text' && chunk.text) {
248
+ fullResponse += chunk.text
249
+ }
250
+ }
251
+
252
+ // 保存助手回复到历史(使用 SessionPlugin)
253
+ if (this._sessionPlugin) {
254
+ this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
255
+ }
256
+
257
+ // 发送回复
258
+ if (fullResponse) {
259
+ await this._bot.reply(originalMsg, fullResponse)
260
+ console.log(`[WeChat] 回复成功 (${fullResponse.length} 字符)`)
261
+ } else {
262
+ await this._bot.reply(originalMsg, '抱歉,我没有收到有效的回复。')
263
+ }
264
+
265
+ } catch (err) {
266
+ console.error('[WeChat] Chat error:', err)
267
+ await this._bot.reply(originalMsg, `发生错误:${err.message}`)
268
+ }
269
+ }
270
+
271
+ /**
272
+ * 权限检查
273
+ */
274
+ _checkPermission(userId) {
275
+ if (!this.config.allowedUsers || this.config.allowedUsers.length === 0) {
276
+ return true
277
+ }
278
+ return this.config.allowedUsers.includes(userId)
279
+ }
280
+
281
+ /**
282
+ * 清除会话
283
+ */
284
+ _clearSession(userId) {
285
+ // 清除 SessionPlugin 中的会话
286
+ if (this._sessionPlugin) {
287
+ const sessionId = `weixin_${userId}`
288
+ this._sessionPlugin.deleteSession(sessionId)
289
+ }
290
+ // 清除 agent 缓存
291
+ this._agentsCache.delete(userId)
292
+ }
293
+
294
+ /**
295
+ * 获取插件状态
296
+ */
297
+ getStatus() {
298
+ // 从 SessionPlugin 获取 WeChat 相关会话
299
+ let sessions = []
300
+ if (this._sessionPlugin) {
301
+ const allSessions = this._sessionPlugin.listSessions()
302
+ sessions = allSessions
303
+ .filter(s => s.id.startsWith('weixin_'))
304
+ .map(s => ({
305
+ userId: s.id.replace('weixin_', ''),
306
+ historyLength: s.messageCount,
307
+ lastActive: s.lastActive
308
+ }))
309
+ }
310
+
311
+ return {
312
+ connected: !!this._bot,
313
+ sessionCount: sessions.length,
314
+ sessions,
315
+ config: {
316
+ forceLogin: this.config.forceLogin,
317
+ qrcodeTerminal: this.config.qrcodeTerminal
318
+ }
319
+ }
320
+ }
321
+
322
+ /**
323
+ * 停止 Bot
324
+ */
325
+ stopBot() {
326
+ if (this._bot) {
327
+ this._bot.stop()
328
+ this._bot = null
329
+ console.log('[WeChat] Bot stopped')
330
+ }
331
+ }
332
+
333
+ reload(framework) {
334
+ this._framework = framework
335
+ this._initialized = false
336
+ this.stopBot()
337
+ this.start(framework)
338
+ }
339
+
340
+ uninstall(framework) {
341
+ this.stopBot()
342
+ this._agentsCache.clear()
343
+ this._sessionPlugin = null
344
+ this._framework = null
345
+ // 恢复 stderr
346
+ if (this._origStderrWrite) {
347
+ process.stderr.write = this._origStderrWrite
348
+ }
349
+ }
350
+ }
351
+ }