foliko 1.0.40 → 1.0.41

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.
@@ -3,628 +3,382 @@
3
3
  * 使用 @larksuiteoapi/node-sdk 的 WebSocket 长连接模式
4
4
  *
5
5
  * 配置:
6
- * - appId: 飞书应用 ID (默认从 FEISHU_APP_ID 环境变量获取)
7
- * - appSecret: 飞书应用密钥 (默认从 FEISHU_APP_SECRET 环境变量获取)
8
- * - allowedUsers: 允许的用户 openId 数组,为空则允许所有用户
6
+ * - appId: 飞书应用 ID
7
+ * - appSecret: 飞书应用密钥
8
+ * - allowedUsers: 允许的用户 openId 数组
9
9
  */
10
10
 
11
11
  const { Plugin } = require('../src/core/plugin-base')
12
12
 
13
- module.exports = function(Plugin) {
14
- return class FeishuPlugin extends Plugin {
15
- constructor(config = {}) {
16
- super()
17
- this.name = 'feishu'
18
- this.version = '1.0.0'
19
- this.description = '飞书对话插件,使用 WebSocket 长连接接收消息'
20
- this.priority = 80
21
- // 默认不启用,需要在 plugins.json 中设置 enabled: true
22
- this.enabled = false
23
- this.systemPrompt='你是一个飞书助手。回复内容不要使用markdown格式文本。'
24
-
25
- this.config = {
26
- appId: config.appId || process.env.FEISHU_APP_ID,
27
- appSecret: config.appSecret || process.env.FEISHU_APP_SECRET,
28
- allowedUsers: config.allowedUsers || []
29
- }
30
-
31
- this._framework = null
32
- this._client = null
33
- this._wsClient = null
34
- this._sessionPlugin = null
35
- this._sessionDeleteHandler = null
36
- this._sessionAgents = new Map() // openId -> Agent
37
- this._initialized = false
38
- this._processedMessages = new Set() // 消息去重
39
- this._messageCleanupInterval = null
40
- this._processingLock = new Map() // openId -> boolean (是否正在处理)
13
+ class FeishuPlugin extends Plugin {
14
+ constructor(config = {}) {
15
+ super()
16
+ this.name = 'feishu'
17
+ this.version = '1.1.0'
18
+ this.description = '飞书对话插件,使用 WebSocket 长连接接收消息'
19
+ this.priority = 80
20
+ this.enabled = false
21
+ this.systemPrompt = '你是一个飞书助手。回复内容不要使用markdown格式文本。'
22
+
23
+ this.config = {
24
+ appId: config.appId || process.env.FEISHU_APP_ID,
25
+ appSecret: config.appSecret || process.env.FEISHU_APP_SECRET,
26
+ allowedUsers: config.allowedUsers || []
41
27
  }
42
28
 
43
- install(framework) {
44
- this._framework = framework
45
- return this
46
- }
47
-
48
- start(framework) {
49
- // 防止重复初始化
50
- if (this._initialized) return this
51
- this._initialized = true
52
-
53
- if (!this.config.appId || !this.config.appSecret) {
54
- console.warn('[Feishu] No appId or appSecret. Set FEISHU_APP_ID and FEISHU_APP_SECRET env vars.')
55
- return this
56
- }
29
+ this._framework = null
30
+ this._client = null
31
+ this._wsClient = null
32
+ this._sessionPlugin = null
33
+ this._sessionDeleteHandler = null
34
+ this._sessionAgents = new Map()
35
+ this._initialized = false
36
+ this._processedMessages = new Set()
37
+ this._messageCleanupInterval = null
38
+ this._processingLock = new Map()
39
+ }
57
40
 
58
- // 获取 SessionPlugin 引用
59
- this._sessionPlugin = framework.pluginManager.get('session')
41
+ install(framework) {
42
+ this._framework = framework
43
+ return this
44
+ }
60
45
 
61
- // 监听 SessionPlugin 的会话删除事件
62
- if (this._sessionPlugin) {
63
- this._sessionDeleteHandler = (sessionId) => {
64
- // 只处理 feishu 会话的删除
65
- if (sessionId.startsWith('feishu_')) {
66
- console.log(`[Feishu] Session deleted: ${sessionId}`)
67
- }
68
- }
69
- this._sessionPlugin.on('session:deleted', this._sessionDeleteHandler)
70
- }
46
+ start(framework) {
47
+ if (this._initialized) return this
48
+ this._initialized = true
71
49
 
72
- this._initClient()
50
+ if (!this.config.appId || !this.config.appSecret) {
51
+ console.warn('[Feishu] No appId or appSecret.')
73
52
  return this
74
53
  }
75
54
 
76
- /**
77
- * 初始化飞书 WebSocket 客户端
78
- */
79
- _initClient() {
80
- try {
81
- const { Client, WSClient, EventDispatcher } = require('@larksuiteoapi/node-sdk')
55
+ this._framework = framework
56
+ this._sessionPlugin = framework.pluginManager.get('session')
82
57
 
83
- this._client = new Client({
84
- appId: this.config.appId,
85
- appSecret: this.config.appSecret,
86
- logger: console
87
- })
88
-
89
- this._wsClient = new WSClient({
90
- appId: this.config.appId,
91
- appSecret: this.config.appSecret,
92
- logger: console
93
- })
94
-
95
- // 创建事件分发器并注册处理器
96
- const eventDispatcher = new EventDispatcher({}).register({
97
- 'im.message.receive_v1': async (data) => {
98
- await this._handleMessage(data)
99
- }
100
- })
101
-
102
- // 启动 WebSocket 连接
103
- this._wsClient.start({ eventDispatcher })
104
-
105
- // 启动消息去重清理定时器(每分钟清理一次)
106
- this._messageCleanupInterval = setInterval(() => {
107
- this._processedMessages.clear()
108
- }, 60000)
109
-
110
- // 监听定时提醒事件
111
- if (this._framework) {
112
- this._framework.on('scheduler:reminder', async (data) => {
113
- console.log('[Feishu] Received scheduler reminder:', data)
114
- await this._handleScheduledReminder(data)
115
- })
58
+ if (this._sessionPlugin) {
59
+ this._sessionDeleteHandler = (sessionId) => {
60
+ if (sessionId.startsWith('feishu_')) {
61
+ console.log(`[Feishu] Session deleted: ${sessionId}`)
116
62
  }
117
-
118
- console.log('[Feishu] WebSocket client started')
119
- } catch (err) {
120
- console.error('[Feishu] Failed to initialize client:', err.message, err.stack)
121
63
  }
64
+ this._sessionPlugin.on('session:deleted', this._sessionDeleteHandler)
122
65
  }
123
66
 
124
- /**
125
- * 处理飞书消息
126
- */
127
- async _handleMessage(data) {
128
- try {
129
- // 解析消息数据 - SDK 事件格式
130
- // data 结构: { message: { chat_id, content, sender: { sender_id: { open_id }, ... }, message_type } }
131
- if (!data || !data.message || Object.keys(data.message).length === 0) {
132
- console.log('[Feishu] Invalid or empty message data:', JSON.stringify(data).substring(0, 500))
133
- return
134
- }
135
-
136
- const message = data.message
137
- const chatId = message.chat_id
138
- const messageId = message.message_id
139
- // 飞书消息结构: sender.sender_id.open_id 或 sender.open_id
140
- // 注意: 群组消息可能没有 sender 字段,此时使用 chat_id 作为会话标识
141
- const openId = message.sender?.sender_id?.open_id || message.sender?.open_id || message.open_id || chatId
142
- const messageType = message.message_type
143
- let content = {}
144
- try {
145
- content = message.content ? JSON.parse(message.content) : {}
146
- } catch (e) {
147
- console.log('[Feishu] Failed to parse message content:', message.content?.substring?.(0, 100))
148
- return
149
- }
150
-
151
- // 消息去重
152
- if (messageId && this._processedMessages.has(messageId)) {
153
- console.log(`[Feishu] Duplicate message ignored: ${messageId}`)
154
- return
155
- }
156
- if (messageId) {
157
- this._processedMessages.add(messageId)
158
- }
159
-
160
- // 忽略非文本消息
161
- if (messageType !== 'text') {
162
- console.log(`[Feishu] Unsupported message type: ${messageType}`)
163
- return
164
- }
165
-
166
- const text = content.text?.trim()
167
- if (!text) {
168
- console.log('[Feishu] Empty text message')
169
- return
170
- }
67
+ this._initClient()
68
+ return this
69
+ }
171
70
 
172
- // 权限检查
173
- if (!this._checkPermission(openId)) {
174
- console.log(`[Feishu] User ${openId} not allowed`)
175
- return
176
- }
71
+ _initClient() {
72
+ try {
73
+ const { Client, WSClient, EventDispatcher } = require('@larksuiteoapi/node-sdk')
177
74
 
178
- // 获取历史消息数量
179
- let messageCount = 0
180
- if (this._sessionPlugin) {
181
- const session = this._sessionPlugin.getSession(`feishu_${openId}`)
182
- messageCount = session?.messages?.length || 0
183
- }
75
+ this._client = new Client({
76
+ appId: this.config.appId,
77
+ appSecret: this.config.appSecret,
78
+ logger: console
79
+ })
184
80
 
185
- console.log(`[Feishu] #${messageCount + 1} | Chat: ${chatId} | User: ${openId}`)
186
- console.log(`[Feishu] Content: ${text}`)
81
+ this._wsClient = new WSClient({
82
+ appId: this.config.appId,
83
+ appSecret: this.config.appSecret,
84
+ logger: console
85
+ })
187
86
 
188
- // 命令处理
189
- if (text.startsWith('/')) {
190
- await this._handleCommand(openId, text, message)
191
- return
87
+ const eventDispatcher = new EventDispatcher({}).register({
88
+ 'im.message.receive_v1': async (data) => {
89
+ await this._handleMessage(data)
192
90
  }
91
+ })
193
92
 
194
- // 检查是否正在处理,防止并发
195
- if (this._processingLock.get(openId)) {
196
- console.log(`[Feishu] Session ${openId} is busy, ignoring message`)
197
- return
198
- }
199
- this._processingLock.set(openId, true)
93
+ this._wsClient.start({ eventDispatcher })
200
94
 
201
- try {
202
- await this._processChat(openId, text, message)
203
- } finally {
204
- this._processingLock.set(openId, false)
205
- }
95
+ this._messageCleanupInterval = setInterval(() => {
96
+ this._processedMessages.clear()
97
+ }, 60000)
206
98
 
207
- } catch (err) {
208
- console.error('[Feishu] Error handling message:', err.message, err.stack)
99
+ if (this._framework) {
100
+ this._framework.on('scheduler:reminder', async (data) => {
101
+ await this._handleScheduledReminder(data)
102
+ })
209
103
  }
210
- }
211
104
 
212
- /**
213
- * 处理命令
214
- */
215
- async _handleCommand(openId, text, originalMsg) {
216
- const parts = text.split(' ')
217
- const command = parts[0].substring(1)
218
- const args = parts.slice(1).join(' ')
219
-
220
- switch (command.toLowerCase()) {
221
- case 'start':
222
- case 'help':
223
- await this._sendMessage(openId,
224
- '👋 欢迎使用 AI 助手!\n\n' +
225
- '直接发送消息即可与我对话。\n\n' +
226
- '可用命令:\n' +
227
- '/start - 显示帮助\n' +
228
- '/clear - 清除对话历史\n' +
229
- '/history - 查看历史消息数',
230
- originalMsg)
231
- break
232
-
233
- case 'clear':
234
- this._clearSession(openId)
235
- await this._sendMessage(openId, '✅ 对话历史已清除', originalMsg)
236
- break
237
-
238
- case 'history':
239
- if (this._sessionPlugin) {
240
- const sessionId = `feishu_${openId}`
241
- const session = this._sessionPlugin.getSession(sessionId)
242
- const allSessions = this._sessionPlugin.listSessions()
243
- const feishuSessionCount = allSessions.filter(s => s.id.startsWith('feishu_')).length
244
- await this._sendMessage(openId,
245
- `📊 当前状态\n\n会话数:${feishuSessionCount}\n` +
246
- `历史消息:${session?.messages?.length || 0}\n` +
247
- `最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
248
- originalMsg)
249
- } else {
250
- await this._sendMessage(openId, '❌ 会话服务不可用', originalMsg)
251
- }
252
- break
253
-
254
- default:
255
- // 未识别命令,作为普通消息处理
256
- await this._processChat(openId, text, originalMsg)
257
- }
105
+ console.log('[Feishu] WebSocket client started')
106
+ } catch (err) {
107
+ console.error('[Feishu] Failed to initialize client:', err.message, err.stack)
258
108
  }
109
+ }
259
110
 
260
- /**
261
- * 获取或创建会话 Agent
262
- * 使用 SessionPlugin 统一管理会话历史
263
- */
264
- _getSessionAgent(openId) {
265
- // 检查缓存
266
- if (this._sessionAgents.has(openId)) {
267
- const agent = this._sessionAgents.get(openId)
268
- console.log('[Feishu] Reusing cached session agent for openId:', openId)
269
- return { agent, sessionId: `feishu_${openId}` }
111
+ async _handleMessage(data) {
112
+ try {
113
+ if (!data || !data.message || Object.keys(data.message).length === 0) {
114
+ return
270
115
  }
271
116
 
272
- // 创建新 agent
273
- const agent = this._framework.createSessionAgent(`feishu_${openId}`, {
274
- systemPrompt: this.systemPrompt
275
- })
276
- this._sessionAgents.set(openId, agent)
277
- console.log('[Feishu] Created new session agent for openId:', openId)
117
+ const message = data.message
118
+ const chatId = message.chat_id
119
+ const messageId = message.message_id
120
+ const openId = message.sender?.sender_id?.open_id || message.sender?.open_id || message.open_id || chatId
121
+ const messageType = message.message_type
122
+ let content = {}
278
123
 
279
- // 使用 SessionPlugin 管理会话历史
280
- if (this._sessionPlugin) {
281
- const sessionId = `feishu_${openId}`
282
- this._sessionPlugin.getOrCreateSession(sessionId, {
283
- metadata: { platform: 'feishu', openId }
284
- })
124
+ try {
125
+ content = message.content ? JSON.parse(message.content) : {}
126
+ } catch (e) {
127
+ return
285
128
  }
286
129
 
287
- console.log(`[Feishu] Session ready for openId: ${openId}`)
288
- return { agent, sessionId: `feishu_${openId}` }
289
- }
130
+ if (messageId && this._processedMessages.has(messageId)) {
131
+ return
132
+ }
133
+ if (messageId) this._processedMessages.add(messageId)
290
134
 
291
- /**
292
- * 处理定时提醒
293
- */
294
- async _handleScheduledReminder(data) {
295
- const { taskName, message, sessionId } = data
135
+ if (messageType !== 'text') return
296
136
 
297
- // 构建提醒消息
298
- const reminderText = `🔔 [${taskName}]\n\n${message}`
137
+ const text = content.text?.trim()
138
+ if (!text) return
299
139
 
300
- // 如果有 sessionId 是 feishu 类型的,发送到对应用户
301
- if (sessionId && sessionId.startsWith('feishu_')) {
302
- const openId = sessionId.replace('feishu_', '')
303
- try {
304
- await this._sendTextMessage(openId, reminderText)
305
- console.log(`[Feishu] Reminder sent to ${openId}`)
306
- } catch (err) {
307
- console.error(`[Feishu] Failed to send reminder:`, err.message)
308
- }
309
- return
310
- }
140
+ if (!this._checkPermission(openId)) return
311
141
 
312
- // 其他情况,发送到最近的 Feishu 会话
313
- if (this._sessionPlugin) {
314
- const allSessions = this._sessionPlugin.listSessions()
315
- const feishuSessions = allSessions
316
- .filter(s => s.id.startsWith('feishu_'))
317
- .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
318
-
319
- if (feishuSessions.length > 0) {
320
- const openId = feishuSessions[0].id.replace('feishu_', '')
321
- try {
322
- await this._sendTextMessage(openId, reminderText)
323
- console.log(`[Feishu] Reminder sent to ${openId}`)
324
- } catch (err) {
325
- console.error(`[Feishu] Failed to send reminder to ${openId}:`, err.message)
326
- }
327
- } else {
328
- console.log('[Feishu] No active Feishu sessions to send reminder')
329
- }
330
- } else {
331
- console.log('[Feishu] No active Feishu sessions to send reminder')
142
+ if (text.startsWith('/')) {
143
+ await this._handleCommand(openId, text, message)
144
+ return
332
145
  }
333
- }
334
146
 
335
- /**
336
- * 发送纯文本消息(内部使用,不经过 markdown 处理)
337
- */
338
- async _sendTextMessage(openId, text) {
339
- if (!this._client) return
147
+ if (this._processingLock.get(openId)) return
148
+ this._processingLock.set(openId, true)
340
149
 
341
150
  try {
342
- await this._client.im.message.create({
343
- params: {
344
- receive_id_type: 'chat_id'
345
- },
346
- data: {
347
- receive_id: openId,
348
- content: JSON.stringify({ text: text }),
349
- msg_type: 'text'
350
- }
351
- })
352
- } catch (err) {
353
- console.error('[Feishu] Failed to send text message:', err.message)
354
- throw err
151
+ await this._processChat(openId, text, message)
152
+ } finally {
153
+ this._processingLock.set(openId, false)
355
154
  }
155
+ } catch (err) {
156
+ console.error('[Feishu] Error handling message:', err.message)
356
157
  }
158
+ }
357
159
 
358
- /**
359
- * 处理对话
360
- */
361
- async _processChat(openId, text, originalMsg) {
362
- const sessionInfo = this._getSessionAgent(openId)
363
- if (!sessionInfo) {
364
- console.error('[Feishu] No session agent available')
365
- return
366
- }
160
+ async _handleCommand(openId, text, originalMsg) {
161
+ const parts = text.split(' ')
162
+ const command = parts[0].substring(1)
163
+ const args = parts.slice(1).join(' ')
164
+
165
+ switch (command.toLowerCase()) {
166
+ case 'start':
167
+ case 'help':
168
+ await this._sendMessage(openId,
169
+ '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
170
+ originalMsg)
171
+ break
172
+ case 'clear':
173
+ this._clearSession(openId)
174
+ await this._sendMessage(openId, '✅ 对话历史已清除', originalMsg)
175
+ break
176
+ case 'history':
177
+ if (this._sessionPlugin) {
178
+ const session = this._sessionPlugin.getSession(`feishu_${openId}`)
179
+ const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('feishu_'))
180
+ await this._sendMessage(openId,
181
+ `📊 当前状态\n\n会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
182
+ originalMsg)
183
+ } else {
184
+ await this._sendMessage(openId, '❌ 会话服务不可用', originalMsg)
185
+ }
186
+ break
187
+ default:
188
+ await this._processChat(openId, text, originalMsg)
189
+ }
190
+ }
367
191
 
368
- const { agent, sessionId } = sessionInfo
192
+ _getSessionAgent(openId) {
193
+ if (this._sessionAgents.has(openId)) {
194
+ return { agent: this._sessionAgents.get(openId), sessionId: `feishu_${openId}` }
195
+ }
369
196
 
370
- // 使用 SessionPlugin 添加用户消息到历史
371
- if (this._sessionPlugin) {
372
- this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
373
- }
197
+ const agent = this._framework.createSessionAgent(`feishu_${openId}`, {
198
+ systemPrompt: this.systemPrompt
199
+ })
200
+ this._sessionAgents.set(openId, agent)
374
201
 
375
- try {
376
- let fullResponse = ''
377
-
378
- // 使用流式响应
379
- for await (const chunk of agent.chatStream(text, {
380
- sessionId: sessionId
381
- })) {
382
- if (chunk.type === 'text' && chunk.text) {
383
- fullResponse += chunk.text
384
- }
385
- }
202
+ if (this._sessionPlugin) {
203
+ this._sessionPlugin.getOrCreateSession(`feishu_${openId}`, {
204
+ metadata: { platform: 'feishu', openId }
205
+ })
206
+ }
386
207
 
387
- // 保存助手回复到历史(使用 SessionPlugin)
388
- if (this._sessionPlugin) {
389
- this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
390
- }
208
+ return { agent, sessionId: `feishu_${openId}` }
209
+ }
391
210
 
392
- // 发送回复
393
- if (fullResponse) {
394
- await this._sendMessage(openId, fullResponse, originalMsg)
395
- console.log(`[Feishu] Reply sent (${fullResponse.length} characters)`)
396
- } else {
397
- await this._sendMessage(openId, '抱歉,我没有收到有效的回复。', originalMsg)
398
- }
211
+ async _handleScheduledReminder(data) {
212
+ const { taskName, message, sessionId } = data
213
+ const reminderText = `🔔 [${taskName}]\n\n${message}`
399
214
 
215
+ if (sessionId && sessionId.startsWith('feishu_')) {
216
+ const openId = sessionId.replace('feishu_', '')
217
+ try {
218
+ await this._sendTextMessage(openId, reminderText)
400
219
  } catch (err) {
401
- console.error('[Feishu] Chat error:', err)
402
- await this._sendMessage(openId, `发生错误:${err.message}`, originalMsg)
220
+ console.error(`[Feishu] Failed to send reminder:`, err.message)
403
221
  }
222
+ return
404
223
  }
405
224
 
406
- /**
407
- * 发送消息到飞书
408
- * 使用 post 富文本类型
409
- */
410
- async _sendMessage(openId, text, originalMsg) {
411
- if (!this._client) return
225
+ if (this._sessionPlugin) {
226
+ const sessions = this._sessionPlugin.listSessions()
227
+ .filter(s => s.id.startsWith('feishu_'))
228
+ .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
412
229
 
413
- try {
414
- // 获取用户所在的聊天 ID(回复到原消息所在的聊天)
415
- const chatId = originalMsg?.chat_id || originalMsg?.message?.chat_id
416
- if (!chatId) {
417
- console.error('[Feishu] No chat_id found for reply')
418
- return
230
+ if (sessions.length > 0) {
231
+ const openId = sessions[0].id.replace('feishu_', '')
232
+ try {
233
+ await this._sendTextMessage(openId, reminderText)
234
+ } catch (err) {
235
+ console.error(`[Feishu] Failed to send reminder:`, err.message)
419
236
  }
420
-
421
- // 暂时用 text 类型,等解决 post 权限问题后再切换
422
- await this._client.im.message.create({
423
- params: {
424
- receive_id_type: 'chat_id'
425
- },
426
- data: {
427
- receive_id: chatId,
428
- content: JSON.stringify({ text: text }),
429
- msg_type: 'text'
430
- }
431
- })
432
- } catch (err) {
433
- console.error('[Feishu] Failed to send message:', err.message, err.response?.data)
434
237
  }
435
238
  }
239
+ }
436
240
 
437
- /**
438
- * 构建飞书富文本内容
439
- */
440
- _buildPostContent(text) {
441
- const lines = text.split('\n')
442
- const paragraphs = []
443
-
444
- for (const line of lines) {
445
- if (line.trim() === '') {
446
- paragraphs.push([{ tag: 'text', text: ' ' }])
447
- continue
448
- }
241
+ async _processChat(openId, text, originalMsg) {
242
+ const sessionInfo = this._getSessionAgent(openId)
243
+ if (!sessionInfo) return
449
244
 
450
- const segments = []
451
- let remaining = line
452
-
453
- while (remaining.length > 0) {
454
- // 匹配加粗 **text**
455
- const boldMatch = remaining.match(/^\*\*(.+?)\*\*/)
456
- if (boldMatch) {
457
- segments.push({ tag: 'text', text: boldMatch[1], bold: true })
458
- remaining = remaining.slice(boldMatch[0].length)
459
- continue
460
- }
461
-
462
- // 匹配斜体 *text* 或 _text_
463
- const italicMatch = remaining.match(/^\*([^*]+?)\*|^_([^_]+?)_/)
464
- if (italicMatch) {
465
- const italicText = italicMatch[1] !== undefined ? italicMatch[1] : italicMatch[2]
466
- segments.push({ tag: 'text', text: italicText, italic: true })
467
- remaining = remaining.slice(italicMatch[0].length)
468
- continue
469
- }
470
-
471
- // 匹配行内代码 `code`
472
- const codeMatch = remaining.match(/^`([^`]+?)`/)
473
- if (codeMatch) {
474
- segments.push({ tag: 'text', text: codeMatch[1], code: true })
475
- remaining = remaining.slice(codeMatch[0].length)
476
- continue
477
- }
478
-
479
- // 匹配删除线 ~~text~~
480
- const delMatch = remaining.match(/^~~(.+?)~~/)
481
- if (delMatch) {
482
- segments.push({ tag: 'text', text: delMatch[1], strikethrough: true })
483
- remaining = remaining.slice(delMatch[0].length)
484
- continue
485
- }
486
-
487
- // 匹配链接 [text](url)
488
- const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/)
489
- if (linkMatch) {
490
- segments.push({ tag: 'a', text: linkMatch[1], href: linkMatch[2] })
491
- remaining = remaining.slice(linkMatch[0].length)
492
- continue
493
- }
494
-
495
- // 普通文本
496
- const nextSpecial = remaining.search(/(\*|_|`|~~|\[)/)
497
- if (nextSpecial === -1) {
498
- segments.push({ tag: 'text', text: remaining })
499
- break
500
- } else if (nextSpecial === 0) {
501
- segments.push({ tag: 'text', text: remaining[0] })
502
- remaining = remaining.slice(1)
503
- } else {
504
- segments.push({ tag: 'text', text: remaining.slice(0, nextSpecial) })
505
- remaining = remaining.slice(nextSpecial)
506
- }
507
- }
245
+ const { agent, sessionId } = sessionInfo
246
+
247
+ if (this._sessionPlugin) {
248
+ this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
249
+ }
508
250
 
509
- if (segments.length > 0) {
510
- paragraphs.push(segments)
251
+ try {
252
+ let fullResponse = ''
253
+ for await (const chunk of agent.chatStream(text, { sessionId })) {
254
+ if (chunk.type === 'text' && chunk.text) {
255
+ fullResponse += chunk.text
511
256
  }
512
257
  }
513
258
 
514
- if (paragraphs.length === 0) {
515
- paragraphs.push([{ tag: 'text', text: ' ' }])
259
+ if (this._sessionPlugin) {
260
+ this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
516
261
  }
517
262
 
518
- return {
519
- post: {
520
- zh_cn: {
521
- title: '',
522
- content: paragraphs
523
- }
524
- }
263
+ if (fullResponse) {
264
+ await this._sendMessage(openId, fullResponse, originalMsg)
265
+ } else {
266
+ await this._sendMessage(openId, '抱歉,我没有收到有效的回复。', originalMsg)
525
267
  }
268
+ } catch (err) {
269
+ console.error('[Feishu] Chat error:', err)
270
+ await this._sendMessage(openId, `发生错误:${err.message}`, originalMsg)
526
271
  }
272
+ }
527
273
 
528
- /**
529
- * 权限检查
530
- */
531
- _checkPermission(openId) {
532
- if (!this.config.allowedUsers || this.config.allowedUsers.length === 0) {
533
- return true
534
- }
535
- return this.config.allowedUsers.includes(openId)
274
+ async _sendTextMessage(openId, text) {
275
+ if (!this._client) return
276
+ try {
277
+ await this._client.im.message.create({
278
+ params: { receive_id_type: 'chat_id' },
279
+ data: {
280
+ receive_id: openId,
281
+ content: JSON.stringify({ text }),
282
+ msg_type: 'text'
283
+ }
284
+ })
285
+ } catch (err) {
286
+ console.error('[Feishu] Failed to send text message:', err.message)
287
+ throw err
536
288
  }
289
+ }
537
290
 
538
- /**
539
- * 清除会话
540
- */
541
- _clearSession(openId) {
542
- // 清除 SessionPlugin 中的会话(会触发 session:deleted 事件)
543
- if (this._sessionPlugin) {
544
- const sessionId = `feishu_${openId}`
545
- this._sessionPlugin.deleteSession(sessionId)
546
- }
291
+ async _sendMessage(openId, text, originalMsg) {
292
+ if (!this._client) return
293
+ try {
294
+ const chatId = originalMsg?.chat_id || originalMsg?.message?.chat_id
295
+ if (!chatId) return
296
+
297
+ await this._client.im.message.create({
298
+ params: { receive_id_type: 'chat_id' },
299
+ data: {
300
+ receive_id: chatId,
301
+ content: JSON.stringify({ text }),
302
+ msg_type: 'text'
303
+ }
304
+ })
305
+ } catch (err) {
306
+ console.error('[Feishu] Failed to send message:', err.message)
547
307
  }
308
+ }
548
309
 
549
- /**
550
- * 获取插件状态
551
- */
552
- getStatus() {
553
- // 从 SessionPlugin 获取 Feishu 相关会话
554
- let sessions = []
555
- if (this._sessionPlugin) {
556
- const allSessions = this._sessionPlugin.listSessions()
557
- sessions = allSessions
558
- .filter(s => s.id.startsWith('feishu_'))
559
- .map(s => ({
560
- openId: s.id.replace('feishu_', ''),
561
- historyLength: s.messageCount,
562
- lastActive: s.lastActive
563
- }))
564
- }
310
+ _checkPermission(openId) {
311
+ return this.config.allowedUsers.length === 0 || this.config.allowedUsers.includes(openId)
312
+ }
565
313
 
566
- return {
567
- connected: !!this._wsClient,
568
- sessionCount: sessions.length,
569
- sessions,
570
- config: {
571
- appId: this.config.appId ? `${this.config.appId.substring(0, 10)}...` : null,
572
- allowedUsersCount: this.config.allowedUsers.length
573
- }
574
- }
314
+ _clearSession(openId) {
315
+ if (this._sessionPlugin) {
316
+ this._sessionPlugin.deleteSession(`feishu_${openId}`)
575
317
  }
318
+ }
576
319
 
577
- /**
578
- * 停止客户端
579
- */
580
- stopClient() {
581
- if (this._wsClient) {
582
- try {
583
- this._wsClient.close()
584
- } catch (e) {
585
- // ignore close error
586
- }
587
- this._wsClient = null
588
- this._client = null
589
- console.log('[Feishu] Client stopped')
320
+ getStatus() {
321
+ let sessions = []
322
+ if (this._sessionPlugin) {
323
+ sessions = this._sessionPlugin.listSessions()
324
+ .filter(s => s.id.startsWith('feishu_'))
325
+ .map(s => ({
326
+ openId: s.id.replace('feishu_', ''),
327
+ historyLength: s.messageCount,
328
+ lastActive: s.lastActive
329
+ }))
330
+ }
331
+ return {
332
+ connected: !!this._wsClient,
333
+ sessionCount: sessions.length,
334
+ sessions,
335
+ config: {
336
+ appId: this.config.appId ? `${this.config.appId.substring(0, 10)}...` : null,
337
+ allowedUsersCount: this.config.allowedUsers.length
590
338
  }
591
339
  }
340
+ }
592
341
 
593
- /**
594
- * 停止插件(实现 PluginManager 接口)
595
- */
596
- stop() {
597
- this.stopClient()
342
+ stopClient() {
343
+ if (this._wsClient) {
344
+ try { this._wsClient.close() } catch (e) { /* ignore */ }
345
+ this._wsClient = null
346
+ this._client = null
347
+ console.log('[Feishu] Client stopped')
598
348
  }
349
+ }
599
350
 
600
- reload(framework) {
601
- this._framework = framework
602
- this._initialized = false
603
- this.stopClient()
604
- this.start(framework)
605
- }
351
+ stop() {
352
+ this.stopClient()
353
+ }
606
354
 
607
- uninstall(framework) {
608
- // 销毁所有 session agents
609
- for (const agent of this._sessionAgents.values()) {
610
- agent.destroy()
611
- }
612
- this._sessionAgents.clear()
355
+ reload(framework) {
356
+ this._framework = framework
357
+ this._initialized = false
358
+ this.stopClient()
359
+ this.start(framework)
360
+ }
613
361
 
614
- // 清理去重定时器
615
- if (this._messageCleanupInterval) {
616
- clearInterval(this._messageCleanupInterval)
617
- this._messageCleanupInterval = null
618
- }
619
- this._processedMessages.clear()
362
+ uninstall() {
363
+ for (const agent of this._sessionAgents.values()) {
364
+ agent.destroy()
365
+ }
366
+ this._sessionAgents.clear()
620
367
 
621
- this.stopClient()
622
- if (this._sessionPlugin && this._sessionDeleteHandler) {
623
- this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
624
- this._sessionDeleteHandler = null
625
- }
626
- this._sessionPlugin = null
627
- this._framework = null
368
+ if (this._messageCleanupInterval) {
369
+ clearInterval(this._messageCleanupInterval)
370
+ this._messageCleanupInterval = null
628
371
  }
372
+ this._processedMessages.clear()
373
+
374
+ this.stopClient()
375
+ if (this._sessionPlugin && this._sessionDeleteHandler) {
376
+ this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
377
+ this._sessionDeleteHandler = null
378
+ }
379
+ this._sessionPlugin = null
380
+ this._framework = null
629
381
  }
630
382
  }
383
+
384
+ module.exports = { FeishuPlugin }