foliko 1.0.40 → 1.0.43

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,389 @@
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
+ **命令执行规范:**
24
+ - 执行 npx skills add / npx skills remove / npx skills list 命令时,必须自动添加参数:-a openclaw -y
25
+ - 禁止添加 -g 参数
26
+ - 例如:npx skills add xxx 应该执行为 npx skills add xxx -a openclaw -y`
27
+
28
+ this.config = {
29
+ appId: config.appId || process.env.FEISHU_APP_ID,
30
+ appSecret: config.appSecret || process.env.FEISHU_APP_SECRET,
31
+ allowedUsers: config.allowedUsers || []
41
32
  }
42
33
 
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
- }
34
+ this._framework = null
35
+ this._client = null
36
+ this._wsClient = null
37
+ this._sessionPlugin = null
38
+ this._sessionDeleteHandler = null
39
+ this._sessionAgents = new Map()
40
+ this._initialized = false
41
+ this._processedMessages = new Set()
42
+ this._messageCleanupInterval = null
43
+ this._processingLock = new Map()
44
+ }
57
45
 
58
- // 获取 SessionPlugin 引用
59
- this._sessionPlugin = framework.pluginManager.get('session')
46
+ install(framework) {
47
+ this._framework = framework
48
+ return this
49
+ }
60
50
 
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
- }
51
+ start(framework) {
52
+ if (this._initialized) return this
53
+ this._initialized = true
71
54
 
72
- this._initClient()
55
+ if (!this.config.appId || !this.config.appSecret) {
56
+ console.warn('[Feishu] No appId or appSecret.')
73
57
  return this
74
58
  }
75
59
 
76
- /**
77
- * 初始化飞书 WebSocket 客户端
78
- */
79
- _initClient() {
80
- try {
81
- const { Client, WSClient, EventDispatcher } = require('@larksuiteoapi/node-sdk')
60
+ this._framework = framework
61
+ this._sessionPlugin = framework.pluginManager.get('session')
82
62
 
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
- })
63
+ if (this._sessionPlugin) {
64
+ this._sessionDeleteHandler = (sessionId) => {
65
+ if (sessionId.startsWith('feishu_')) {
66
+ console.log(`[Feishu] Session deleted: ${sessionId}`)
116
67
  }
117
-
118
- console.log('[Feishu] WebSocket client started')
119
- } catch (err) {
120
- console.error('[Feishu] Failed to initialize client:', err.message, err.stack)
121
68
  }
69
+ this._sessionPlugin.on('session:deleted', this._sessionDeleteHandler)
122
70
  }
123
71
 
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
- }
72
+ this._initClient()
73
+ return this
74
+ }
171
75
 
172
- // 权限检查
173
- if (!this._checkPermission(openId)) {
174
- console.log(`[Feishu] User ${openId} not allowed`)
175
- return
176
- }
76
+ _initClient() {
77
+ try {
78
+ const { Client, WSClient, EventDispatcher } = require('@larksuiteoapi/node-sdk')
177
79
 
178
- // 获取历史消息数量
179
- let messageCount = 0
180
- if (this._sessionPlugin) {
181
- const session = this._sessionPlugin.getSession(`feishu_${openId}`)
182
- messageCount = session?.messages?.length || 0
183
- }
80
+ this._client = new Client({
81
+ appId: this.config.appId,
82
+ appSecret: this.config.appSecret,
83
+ logger: console
84
+ })
184
85
 
185
- console.log(`[Feishu] #${messageCount + 1} | Chat: ${chatId} | User: ${openId}`)
186
- console.log(`[Feishu] Content: ${text}`)
86
+ this._wsClient = new WSClient({
87
+ appId: this.config.appId,
88
+ appSecret: this.config.appSecret,
89
+ logger: console
90
+ })
187
91
 
188
- // 命令处理
189
- if (text.startsWith('/')) {
190
- await this._handleCommand(openId, text, message)
191
- return
92
+ const eventDispatcher = new EventDispatcher({}).register({
93
+ 'im.message.receive_v1': async (data) => {
94
+ await this._handleMessage(data)
192
95
  }
96
+ })
193
97
 
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)
98
+ this._wsClient.start({ eventDispatcher })
200
99
 
201
- try {
202
- await this._processChat(openId, text, message)
203
- } finally {
204
- this._processingLock.set(openId, false)
205
- }
100
+ this._messageCleanupInterval = setInterval(() => {
101
+ this._processedMessages.clear()
102
+ }, 60000)
206
103
 
