@53ai/53ai-openclaw 1.0.9 → 1.1.1

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/dist/index.cjs.js CHANGED
@@ -58,8 +58,32 @@ const WS_RECONNECT_BASE_DELAY_MS = 1000;
58
58
  // ============================================================================
59
59
  /** 文本分块限制 */
60
60
  const TEXT_CHUNK_LIMIT = 4000;
61
- /** 消息处理超时(毫秒) */
62
- const MESSAGE_PROCESS_TIMEOUT_MS = 120000;
61
+ /**
62
+ * 请求分析软阈值(毫秒)
63
+ * 超过该时间后只告警并继续等待,不直接中断长分析任务。
64
+ */
65
+ const REQUEST_ANALYSIS_TIMEOUT_MS = 600000;
66
+ /**
67
+ * 读取请求分析软阈值的运行时覆盖值。
68
+ * 仅用于本地或特定部署快速调参;未设置时回退到默认值。
69
+ */
70
+ function getRequestAnalysisTimeoutMs() {
71
+ const raw = process.env.OPENCLAW_REQUEST_ANALYSIS_TIMEOUT_MS?.trim();
72
+ if (!raw) {
73
+ return REQUEST_ANALYSIS_TIMEOUT_MS;
74
+ }
75
+ const parsed = Number(raw);
76
+ if (!Number.isFinite(parsed) || parsed <= 0) {
77
+ return REQUEST_ANALYSIS_TIMEOUT_MS;
78
+ }
79
+ return parsed;
80
+ }
81
+ /**
82
+ * 消息缓存 TTL(毫秒)
83
+ * 设为 35 分钟,确保覆盖最大重连时间(约 28.5 分钟)+ 缓冲
84
+ * @see WS_MAX_RECONNECT_ATTEMPTS - 60 次,指数退避最大 30s
85
+ */
86
+ const MESSAGE_CACHE_TTL_MS = 35 * 60 * 1000;
63
87
  // ============================================================================
64
88
  // 媒体处理配置
65
89
  // ============================================================================
