claw-subagent-service 0.0.115 → 0.0.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.115",
3
+ "version": "0.0.117",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -29,6 +29,10 @@ class MessageHandler {
29
29
  this._groupConfigCache = new Map();
30
30
  this._defaultMaxRounds = 10;
31
31
  this._groupConfigCacheTTL = config.groupConfigCacheTTL || 60000; // 默认缓存 60 秒
32
+
33
+ // 消息合并相关
34
+ this._pendingMessages = new Map(); // 待合并的消息
35
+ this._messageMergeTimeout = 500; // 合并等待时间(毫秒)
32
36
  }
33
37
 
34
38
  /**
@@ -215,42 +219,102 @@ class MessageHandler {
215
219
  });
216
220
  }
217
221
 
218
- // 如果配置了代理地址,使用流式处理
219
- if (this.isStreamingEnabled) {
220
- try {
221
- await this.handleNormalMessageStream(msg);
222
- // 流式处理成功,群聊轮数 +1
223
- if (msg.conversationType === 3) {
224
- this._incrementGroupRoundCount(msg.targetId, maxRounds);
225
- }
226
- } catch (err) {
227
- this.log?.error(`[MessageHandler] 流式处理失败,回退到非流式: ${err.message}`);
228
- const reply = await this.handleNormalMessage(msg);
229
- if (reply) {
230
- const targetId = this.getReplyTarget(msg);
231
- await this.sendFn(targetId, reply, msg.conversationType);
232
- // 非流式回退成功,群聊轮数 +1
233
- if (msg.conversationType === 3) {
234
- this._incrementGroupRoundCount(msg.targetId, maxRounds);
235
- }
236
- }
222
+ // 消息合并逻辑:如果是图片消息,等待一段时间看是否有文字消息跟随
223
+ if (msg.messageType === 'RC:ImgMsg') {
224
+ await this._handleImageMessageWithMerge(msg, maxRounds);
225
+ return;
226
+ }
227
+
228
+ // 普通消息直接处理
229
+ await this._processMessage(msg, maxRounds);
230
+ } catch (err) {
231
+ this.log?.error(`[MessageHandler] 处理消息异常: ${err.message}`);
232
+ const targetId = msg.conversationType === 3 ? msg.targetId : msg.senderUserId;
233
+ await this.sendFn(targetId, `处理失败: ${err.message}`, msg.conversationType);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * 处理图片消息,支持合并后续文字消息
239
+ */
240
+ async _handleImageMessageWithMerge(msg, maxRounds) {
241
+ const userId = msg.senderUserId;
242
+ const conversationKey = `${msg.conversationType}-${msg.targetId}-${userId}`;
243
+
244
+ // 设置待处理图片消息
245
+ this._pendingMessages.set(conversationKey, {
246
+ imageMsg: msg,
247
+ timestamp: Date.now(),
248
+ });
249
+
250
+ // 等待一段时间,看是否有文字消息跟随
251
+ await new Promise(resolve => setTimeout(resolve, this._messageMergeTimeout));
252
+
253
+ // 获取待处理消息
254
+ const pending = this._pendingMessages.get(conversationKey);
255
+ this._pendingMessages.delete(conversationKey);
256
+
257
+ if (!pending) {
258
+ return; // 消息已被处理
259
+ }
260
+
261
+ // 构建合并后的消息内容
262
+ const imageContent = this._extractMessageContent(pending.imageMsg);
263
+ let mergedContent = imageContent;
264
+
265
+ if (pending.textMsg) {
266
+ const textContent = typeof pending.textMsg.content === 'string'
267
+ ? pending.textMsg.content
268
+ : (pending.textMsg.content?.content || '');
269
+ mergedContent = `${textContent}\n${imageContent}`;
270
+ this.log?.info(`[MessageHandler] 合并消息: 图片+文字`);
271
+ }
272
+
273
+ // 创建合并后的消息对象
274
+ const mergedMsg = {
275
+ ...pending.imageMsg,
276
+ content: mergedContent,
277
+ messageType: 'RC:TxtMsg', // 转为文本消息处理
278
+ };
279
+
280
+ await this._processMessage(mergedMsg, maxRounds);
281
+ }
282
+
283
+ /**
284
+ * 处理普通消息(包括合并后的消息)
285
+ */
286
+ async _processMessage(msg, maxRounds) {
287
+ // 如果配置了代理地址,使用流式处理
288
+ if (this.isStreamingEnabled) {
289
+ try {
290
+ await this.handleNormalMessageStream(msg);
291
+ // 流式处理成功,群聊轮数 +1
292
+ if (msg.conversationType === 3) {
293
+ this._incrementGroupRoundCount(msg.targetId, maxRounds);
237
294
  }
238
- } else {
239
- // 降级到非流式处理
295
+ } catch (err) {
296
+ this.log?.error(`[MessageHandler] 流式处理失败,回退到非流式: ${err.message}`);
240
297
  const reply = await this.handleNormalMessage(msg);
241
298
  if (reply) {
242
299
  const targetId = this.getReplyTarget(msg);
243
300
  await this.sendFn(targetId, reply, msg.conversationType);
244
- // 非流式处理成功,群聊轮数 +1
301
+ // 非流式回退成功,群聊轮数 +1
245
302
  if (msg.conversationType === 3) {
246
303
  this._incrementGroupRoundCount(msg.targetId, maxRounds);
247
304
  }
248
305
  }
249
306
  }
250
- } catch (err) {
251
- this.log?.error(`[MessageHandler] 处理消息异常: ${err.message}`);
252
- const targetId = msg.conversationType === 3 ? msg.targetId : msg.senderUserId;
253
- await this.sendFn(targetId, `处理失败: ${err.message}`, msg.conversationType);
307
+ } else {
308
+ // 降级到非流式处理
309
+ const reply = await this.handleNormalMessage(msg);
310
+ if (reply) {
311
+ const targetId = this.getReplyTarget(msg);
312
+ await this.sendFn(targetId, reply, msg.conversationType);
313
+ // 非流式处理成功,群聊轮数 +1
314
+ if (msg.conversationType === 3) {
315
+ this._incrementGroupRoundCount(msg.targetId, maxRounds);
316
+ }
317
+ }
254
318
  }
255
319
  }