207
- } catch (err) {
208
- console.error('[Feishu] Error handling message:', err.message, err.stack)
104
+ if (this._framework) {
105
+ this._framework.on('scheduler:reminder', async (data) => {
106
+ await this._handleScheduledReminder(data)
107
+ })
209
108
  }
210
- }
211
109
 
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
- }
110
+ console.log('[Feishu] WebSocket client started')
111
+ } catch (err) {
112
+ console.error('[Feishu] Failed to initialize client:', err.message, err.stack)
258
113
  }
114
+ }
259
115
 
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}` }
116
+ async _handleMessage(data) {
117
+ try {
118
+ if (!data || !data.message || Object.keys(data.message).length === 0) {
119
+ return
270
120
  }
271
121
 
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)
122
+ const message = data.message
123
+ const chatId = message.chat_id
124
+ const messageId = message.message_id
125
+ const openId = message.sender?.sender_id?.open_id || message.sender?.open_id || message.open_id || chatId
126
+ const messageType = message.message_type
127
+ let content = {}
278
128
 
279
- // 使用 SessionPlugin 管理会话历史
280
- if (this._sessionPlugin) {
281
- const sessionId = `feishu_${openId}`
282
- this._sessionPlugin.getOrCreateSession(sessionId, {
283
- metadata: { platform: 'feishu', openId }
284
- })
129
+ try {
130
+ content = message.content ? JSON.parse(message.content) : {}
131
+ } catch (e) {
132
+ return
285
133
  }
286
134
 
287
- console.log(`[Feishu] Session ready for openId: ${openId}`)
288
- return { agent, sessionId: `feishu_${openId}` }
289
- }
135
+ if (messageId && this._processedMessages.has(messageId)) {
136
+ return
137
+ }
138
+ if (messageId) this._processedMessages.add(messageId)
290
139
 
291
- /**
292
- * 处理定时提醒
293
- */
294
- async _handleScheduledReminder(data) {
295
- const { taskName, message, sessionId } = data
140
+ if (messageType !== 'text') return
296
141
 
297
- // 构建提醒消息
298
- const reminderText = `🔔 [${taskName}]\n\n${message}`
142
+ const text = content.text?.trim()
143
+ if (!text) return
299
144
 
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
- }
145
+ if (!this._checkPermission(openId)) return
311
146
 
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')
147
+ if (text.startsWith('/')) {
148
+ await this._handleCommand(openId, text, message)
149
+ return
332
150
  }
333
- }
334
151
 
335
- /**
336
- * 发送纯文本消息(内部使用,不经过 markdown 处理)
337
- */
338
- async _sendTextMessage(openId, text) {
339
- if (!this._client) return
152
+ if (this._processingLock.get(openId)) return
153
+ this._processingLock.set(openId, true)
340
154
 
341
155
  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
156
+ await this._processChat(openId, text, message)
157
+ } finally {
158
+ this._processingLock.set(openId, false)
355
159
  }
160
+ } catch (err) {
161
+ console.error('[Feishu] Error handling message:', err.message)
356
162
  }
163
+ }
357
164
 
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
- }
165
+ async _handleCommand(openId, text, originalMsg) {
166
+ const parts = text.split(' ')
167
+ const command = parts[0].substring(1)
168
+ const args = parts.slice(1).join(' ')
169
+
170
+ switch (command.toLowerCase()) {
171
+ case 'start':
172
+ case 'help':
173
+ await this._sendMessage(openId,
174
+ '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
175
+ originalMsg)
176
+ break
177
+ case 'clear':
178
+ this._clearSession(openId)
179
+ await this._sendMessage(openId, '✅ 对话历史已清除', originalMsg)
180
+ break
181
+ case 'history':
182
+ if (this._sessionPlugin) {
183
+ const session = this._sessionPlugin.getSession(`feishu_${openId}`)
184
+ const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('feishu_'))
185
+ await this._sendMessage(openId,
186
+ `📊 当前状态\n\n会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
187
+ originalMsg)
188
+ } else {
189
+ await this._sendMessage(openId, '❌ 会话服务不可用', originalMsg)
190
+ }
191
+ break
192
+ default:
193
+ await this._processChat(openId, text, originalMsg)
194
+ }
195
+ }
367
196
 