@@ -338,8 +362,9 @@ async function sendReply(params) {
338
362
  const reqId = replyToMsgId || streamId;
339
363
  runtime.log?.(`[53aihub] sendReply START: reqId=${reqId}, finish=${finish}, isError=${isError}, textLen=${text?.length || 0}, wsReadyState=${wsClient.readyState}`);
340
364
  if (wsClient.readyState !== 1) {
341
- runtime.error?.(`[53aihub] WebSocket is not open (readyState=${wsClient.readyState}). Cannot send message to ${toChatId}`);
342
- return;
365
+ const errorMsg = `WebSocket not connected (readyState=${wsClient.readyState}), cannot send message to ${toChatId}`;
366
+ runtime.error?.(`[53aihub] ${errorMsg}`);
367
+ throw new Error(`[53aihub] ${errorMsg}`);
343
368
  }
344
369
  if (isError) {
345
370
  runtime.error?.(`[53aihub] sendReply ERROR: reqId=${reqId}, code=${errorCode}, text=${text?.substring(0, 100)}`);
@@ -450,8 +475,9 @@ async function sendThinkingMessage(wsClient, text, msgId, streamId, runtime) {
450
475
  const wsState = wsClient.readyState;
451
476
  runtime?.log?.(`[53aihub] sendThinkingMessage CALLED: msgId=${msgId}, streamId=${streamId}, text=${text}, wsReadyState=${wsState}`);
452
477
  if (wsState !== 1) {
453
- runtime?.error?.(`[53aihub] sendThinkingMessage SKIPPED: WebSocket not ready (state=${wsState})`);
454
- return;
478
+ const errorMsg = `WebSocket not ready (state=${wsState})`;
479
+ runtime?.error?.(`[53aihub] sendThinkingMessage FAILED: ${errorMsg}`);
480
+ throw new Error(`[53aihub] ${errorMsg}`);
455
481
  }
456
482
  // 使用 OpenAI 兼容格式,确保 Go 后端能正确解析
457
483
  // 关键:req_id 必须使用原始消息的 msgId,这样 Go 后端才能关联请求和响应
@@ -686,7 +712,7 @@ async function warmupReqIdStore(accountId = "default", log) {
686
712
  const store = getOrCreateReqIdStore(accountId);
687
713
  return store.warmup((err) => log?.(`ReqId warmup error: ${String(err)}`));
688
714
  }
689
- async function cleanupAccount(accountId) {
715
+ async function cleanupAccount(accountId, clearCache = false) {
690
716
  const wsClient = wsClientInstances.get(accountId);
691
717
  if (wsClient) {
692
718
  try {
@@ -700,6 +726,10 @@ async function cleanupAccount(accountId) {
700
726
  const store = reqIdStores.get(accountId);
701
727
  if (store)
702
728
  await store.flush();
729
+ if (clearCache) {
730
+ const { clearAccountCache } = await Promise.resolve().then(function () { return messageCache; });
731
+ clearAccountCache(accountId);
732
+ }
703
733
  }
704
734
 
705
735
  /**
@@ -807,41 +837,288 @@ async function downloadAndSaveFiles(params) {
807
837
  return mediaList;
808
838
  }
809
839
 
810
- // 消息队列处理器 - 确保消息按顺序处理,避免竞态条件
840
+ const DEFAULT_CONFIG = {
841
+ maxEntries: 100,
842
+ ttlMs: MESSAGE_CACHE_TTL_MS,
843
+ maxRetries: 3,
844
+ };
845
+ /**
846
+ * 按 accountId 隔离的消息缓存管理器
847
+ */
848
+ class MessageCache {
849
+ cache = new Map();
850
+ config;
851
+ accountId;
852
+ runtime;
853
+ constructor(accountId, config = {}, runtime) {
854
+ this.accountId = accountId;
855
+ this.config = { ...DEFAULT_CONFIG, ...config };
856
+ this.runtime = runtime;
857
+ }
858
+ /**
859
+ * 生成唯一缓存 key
860
+ */
861
+ generateCacheKey(reqId) {
862
+ return `${this.accountId}:${reqId}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
863
+ }
864
+ /**
865
+ * 添加消息到缓存,返回缓存 key
866
+ */
867
+ add(params) {
868
+ this.prune();
869
+ if (this.cache.size >= this.config.maxEntries) {
870
+ // 优先淘汰非终态消息(finish=false 且 isError=false),保留终态消息
871
+ const entries = [...this.cache.entries()];
872
+ // 先找最老的非终态消息
873
+ const oldestNonFinal = entries
874
+ .filter(([, entry]) => !entry.finish && !entry.isError)
875
+ .sort((a, b) => a[1].createdAt - b[1].createdAt)[0];
876
+ if (oldestNonFinal) {
877
+ this.cache.delete(oldestNonFinal[0]);
878
+ this.runtime?.log?.(`[53aihub] MessageCache: evicted oldest non-final entry ${oldestNonFinal[0]}`);
879
+ }
880
+ else {
881
+ // 所有消息都是终态,才淘汰最老的
882
+ const oldest = entries.sort((a, b) => a[1].createdAt - b[1].createdAt)[0];
883
+ if (oldest) {
884
+ this.cache.delete(oldest[0]);
885
+ this.runtime?.log?.(`[53aihub] MessageCache: evicted oldest final entry ${oldest[0]} (all entries are final)`);
886
+ }
887
+ }
888
+ }
889
+ const cacheKey = this.generateCacheKey(params.reqId);
890
+ this.cache.set(cacheKey, {
891
+ cacheKey,
892
+ ...params,
893
+ createdAt: Date.now(),
894
+ retryCount: 0,
895
+ });
896
+ this.runtime?.log?.(`[53aihub] MessageCache: cached message reqId=${params.reqId}, cacheKey=${cacheKey}, cacheSize=${this.cache.size}`);
897
+ return cacheKey;
898
+ }
899
+ /**
900
+ * 获取所有待重试的消息,返回 cacheKey + entry
901
+ */
902
+ getPendingMessages() {
903
+ const now = Date.now();
904
+ const pending = [];
905
+ for (const [key, entry] of this.cache) {
906
+ if (now - entry.createdAt > this.config.ttlMs) {
907
+ this.cache.delete(key);
908
+ continue;
909
+ }
910
+ if (entry.retryCount >= this.config.maxRetries) {
911
+ this.cache.delete(key);
912
+ this.runtime?.log?.(`[53aihub] MessageCache: dropped message after max retries cacheKey=${key}`);
913
+ continue;
914
+ }
915
+ pending.push(entry);
916
+ }
917
+ return pending;
918
+ }
919
+ /**
920
+ * 按 cacheKey 标记消息为已发送
921
+ */
922
+ markSent(cacheKey) {
923
+ if (this.cache.has(cacheKey)) {
924
+ this.cache.delete(cacheKey);
925
+ this.runtime?.log?.(`[53aihub] MessageCache: marked as sent cacheKey=${cacheKey}`);
926
+ }
927
+ }
928
+ /**
929
+ * 按 cacheKey 增加重试计数
930
+ */
931
+ incrementRetry(cacheKey) {
932
+ const entry = this.cache.get(cacheKey);
933
+ if (entry) {
934
+ entry.retryCount++;
935
+ }
936
+ }
937
+ /**
938
+ * 清理过期条目
939
+ */
940
+ prune() {
941
+ const now = Date.now();
942
+ let pruned = 0;
943
+ for (const [key, entry] of this.cache) {
944
+ if (now - entry.createdAt > this.config.ttlMs) {
945
+ this.cache.delete(key);
946
+ pruned++;
947
+ }
948
+ }
949
+ if (pruned > 0) {
950
+ this.runtime?.log?.(`[53aihub] MessageCache: pruned ${pruned} expired entries`);
951
+ }
952
+ }
953
+ size() {
954
+ return this.cache.size;
955
+ }
956
+ clear() {
957
+ this.cache.clear();
958
+ }
959
+ }
960
+ // 按 accountId 隔离的缓存实例
961
+ const cacheInstances = new Map();
962
+ /**
963
+ * 获取或创建按账号隔离的缓存实例
964
+ */
965
+ function getMessageCache(accountId, runtime) {
966
+ let cache = cacheInstances.get(accountId);
967
+ if (!cache) {
968
+ cache = new MessageCache(accountId, {}, runtime);
969
+ cacheInstances.set(accountId, cache);
970
+ }
971
+ return cache;
972
+ }
973
+ /**
974
+ * 清理指定账号的缓存
975
+ */
976
+ function clearAccountCache(accountId) {
977
+ const cache = cacheInstances.get(accountId);
978
+ if (cache) {
979
+ cache.clear();
980
+ cacheInstances.delete(accountId);
981
+ }
982
+ }
983
+ /**
984
+ * 缓存感知的消息发送器
985
+ * 返回 [success, cacheKey?] - 成功时 cacheKey 为 undefined,失败时返回缓存 key
986
+ */
987
+ async function sendWithCache(accountId, params, sendFn) {
988
+ const { wsClient, replyToMsgId, streamId, text, toChatId, finish, isError, errorCode, errorDetails, isThinking, runtime } = params;
989
+ const reqId = replyToMsgId || streamId;
990
+ if (wsClient.readyState !== 1) {
991
+ const cache = getMessageCache(accountId, runtime);
992
+ const cacheKey = cache.add({
993
+ reqId,
994
+ toChatId,
995
+ text,
996
+ finish,
997
+ isError,
998
+ errorCode,
999
+ errorDetails,
1000
+ isThinking,
1001
+ });
1002
+ runtime.error?.(`[53aihub] sendWithCache: WebSocket not ready, cached message cacheKey=${cacheKey}`);
1003
+ return { success: false, cacheKey };
1004
+ }
1005
+ try {
1006
+ await sendFn(params);
1007
+ return { success: true };
1008
+ }
1009
+ catch (err) {
1010
+ const cache = getMessageCache(accountId, runtime);
1011
+ const cacheKey = cache.add({
1012
+ reqId,
1013
+ toChatId,
1014
+ text,
1015
+ finish,
1016
+ isError,
1017
+ errorCode,
1018
+ errorDetails,
1019
+ isThinking,
1020
+ });
1021
+ runtime.error?.(`[53aihub] sendWithCache: send failed, cached message cacheKey=${cacheKey}, error=${String(err)}`);
1022
+ return { success: false, cacheKey };
1023
+ }
1024
+ }
1025
+ /**
1026
+ * 重放缓存的消息
1027
+ */
1028
+ async function replayCachedMessages(accountId, wsClient, runtime, sendFn) {
1029
+ const cache = getMessageCache(accountId, runtime);
1030
+ const pending = cache.getPendingMessages();
1031
+ if (pending.length === 0) {
1032
+ return 0;
1033
+ }
1034
+ runtime.log?.(`[53aihub] replayCachedMessages: replaying ${pending.length} cached messages for account=${accountId}`);
1035
+ let successCount = 0;
1036
+ for (const entry of pending) {
1037
+ if (wsClient.readyState !== 1) {
1038
+ runtime.error?.(`[53aihub] replayCachedMessages: WebSocket not ready, stopping replay`);
1039
+ break;
1040
+ }
1041
+ try {
1042
+ await sendFn({
1043
+ wsClient,
1044
+ text: entry.text,
1045
+ toChatId: entry.toChatId,
1046
+ replyToMsgId: entry.reqId,
1047
+ runtime,
1048
+ finish: entry.finish,
1049
+ streamId: entry.reqId,
1050
+ isError: entry.isError,
1051
+ errorCode: entry.errorCode,
1052
+ errorDetails: entry.errorDetails,
1053
+ isThinking: entry.isThinking,
1054
+ });
1055
+ cache.markSent(entry.cacheKey);
1056
+ successCount++;
1057
+ await new Promise((resolve) => setTimeout(resolve, 100));
1058
+ }
1059
+ catch (err) {
1060
+ const newRetryCount = entry.retryCount + 1;
1061
+ cache.incrementRetry(entry.cacheKey);
1062
+ runtime.error?.(`[53aihub] replayCachedMessages: failed to send cacheKey=${entry.cacheKey}, retryCount=${newRetryCount}`);
1063
+ }
1064
+ }
1065
+ runtime.log?.(`[53aihub] replayCachedMessages: completed, ${successCount}/${pending.length} sent`);
1066
+ return successCount;
1067
+ }
1068
+
1069
+ var messageCache = /*#__PURE__*/Object.freeze({
1070
+ __proto__: null,
1071
+ clearAccountCache: clearAccountCache,
1072
+ getMessageCache: getMessageCache,
1073
+ replayCachedMessages: replayCachedMessages,
1074
+ sendWithCache: sendWithCache
1075
+ });
1076
+
1077
+ // 按 chatId 分队列的消息处理器 - 同一会话串行,不同会话并行
811
1078
  class MessageQueue {
812
- queue = [];
813
- processing = false;
1079
+ queues = new Map();
1080
+ processing = new Map();
814
1081
  runtime;
815
1082
  constructor(runtime) {
816
1083
  this.runtime = runtime;
817
1084
  }
818
- enqueue(task) {
819
- this.queue.push(task);
820
- this.process();
1085
+ enqueue(chatId, task) {
1086
+ if (!this.queues.has(chatId)) {
1087
+ this.queues.set(chatId, []);
1088
+ this.processing.set(chatId, false);
1089
+ }
1090
+ this.queues.get(chatId).push(task);
1091
+ this.process(chatId);
821
1092
  }
822
- async process() {
823
- if (this.processing || this.queue.length === 0)
1093
+ async process(chatId) {
1094
+ const queue = this.queues.get(chatId);
1095
+ const isProcessing = this.processing.get(chatId);
1096
+ if (!queue || isProcessing || queue.length === 0)
824
1097
  return;
825
- this.processing = true;
1098
+ this.processing.set(chatId, true);
826
1099
  try {
827
- const task = this.queue.shift();
1100
+ const task = queue.shift();
828
1101
  if (task) {
829
1102
  await task();
830
1103
  }
831
1104
  }
832
1105
  catch (err) {
833
- this.runtime.error?.(`[53aihub] MessageQueue error: ${String(err)}`);
1106
+ this.runtime.error?.(`[53aihub] MessageQueue error (chatId=${chatId}): ${String(err)}`);
834
1107
  }
835
1108
  finally {
836
- this.processing = false;
837
- // 继续处理队列中的下一条消息
838
- if (this.queue.length > 0) {
839
- this.process();
1109
+ this.processing.set(chatId, false);
1110
+ if (queue.length > 0) {
1111
+ this.process(chatId);
1112
+ }
1113
+ else {
1114
+ this.queues.delete(chatId);
1115
+ this.processing.delete(chatId);
840
1116
  }
841
1117
  }
842
1118
  }
843
1119
  clear() {
844
- this.queue = [];
1120
+ this.queues.clear();
1121
+ this.processing.clear();
845
1122
  }
846
1123
  }
847
1124
  function buildMessageContext(body, account, config, mediaList) {
@@ -906,20 +1183,26 @@ async function processMessage(params) {
906
1183
  runtime.log?.(`[53aihub] processMessage: chatId=${chatId}, msgId=${body.msgId}, text=${parsed.textParts.join(" ").substring(0, 50)}... images=${parsed.imageUrls.length} files=${parsed.fileUrls.length}`);
907
1184
  const core = getRuntime();
908
1185
  const streamId = `stream-${Date.now()}`;
1186
+ // 缓存感知的消息发送器 - 必须在所有 sendReply 调用前定义
1187
+ const cachedSend = async (sendParams) => {
1188
+ await sendWithCache(account.accountId, {
1189
+ ...sendParams,
1190
+ wsClient,
1191
+ runtime,
1192
+ }, sendReply);
1193
+ };
909
1194
  const accessResult = await checkAccessPolicy({
910
1195
  userId: body.userId,
911
1196
  account,
912
1197
  runtime,
913
1198
  });
914
1199
  if (!accessResult.allowed) {
915
- await sendReply({
916
- wsClient,
1200
+ await cachedSend({
917
1201
  text: accessResult.reason === "Pairing required"
918
1202
  ? "您尚未获得授权使用此机器人,请联系管理员进行审核。"
919
1203
  : `⚠️ 访问被拒绝: ${accessResult.reason || "未知原因"}`,
920
1204
  toChatId: chatId,
921
1205
  replyToMsgId: body.msgId,
922
- runtime,
923
1206
  finish: true,
924
1207
  streamId,
925
1208
  isError: true,
@@ -960,6 +1243,8 @@ async function processMessage(params) {
960
1243
  const mediaList = [...imageMediaList, ...fileMediaList];
961
1244
  const ctxPayload = buildMessageContext(body, account, config, mediaList);
962
1245
  let cleanedUp = false;
1246
+ let analysisTimeoutTimer = null;
1247
+ const analysisTimeoutMs = getRequestAnalysisTimeoutMs();
963
1248
  const safeCleanup = () => {
964
1249
  if (!cleanedUp) {
965
1250
  cleanedUp = true;
@@ -968,7 +1253,20 @@ async function processMessage(params) {
968
1253
  };
969
1254
  runtime.log?.(`[53aihub] processMessage: Starting dispatchReplyWithBufferedBlockDispatcher for msgId=${body.msgId}`);
970
1255
  try {
971
- await withTimeout(core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
1256
+ analysisTimeoutTimer = setTimeout(() => {
1257
+ runtime.log?.(`[53aihub] processMessage: analysis exceeded soft threshold ${analysisTimeoutMs}ms for msgId=${body.msgId}, keep waiting for completion`);
1258
+ if (wsClient.readyState !== WebSocket.OPEN) {
1259
+ return;
1260
+ }
1261
+ try {
1262
+ wsClient.ping();
1263
+ runtime.log?.(`[53aihub] processMessage: timeout keepalive ping sent for msgId=${body.msgId}`);
1264
+ }
1265
+ catch (err) {
1266
+ runtime.error?.(`[53aihub] processMessage: timeout keepalive ping failed for msgId=${body.msgId}: ${String(err)}`);
1267
+ }
1268
+ }, analysisTimeoutMs);
1269
+ await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
972
1270
  ctx: ctxPayload,
973
1271
  cfg: config,
974
1272
  dispatcherOptions: {
@@ -978,12 +1276,10 @@ async function processMessage(params) {
978
1276
  const errorMsg = payload.text || "Unknown error";
979
1277
  const errorCode = inferErrorCode(errorMsg);
980
1278
  runtime.error?.(`[53aihub] deliver ERROR: ${errorMsg}`);
981
- await sendReply({
982
- wsClient,
1279
+ await cachedSend({
983
1280
  text: `⚠️ ${errorMsg}`,
984
1281
  toChatId: chatId,
985
1282
  replyToMsgId: body.msgId,
986
- runtime,
987
1283
  finish: true,
988
1284
  streamId: state.streamId,
989
1285
  isError: true,
@@ -996,12 +1292,10 @@ async function processMessage(params) {
996
1292
  state.accumulatedText.startsWith("🧹 Compacting context");
997
1293
  if (isCompaction && info.kind !== "final") {
998
1294
  runtime.log?.(`[53aihub] deliver COMPACTION: text preview=${payload.text?.substring(0, 50)}...`);
999
- await sendReply({
1000
- wsClient,
1295
+ await cachedSend({
1001
1296
  text: payload.text || "",
1002
1297
  toChatId: chatId,
1003
1298
  replyToMsgId: body.msgId,
1004
- runtime,
1005
1299
  finish: false,
1006
1300
  streamId: state.streamId,
1007
1301
  isThinking: true,
@@ -1011,12 +1305,10 @@ async function processMessage(params) {
1011
1305
  state.accumulatedText += payload.text;
1012
1306
  if (info.kind !== "final") {
1013
1307
  runtime.log?.(`[53aihub] deliver STREAMING: accumulatedText preview=${state.accumulatedText.substring(0, 50)}...`);
1014
- await sendReply({
1015
- wsClient,
1308
+ await cachedSend({
1016
1309
  text: state.accumulatedText,
1017
1310
  toChatId: chatId,
1018
1311
  replyToMsgId: body.msgId,
1019
- runtime,
1020
1312
  finish: false,
1021
1313
  streamId: state.streamId,
1022
1314
  });
@@ -1026,47 +1318,36 @@ async function processMessage(params) {
1026
1318
  runtime.error?.(`[53aihub] onError: kind=${info.kind}, error=${String(err)}`);
1027
1319
  const errorText = String(err);
1028
1320
  const errorCode = inferErrorCode(errorText);
1029
- try {
1030
- await sendReply({
1031
- wsClient,
1032
- text: `⚠️ 系统错误: ${errorText}`,
1033
- toChatId: chatId,
1034
- replyToMsgId: body.msgId,
1035
- runtime,
1036
- finish: true,
1037
- streamId: state.streamId,
1038
- isError: true,
1039
- errorCode,
1040
- errorDetails: `kind=${info.kind}, error=${errorText}`,
1041
- });
1042
- }
1043
- catch (sendErr) {
1044
- runtime.error?.(`[53aihub] Failed to send error notification: ${String(sendErr)}`);
1045
- }
1321
+ await cachedSend({
1322
+ text: `⚠️ 系统错误: ${errorText}`,
1323
+ toChatId: chatId,
1324
+ replyToMsgId: body.msgId,
1325
+ finish: true,
1326
+ streamId: state.streamId,
1327
+ isError: true,
1328
+ errorCode,
1329
+ errorDetails: `kind=${info.kind}, error=${errorText}`,
1330
+ });
1046
1331
  },
1047
1332
  },
1048
- }), MESSAGE_PROCESS_TIMEOUT_MS, `Message processing timed out (msgId=${body.msgId})`);
1333
+ });
1049
1334
  runtime.log?.(`[53aihub] processMessage: dispatchReply completed, accumulatedTextLen=${state.accumulatedText.length}`);
1050
1335
  if (state.accumulatedText) {
1051
1336
  runtime.log?.(`[53aihub] processMessage: Sending final reply with accumulatedText`);
1052
- await sendReply({
1053
- wsClient,
1337
+ await cachedSend({
1054
1338
  text: state.accumulatedText,
1055
1339
  toChatId: chatId,
1056
1340
  replyToMsgId: body.msgId,
1057
- runtime,
1058
1341
  finish: true,
1059
1342
  streamId: state.streamId,
1060
1343
  });
1061
1344
  }
1062
1345
  else {
1063
1346
  runtime.log?.(`[53aihub] processMessage: No accumulatedText, sending empty final reply`);
1064
- await sendReply({
1065
- wsClient,
1347
+ await cachedSend({
1066
1348
  text: "",
1067
1349
  toChatId: chatId,
1068
1350
  replyToMsgId: body.msgId,
1069
- runtime,
1070
1351
  finish: true,
1071
1352
  streamId: state.streamId,
1072
1353
  });
@@ -1079,12 +1360,10 @@ async function processMessage(params) {
1079
1360
  const errorCode = inferErrorCode(errorText);
1080
1361
  if (!cleanedUp) {
1081
1362
  try {
1082
- await sendReply({
1083
- wsClient,
1363
+ await cachedSend({
1084
1364
  text: `⚠️ 处理请求时发生异常: ${errorText}`,
1085
1365
  toChatId: chatId,
1086
1366
  replyToMsgId: body.msgId,
1087
- runtime,
1088
1367
  finish: true,
1089
1368
  streamId: state.streamId,
1090
1369
  isError: true,
@@ -1098,6 +1377,12 @@ async function processMessage(params) {
1098
1377
  }
1099
1378
  safeCleanup();
1100
1379
  }
1380
+ finally {
1381
+ if (analysisTimeoutTimer) {
1382
+ clearTimeout(analysisTimeoutTimer);
1383
+ analysisTimeoutTimer = null;
1384
+ }
1385
+ }
1101
1386
  }
1102
1387
  async function monitorProvider(options) {
1103
1388
  const { account, config, runtime, abortSignal } = options;
@@ -1109,17 +1394,6 @@ async function monitorProvider(options) {
1109
1394
  let pingInterval = null;
1110
1395
  let isAborted = false;
1111
1396
  let messageQueue = null;
1112
- const cleanup = async () => {
1113
- if (pingInterval) {
1114
- clearInterval(pingInterval);
1115
- pingInterval = null;
1116
- }
1117
- if (messageQueue) {
1118
- messageQueue.clear();
1119
- messageQueue = null;
1120
- }
1121
- await cleanupAccount(account.accountId);
1122
- };
1123
1397
  const connect = () => {
1124
1398
  if (isAborted)
1125
1399
  return;
@@ -1144,9 +1418,18 @@ async function monitorProvider(options) {
1144
1418
  setWebSocket(account.accountId, wsClient);
1145
1419
  // 初始化消息队列
1146
1420
  messageQueue = new MessageQueue(runtime);
1147
- wsClient.on("open", () => {
1421
+ wsClient.on("open", async () => {
1148
1422
  runtime.log?.(`[${account.accountId}] WebSocket connected successfully`);
1149
1423
  reconnectAttempts = 0;
1424
+ try {
1425
+ const replayedCount = await replayCachedMessages(account.accountId, wsClient, runtime, sendReply);
1426
+ if (replayedCount > 0) {
1427
+ runtime.log?.(`[${account.accountId}] Replayed ${replayedCount} cached messages`);
1428
+ }
1429
+ }
1430
+ catch (err) {
1431
+ runtime.error?.(`[${account.accountId}] Failed to replay cached messages: ${String(err)}`);
1432
+ }
1150
1433
  pingInterval = setInterval(() => {
1151
1434
  if (wsClient?.readyState === WebSocket.OPEN) {
1152
1435
  wsClient.ping();
@@ -1156,8 +1439,20 @@ async function monitorProvider(options) {
1156
1439
  wsClient.on("message", (data) => {
1157
1440
  const rawPayload = data.toString();
1158
1441
  runtime.log?.(`[${account.accountId}] Received WS message: ${rawPayload.substring(0, 200)}...`);
1159
- // 使用消息队列确保顺序处理,避免竞态条件
1160
- messageQueue?.enqueue(async () => {
1442
+ let chatId = "unknown";
1443
+ try {
1444
+ const msg = JSON.parse(rawPayload);
1445
+ if (msg.action === "chat") {
1446
+ chatId = msg.data?.conversation_id || msg.data?.user || "unknown";
1447
+ }
1448
+ else {
1449
+ chatId = msg.data?.chatId || msg.data?.userId || "unknown";
1450
+ }
1451
+ }
1452
+ catch {
1453
+ chatId = "unknown";
1454
+ }
1455
+ messageQueue?.enqueue(chatId, async () => {
1161
1456
  await processMessage({
1162
1457
  rawPayload,
1163
1458
  account,
@@ -1188,8 +1483,8 @@ async function monitorProvider(options) {
1188
1483
  setTimeout(connect, backoff);
1189
1484
  }
1190
1485
  else if (!isAborted) {
1191
- runtime.error?.(`[${account.accountId}] Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached`);
1192
- await cleanup();
1486
+ runtime.error?.(`[${account.accountId}] Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached, preserving cache for external recovery`);
1487
+ await cleanupAccount(account.accountId, false);
1193
1488
  reject(new Error(`Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached`));
1194
1489
  }
1195
1490
  });
@@ -1197,15 +1492,23 @@ async function monitorProvider(options) {
1197
1492
  if (abortSignal) {
1198
1493
  abortSignal.addEventListener("abort", async () => {
1199
1494
  isAborted = true;
1200
- await cleanup();
1495
+ if (pingInterval) {
1496
+ clearInterval(pingInterval);
1497
+ pingInterval = null;
1498
+ }
1499
+ if (messageQueue) {
1500
+ messageQueue.clear();
1501
+ messageQueue = null;
1502
+ }
1503
+ await cleanupAccount(account.accountId, true);
1201
1504
  resolve();
1202
1505
  });
1203
1506
  }
1204
1507
  warmupReqIdStore(account.accountId, (msg) => runtime.log?.(msg))
1205
1508
  .then(() => connect())
1206
1509
  .catch(async (err) => {
1207
- runtime.error?.(`[${account.accountId}] Failed to warmup ReqId store: ${String(err)}`);
1208
- await cleanup();
1510
+ runtime.error?.(`[${account.accountId}] Failed to warmup ReqId store: ${String(err)}, preserving cache for retry`);
1511
+ await cleanupAccount(account.accountId, false);
1209
1512
  reject(err);
1210
1513
  });
1211
1514
  });
@@ -1298,6 +1601,12 @@ async function promptWSUrl(prompter, account) {
1298
1601
  },
1299
1602
  })).trim();
1300
1603
  }
1604
+ async function promptSendThinkingMessage(prompter, account) {
1605
+ return await prompter.confirm({
1606
+ message: "Send a thinking message while the agent is processing?",
1607
+ initialValue: account?.sendThinkingMessage ?? true,
1608
+ });
1609
+ }
1301
1610
  function setAccessPolicy(cfg, accessPolicy) {
1302
1611
  const account = resolveAccount(cfg);
1303
1612
  const existingAllowFrom = account.config.allowFrom ?? [];
@@ -1357,6 +1666,7 @@ const aiHubOnboardingAdapter = {
1357
1666
  const botId = await promptBotId(prompter, account);
1358
1667
  const secret = await promptSecret(prompter, account);
1359
1668
  const WSUrl = await promptWSUrl(prompter, account);
1669
+ const sendThinkingMessage = await promptSendThinkingMessage(prompter, account);
1360
1670
  const cfgWithAccount = setAccount(cfg, {
1361
1671
  botId,
1362
1672
  secret,
@@ -1364,7 +1674,7 @@ const aiHubOnboardingAdapter = {
1364
1674
  enabled: true,
1365
1675
  accessPolicy: account.config.accessPolicy ?? "open",
1366
1676
  allowFrom: account.config.allowFrom ?? [],
1367
- sendThinkingMessage: account.sendThinkingMessage ?? true,
1677
+ sendThinkingMessage,
1368
1678
  });
1369
1679
  return { cfg: cfgWithAccount };
1370
1680
  },