256
320
 
@@ -254,9 +254,11 @@ class OpenClawClient {
254
254
  // 会话历史管理:为每个用户维护对话上下文
255
255
  static conversationHistory = new Map();
256
256
  // 最大历史轮数(用户+AI 各算一轮)
257
- static maxHistoryRounds = 10;
257
+ static maxHistoryRounds = 50;
258
258
  // 单条消息最大长度(超过则截断)
259
259
  static maxMessageLength = 2000;
260
+ // 活跃请求管理:为每个用户跟踪当前正在进行的请求
261
+ static activeRequests = new Map();
260
262
 
261
263
  static async acquireSlot() {
262
264
  if (OpenClawClient.runningCount < OpenClawClient.maxConcurrency) {
@@ -414,6 +416,21 @@ class OpenClawClient {
414
416
  * @param {Function} onError - 出错时的回调 (error: Error) => void
415
417
  * @returns {Promise<void>}
416
418
  */
419
+ /**
420
+ * 取消用户的活跃请求
421
+ * @param {string} fromUser - 用户ID
422
+ */
423
+ cancelActiveRequest(fromUser) {
424
+ const activeRequest = OpenClawClient.activeRequests.get(fromUser);
425
+ if (activeRequest) {
426
+ this.log?.info(`[OpenClawClient] 取消用户 ${fromUser} 的活跃请求`);
427
+ activeRequest.abort();
428
+ OpenClawClient.activeRequests.delete(fromUser);
429
+ return true;
430
+ }
431
+ return false;
432
+ }
433
+
417
434
  async chatStream(message, fromUser, onDelta, onDone, onError) {
418
435
  if (!message || !message.trim()) {
419
436
  onError?.(new Error('消息内容为空'));
@@ -428,6 +445,12 @@ class OpenClawClient {
428
445
  return;
429
446
  }
430
447
 
448
+ // 取消该用户之前的活跃请求(如果存在)
449
+ const hasCancelled = this.cancelActiveRequest(fromUser);
450
+ if (hasCancelled) {
451
+ this.log?.info(`[OpenClawClient] 用户 ${fromUser} 的新消息到达,已取消前一条消息的回复`);
452
+ }
453
+
431
454
  this.log?.info(`[OpenClawClient] 准备 SSE 流式调用 OpenClaw,from=${fromUser}`);
432
455
 
433
456
  const gatewayToken = getGatewayToken();
@@ -445,7 +468,7 @@ class OpenClawClient {
445
468
  for (let i = 0; i < endpoints.length; i++) {
446
469
  const apiUrl = endpoints[i];
447
470
  try {
448
- const fullText = await this._doChatStream(apiUrl, gatewayToken, sessionId, messagesWithHistory, onDelta, onDone);
471
+ const fullText = await this._doChatStream(apiUrl, gatewayToken, sessionId, fromUser, messagesWithHistory, onDelta, onDone);
449
472
 
450
473
  // 成功响应后,保存当前对话到历史记录
451
474
  this._addToHistory(fromUser, 'user', message);
@@ -453,6 +476,12 @@ class OpenClawClient {
453
476
 
454
477
  return; // 成功则直接返回
455
478
  } catch (err) {
479
+ // 检查是否是取消错误
480
+ if (err.name === 'AbortError' || err.message?.includes('aborted')) {
481
+ this.log?.info(`[OpenClawClient] 请求被用户取消: ${fromUser}`);
482
+ return;
483
+ }
484
+
456
485
  const is404 = err.response?.status === 404;
457
486
  const isLast = i === endpoints.length - 1;
458
487
 
@@ -531,7 +560,7 @@ class OpenClawClient {
531
560
  }
532
561
  }
533
562
 
534
- async _doChatStream(apiUrl, gatewayToken, sessionId, messages, onDelta, onDone) {
563
+ async _doChatStream(apiUrl, gatewayToken, sessionId, fromUser, messages, onDelta, onDone) {
535
564
  const headers = {
536
565
  'Content-Type': 'application/json',
537
566
  'Accept': 'text/event-stream'
@@ -605,10 +634,17 @@ class OpenClawClient {
605
634
  let hasError = false;
606
635
  let errorMsg = '';
607
636
 
637
+ // 创建 AbortController 用于取消请求
638
+ const abortController = new AbortController();
639
+
640
+ // 注册活跃请求
641
+ OpenClawClient.activeRequests.set(fromUser, abortController);
642
+
608
643
  const response = await axios.post(apiUrl, payload, {
609
644
  headers,
610
645
  responseType: 'stream',
611
- timeout: 600000 // 10 分钟
646
+ timeout: 600000, // 10 分钟
647
+ signal: abortController.signal
612
648
  });
613
649
 
614
650
  return new Promise((resolve, reject) => {
@@ -677,6 +713,9 @@ class OpenClawClient {
677
713
  });
678
714
 
679
715
  response.data.on('end', async () => {
716
+ // 清理活跃请求
717
+ OpenClawClient.activeRequests.delete(fromUser);
718
+
680
719
  this.log?.info(`[OpenClawClient] SSE 流结束,总长度: ${fullText.length}`);
681
720
 
682
721
  if (hasError) {
@@ -706,6 +745,9 @@ class OpenClawClient {
706
745
  });
707
746
 
708
747
  response.data.on('error', (err) => {
748
+ // 清理活跃请求
749
+ OpenClawClient.activeRequests.delete(fromUser);
750
+
709
751
  this.log?.error(`[OpenClawClient] SSE 流错误: ${err.message}`);
710
752
  reject(err);
711
753
  });