368
- const { agent, sessionId } = sessionInfo
197
+ _getSessionAgent(openId) {
198
+ if (this._sessionAgents.has(openId)) {
199
+ return { agent: this._sessionAgents.get(openId), sessionId: `feishu_${openId}` }
200
+ }
369
201
 
370
- // 使用 SessionPlugin 添加用户消息到历史
371
- if (this._sessionPlugin) {
372
- this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
373
- }
202
+ const agent = this._framework.createSessionAgent(`feishu_${openId}`, {
203
+ systemPrompt: this.systemPrompt,
204
+ sharedPrompt: `工作目录: {{WORK_DIR}}`,
205
+ metadata: { WORK_DIR: process.cwd() }
206
+ })
207
+ this._sessionAgents.set(openId, agent)
374
208
 
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
- }
209
+ if (this._sessionPlugin) {
210
+ this._sessionPlugin.getOrCreateSession(`feishu_${openId}`, {
211
+ metadata: { platform: 'feishu', openId }
212
+ })
213
+ }
386
214
 
387
- // 保存助手回复到历史(使用 SessionPlugin)
388
- if (this._sessionPlugin) {
389
- this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
390
- }
215
+ return { agent, sessionId: `feishu_${openId}` }
216
+ }
391
217
 
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
- }
218
+ async _handleScheduledReminder(data) {
219
+ const { taskName, message, sessionId } = data
220
+ const reminderText = `🔔 [${taskName}]\n\n${message}`
399
221
 
222
+ if (sessionId && sessionId.startsWith('feishu_')) {
223
+ const openId = sessionId.replace('feishu_', '')
224
+ try {
225
+ await this._sendTextMessage(openId, reminderText)
400
226
  } catch (err) {
401
- console.error('[Feishu] Chat error:', err)
402
- await this._sendMessage(openId, `发生错误:${err.message}`, originalMsg)
227
+ console.error(`[Feishu] Failed to send reminder:`, err.message)
403
228
  }
229
+ return
404
230
  }
405
231
 
406
- /**
407
- * 发送消息到飞书
408
- * 使用 post 富文本类型
409
- */
410
- async _sendMessage(openId, text, originalMsg) {
411
- if (!this._client) return
232
+ if (this._sessionPlugin) {
233
+ const sessions = this._sessionPlugin.listSessions()
234
+ .filter(s => s.id.startsWith('feishu_'))
235
+ .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
412
236
 
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
237
+ if (sessions.length > 0) {
238
+ const openId = sessions[0].id.replace('feishu_', '')
239
+ try {
240
+ await this._sendTextMessage(openId, reminderText)
241
+ } catch (err) {
242
+ console.error(`[Feishu] Failed to send reminder:`, err.message)
419
243
  }
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
244
  }
435
245
  }
246
+ }
436
247
 
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
- }
248
+ async _processChat(openId, text, originalMsg) {
249
+ const sessionInfo = this._getSessionAgent(openId)
250
+ if (!sessionInfo) return
449
251
 
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
- }
252
+ const { agent, sessionId } = sessionInfo
253
+
254
+ if (this._sessionPlugin) {
255
+ this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
256
+ }
508
257
 
509
- if (segments.length > 0) {
510
- paragraphs.push(segments)
258
+ try {
259
+ let fullResponse = ''
260
+ for await (const chunk of agent.chatStream(text, { sessionId })) {
261
+ if (chunk.type === 'text' && chunk.text) {
262
+ fullResponse += chunk.text
511
263
  }
512
264
  }
513
265
 
514
- if (paragraphs.length === 0) {
515
- paragraphs.push([{ tag: 'text', text: ' ' }])
266
+ if (this._sessionPlugin) {
267
+ this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
516
268
  }
517
269
 
518
- return {
519
- post: {
520
- zh_cn: {
521
- title: '',
522
- content: paragraphs
523
- }
524
- }
270
+ if (fullResponse) {
271
+ await this._sendMessage(openId, fullResponse, originalMsg)
272
+ } else {
273
+ await this._sendMessage(openId, '抱歉,我没有收到有效的回复。', originalMsg)
525
274
  }
275
+ } catch (err) {
276
+ console.error('[Feishu] Chat error:', err)
277
+ await this._sendMessage(openId, `发生错误:${err.message}`, originalMsg)
526
278
  }
279
+ }
527
280
 
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)
281
+ async _sendTextMessage(openId, text) {
282
+ if (!this._client) return
283
+ try {
284
+ await this._client.im.message.create({
285
+ params: { receive_id_type: 'chat_id' },
286
+ data: {
287
+ receive_id: openId,
288
+ content: JSON.stringify({ text }),
289
+ msg_type: 'text'
290
+ }
291
+ })
292
+ } catch (err) {
293
+ console.error('[Feishu] Failed to send text message:', err.message)
294
+ throw err
536
295
  }
296
+ }
537
297
 
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
- }
298
+ async _sendMessage(openId, text, originalMsg) {
299
+ if (!this._client) return
300
+ try {
301
+ const chatId = originalMsg?.chat_id || originalMsg?.message?.chat_id
302
+ if (!chatId) return
303
+
304
+ await this._client.im.message.create({
305
+ params: { receive_id_type: 'chat_id' },
306
+ data: {
307
+ receive_id: chatId,
308
+ content: JSON.stringify({ text }),
309
+ msg_type: 'text'
310
+ }
311
+ })
312
+ } catch (err) {
313
+ console.error('[Feishu] Failed to send message:', err.message)
547
314
  }
315
+ }
548
316
 
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
- }
317
+ _checkPermission(openId) {
318
+ return this.config.allowedUsers.length === 0 || this.config.allowedUsers.includes(openId)
319
+ }
565
320
 
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
- }
321
+ _clearSession(openId) {
322
+ if (this._sessionPlugin) {
323
+ this._sessionPlugin.deleteSession(`feishu_${openId}`)
575
324
  }
325
+ }
576
326
 
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')
327
+ getStatus() {
328
+ let sessions = []
329
+ if (this._sessionPlugin) {
330
+ sessions = this._sessionPlugin.listSessions()
331
+ .filter(s => s.id.startsWith('feishu_'))
332
+ .map(s => ({
333
+ openId: s.id.replace('feishu_', ''),
334
+ historyLength: s.messageCount,
335
+ lastActive: s.lastActive
336
+ }))
337
+ }
338
+ return {
339
+ connected: !!this._wsClient,
340
+ sessionCount: sessions.length,
341
+ sessions,
342
+ config: {
343
+ appId: this.config.appId ? `${this.config.appId.substring(0, 10)}...` : null,
344
+ allowedUsersCount: this.config.allowedUsers.length
590
345
  }
591
346
  }
347
+ }
592
348
 
593
- /**
594
- * 停止插件(实现 PluginManager 接口)
595
- */
596
- stop() {
597
- this.stopClient()
349
+ stopClient() {
350
+ if (this._wsClient) {
351
+ try { this._wsClient.close() } catch (e) { /* ignore */ }
352
+ this._wsClient = null
353
+ this._client = null
354
+ console.log('[Feishu] Client stopped')
598
355
  }
356
+ }
599
357
 
600
- reload(framework) {
601
- this._framework = framework
602
- this._initialized = false
603
- this.stopClient()
604
- this.start(framework)
605
- }
358
+ stop() {
359
+ this.stopClient()
360
+ }
606
361
 
607
- uninstall(framework) {
608
- // 销毁所有 session agents
609
- for (const agent of this._sessionAgents.values()) {
610
- agent.destroy()
611
- }
612
- this._sessionAgents.clear()
362
+ reload(framework) {
363
+ this._framework = framework
364
+ this._initialized = false
365
+ this.stopClient()
366
+ this.start(framework)
367
+ }
613
368
 
614
- // 清理去重定时器
615
- if (this._messageCleanupInterval) {
616
- clearInterval(this._messageCleanupInterval)
617
- this._messageCleanupInterval = null
618
- }
619
- this._processedMessages.clear()
369
+ uninstall() {
370
+ for (const agent of this._sessionAgents.values()) {
371
+ agent.destroy()
372
+ }
373
+ this._sessionAgents.clear()
620
374
 
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
375
+ if (this._messageCleanupInterval) {
376
+ clearInterval(this._messageCleanupInterval)
377
+ this._messageCleanupInterval = null
628
378
  }
379
+ this._processedMessages.clear()
380
+
381
+ this.stopClient()
382
+ if (this._sessionPlugin && this._sessionDeleteHandler) {
383
+ this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
384
+ this._sessionDeleteHandler = null
385
+ }
386
+ this._sessionPlugin = null
387
+ this._framework = null
629
388
  }
630
389
  }
390
+
391
+ module.exports = { FeishuPlugin }