@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.esm.js CHANGED
@@ -34,8 +34,32 @@ const WS_RECONNECT_BASE_DELAY_MS = 1000;
34
34
  // ============================================================================
35
35
  /** 文本分块限制 */
36
36
  const TEXT_CHUNK_LIMIT = 4000;
37
- /** 消息处理超时(毫秒) */
38
- const MESSAGE_PROCESS_TIMEOUT_MS = 120000;
37
+ /**
38
+ * 请求分析软阈值(毫秒)
39
+ * 超过该时间后只告警并继续等待,不直接中断长分析任务。
40
+ */
41
+ const REQUEST_ANALYSIS_TIMEOUT_MS = 600000;
42
+ /**
43
+ * 读取请求分析软阈值的运行时覆盖值。
44
+ * 仅用于本地或特定部署快速调参;未设置时回退到默认值。
45
+ */
46
+ function getRequestAnalysisTimeoutMs() {
47
+ const raw = process.env.OPENCLAW_REQUEST_ANALYSIS_TIMEOUT_MS?.trim();
48
+ if (!raw) {
49
+ return REQUEST_ANALYSIS_TIMEOUT_MS;
50
+ }
51
+ const parsed = Number(raw);
52
+ if (!Number.isFinite(parsed) || parsed <= 0) {
53
+ return REQUEST_ANALYSIS_TIMEOUT_MS;
54
+ }
55
+ return parsed;
56
+ }
57
+ /**
58
+ * 消息缓存 TTL(毫秒)
59
+ * 设为 35 分钟,确保覆盖最大重连时间(约 28.5 分钟)+ 缓冲
60
+ * @see WS_MAX_RECONNECT_ATTEMPTS - 60 次,指数退避最大 30s
61
+ */
62
+ const MESSAGE_CACHE_TTL_MS = 35 * 60 * 1000;
39
63
  // ============================================================================
40
64
  // 媒体处理配置
41
65
  // ============================================================================
@@ -314,8 +338,9 @@ async function sendReply(params) {
314
338
  const reqId = replyToMsgId || streamId;
315
339
  runtime.log?.(`[53aihub] sendReply START: reqId=${reqId}, finish=${finish}, isError=${isError}, textLen=${text?.length || 0}, wsReadyState=${wsClient.readyState}`);
316
340
  if (wsClient.readyState !== 1) {
317
- runtime.error?.(`[53aihub] WebSocket is not open (readyState=${wsClient.readyState}). Cannot send message to ${toChatId}`);
318
- return;
341
+ const errorMsg = `WebSocket not connected (readyState=${wsClient.readyState}), cannot send message to ${toChatId}`;
342
+ runtime.error?.(`[53aihub] ${errorMsg}`);
343
+ throw new Error(`[53aihub] ${errorMsg}`);
319
344
  }
320
345
  if (isError) {
321
346
  runtime.error?.(`[53aihub] sendReply ERROR: reqId=${reqId}, code=${errorCode}, text=${text?.substring(0, 100)}`);
@@ -426,8 +451,9 @@ async function sendThinkingMessage(wsClient, text, msgId, streamId, runtime) {
426
451
  const wsState = wsClient.readyState;
427
452
  runtime?.log?.(`[53aihub] sendThinkingMessage CALLED: msgId=${msgId}, streamId=${streamId}, text=${text}, wsReadyState=${wsState}`);
428
453
  if (wsState !== 1) {
429
- runtime?.error?.(`[53aihub] sendThinkingMessage SKIPPED: WebSocket not ready (state=${wsState})`);
430
- return;
454
+ const errorMsg = `WebSocket not ready (state=${wsState})`;
455
+ runtime?.error?.(`[53aihub] sendThinkingMessage FAILED: ${errorMsg}`);
456
+ throw new Error(`[53aihub] ${errorMsg}`);
431
457
  }
432
458
  // 使用 OpenAI 兼容格式,确保 Go 后端能正确解析
433
459
  // 关键:req_id 必须使用原始消息的 msgId,这样 Go 后端才能关联请求和响应
@@ -662,7 +688,7 @@ async function warmupReqIdStore(accountId = "default", log) {
662
688
  const store = getOrCreateReqIdStore(accountId);
663
689
  return store.warmup((err) => log?.(`ReqId warmup error: ${String(err)}`));
664
690
  }
665
- async function cleanupAccount(accountId) {
691
+ async function cleanupAccount(accountId, clearCache = false) {
666
692
  const wsClient = wsClientInstances.get(accountId);
667
693
  if (wsClient) {
668
694
  try {
@@ -676,6 +702,10 @@ async function cleanupAccount(accountId) {
676
702
  const store = reqIdStores.get(accountId);
677
703
  if (store)
678
704
  await store.flush();
705
+ if (clearCache) {
706
+ const { clearAccountCache } = await Promise.resolve().then(function () { return messageCache; });
707
+ clearAccountCache(accountId);
708
+ }
679
709
  }
680
710
 
681
711
  /**
@@ -783,41 +813,288 @@ async function downloadAndSaveFiles(params) {
783
813
  return mediaList;
784
814
  }
785
815
 
786
- // 消息队列处理器 - 确保消息按顺序处理,避免竞态条件
816
+ const DEFAULT_CONFIG = {
817
+ maxEntries: 100,
818
+ ttlMs: MESSAGE_CACHE_TTL_MS,
819
+ maxRetries: 3,
820
+ };
821
+ /**
822
+ * 按 accountId 隔离的消息缓存管理器
823
+ */
824
+ class MessageCache {
825
+ cache = new Map();
826
+ config;
827
+ accountId;
828
+ runtime;
829
+ constructor(accountId, config = {}, runtime) {
830
+ this.accountId = accountId;
831
+ this.config = { ...DEFAULT_CONFIG, ...config };
832
+ this.runtime = runtime;
833
+ }
834
+ /**
835
+ * 生成唯一缓存 key
836
+ */
837
+ generateCacheKey(reqId) {
838
+ return `${this.accountId}:${reqId}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
839
+ }
840
+ /**
841
+ * 添加消息到缓存,返回缓存 key
842
+ */
843
+ add(params) {
844
+ this.prune();
845
+ if (this.cache.size >= this.config.maxEntries) {
846
+ // 优先淘汰非终态消息(finish=false 且 isError=false),保留终态消息
847
+ const entries = [...this.cache.entries()];
848
+ // 先找最老的非终态消息
849
+ const oldestNonFinal = entries
850
+ .filter(([, entry]) => !entry.finish && !entry.isError)
851
+ .sort((a, b) => a[1].createdAt - b[1].createdAt)[0];
852
+ if (oldestNonFinal) {
853
+ this.cache.delete(oldestNonFinal[0]);
854
+ this.runtime?.log?.(`[53aihub] MessageCache: evicted oldest non-final entry ${oldestNonFinal[0]}`);
855
+ }
856
+ else {
857
+ // 所有消息都是终态,才淘汰最老的
858
+ const oldest = entries.sort((a, b) => a[1].createdAt - b[1].createdAt)[0];
859
+ if (oldest) {
860
+ this.cache.delete(oldest[0]);
861
+ this.runtime?.log?.(`[53aihub] MessageCache: evicted oldest final entry ${oldest[0]} (all entries are final)`);
862
+ }
863
+ }
864
+ }
865
+ const cacheKey = this.generateCacheKey(params.reqId);
866
+ this.cache.set(cacheKey, {
867
+ cacheKey,
868
+ ...params,
869
+ createdAt: Date.now(),
870
+ retryCount: 0,
871
+ });
872
+ this.runtime?.log?.(`[53aihub] MessageCache: cached message reqId=${params.reqId}, cacheKey=${cacheKey}, cacheSize=${this.cache.size}`);
873
+ return cacheKey;
874
+ }
875
+ /**
876
+ * 获取所有待重试的消息,返回 cacheKey + entry
877
+ */
878
+ getPendingMessages() {
879
+ const now = Date.now();
880
+ const pending = [];
881
+ for (const [key, entry] of this.cache) {
882
+ if (now - entry.createdAt > this.config.ttlMs) {
883
+ this.cache.delete(key);
884
+ continue;
885
+ }
886
+ if (entry.retryCount >= this.config.maxRetries) {
887
+ this.cache.delete(key);
888
+ this.runtime?.log?.(`[53aihub] MessageCache: dropped message after max retries cacheKey=${key}`);
889
+ continue;
890
+ }
891
+ pending.push(entry);
892
+ }
893
+ return pending;
894
+ }
895
+ /**
896
+ * 按 cacheKey 标记消息为已发送
897
+ */
898
+ markSent(cacheKey) {
899
+ if (this.cache.has(cacheKey)) {
900
+ this.cache.delete(cacheKey);
901
+ this.runtime?.log?.(`[53aihub] MessageCache: marked as sent cacheKey=${cacheKey}`);
902
+ }
903
+ }
904
+ /**
905
+ * 按 cacheKey 增加重试计数
906
+ */
907
+ incrementRetry(cacheKey) {
908
+ const entry = this.cache.get(cacheKey);
909
+ if (entry) {
910
+ entry.retryCount++;
911
+ }
912
+ }
913
+ /**
914
+ * 清理过期条目
915
+ */
916
+ prune() {
917
+ const now = Date.now();
918
+ let pruned = 0;
919
+ for (const [key, entry] of this.cache) {
920
+ if (now - entry.createdAt > this.config.ttlMs) {
921
+ this.cache.delete(key);
922
+ pruned++;
923
+ }
924
+ }
925
+ if (pruned > 0) {
926
+ this.runtime?.log?.(`[53aihub] MessageCache: pruned ${pruned} expired entries`);
927
+ }
928
+ }
929
+ size() {
930
+ return this.cache.size;
931
+ }
932
+ clear() {
933
+ this.cache.clear();
934
+ }
935
+ }
936
+ // 按 accountId 隔离的缓存实例
937
+ const cacheInstances = new Map();
938
+ /**
939
+ * 获取或创建按账号隔离的缓存实例
940
+ */
941
+ function getMessageCache(accountId, runtime) {
942
+ let cache = cacheInstances.get(accountId);
943
+ if (!cache) {
944
+ cache = new MessageCache(accountId, {}, runtime);
945
+ cacheInstances.set(accountId, cache);
946
+ }
947
+ return cache;
948
+ }
949
+ /**
950
+ * 清理指定账号的缓存
951
+ */
952
+ function clearAccountCache(accountId) {
953
+ const cache = cacheInstances.get(accountId);
954
+ if (cache) {
955
+ cache.clear();
956
+ cacheInstances.delete(accountId);
957
+ }
958
+ }
959
+ /**
960
+ * 缓存感知的消息发送器
961
+ * 返回 [success, cacheKey?] - 成功时 cacheKey 为 undefined,失败时返回缓存 key
962
+ */
963
+ async function sendWithCache(accountId, params, sendFn) {
964
+ const { wsClient, replyToMsgId, streamId, text, toChatId, finish, isError, errorCode, errorDetails, isThinking, runtime } = params;
965
+ const reqId = replyToMsgId || streamId;
966
+ if (wsClient.readyState !== 1) {
967
+ const cache = getMessageCache(accountId, runtime);
968
+ const cacheKey = cache.add({
969
+ reqId,
970
+ toChatId,
971
+ text,
972
+ finish,
973
+ isError,
974
+ errorCode,
975
+ errorDetails,
976
+ isThinking,
977
+ });
978
+ runtime.error?.(`[53aihub] sendWithCache: WebSocket not ready, cached message cacheKey=${cacheKey}`);
979
+ return { success: false, cacheKey };
980
+ }
981
+ try {
982
+ await sendFn(params);
983
+ return { success: true };
984
+ }
985
+ catch (err) {
986
+ const cache = getMessageCache(accountId, runtime);
987
+ const cacheKey = cache.add({
988
+ reqId,
989
+ toChatId,
990
+ text,
991
+ finish,
992
+ isError,
993
+ errorCode,
994
+ errorDetails,
995
+ isThinking,
996
+ });
997
+ runtime.error?.(`[53aihub] sendWithCache: send failed, cached message cacheKey=${cacheKey}, error=${String(err)}`);
998
+ return { success: false, cacheKey };
999
+ }
1000
+ }
1001
+ /**
1002
+ * 重放缓存的消息
1003
+ */
1004
+ async function replayCachedMessages(accountId, wsClient, runtime, sendFn) {
1005
+ const cache = getMessageCache(accountId, runtime);
1006
+ const pending = cache.getPendingMessages();
1007
+ if (pending.length === 0) {
1008
+ return 0;
1009
+ }
1010
+ runtime.log?.(`[53aihub] replayCachedMessages: replaying ${pending.length} cached messages for account=${accountId}`);
1011
+ let successCount = 0;
1012
+ for (const entry of pending) {
1013
+ if (wsClient.readyState !== 1) {
1014
+ runtime.error?.(`[53aihub] replayCachedMessages: WebSocket not ready, stopping replay`);
1015
+ break;
1016
+ }
1017
+ try {
1018
+ await sendFn({
1019
+ wsClient,
1020
+ text: entry.text,
1021
+ toChatId: entry.toChatId,
1022
+ replyToMsgId: entry.reqId,
1023
+ runtime,
1024
+ finish: entry.finish,
1025
+ streamId: entry.reqId,
1026
+ isError: entry.isError,
1027
+ errorCode: entry.errorCode,
1028
+ errorDetails: entry.errorDetails,
1029
+ isThinking: entry.isThinking,
1030
+ });
1031
+ cache.markSent(entry.cacheKey);
1032
+ successCount++;
1033
+ await new Promise((resolve) => setTimeout(resolve, 100));
1034
+ }
1035
+ catch (err) {
1036
+ const newRetryCount = entry.retryCount + 1;
1037
+ cache.incrementRetry(entry.cacheKey);
1038
+ runtime.error?.(`[53aihub] replayCachedMessages: failed to send cacheKey=${entry.cacheKey}, retryCount=${newRetryCount}`);
1039
+ }
1040
+ }
1041
+ runtime.log?.(`[53aihub] replayCachedMessages: completed, ${successCount}/${pending.length} sent`);
1042
+ return successCount;
1043
+ }
1044
+
1045
+ var messageCache = /*#__PURE__*/Object.freeze({
1046
+ __proto__: null,
1047
+ clearAccountCache: clearAccountCache,
1048
+ getMessageCache: getMessageCache,
1049
+ replayCachedMessages: replayCachedMessages,
1050
+ sendWithCache: sendWithCache
1051
+ });
1052
+
1053
+ // 按 chatId 分队列的消息处理器 - 同一会话串行,不同会话并行
787
1054
  class MessageQueue {
788
- queue = [];
789
- processing = false;
1055
+ queues = new Map();
1056
+ processing = new Map();
790
1057
  runtime;
791
1058
  constructor(runtime) {
792
1059
  this.runtime = runtime;
793
1060
  }
794
- enqueue(task) {
795
- this.queue.push(task);
796
- this.process();
1061
+ enqueue(chatId, task) {
1062
+ if (!this.queues.has(chatId)) {
1063
+ this.queues.set(chatId, []);
1064
+ this.processing.set(chatId, false);
1065
+ }
1066
+ this.queues.get(chatId).push(task);
1067
+ this.process(chatId);
797
1068
  }
798
- async process() {
799
- if (this.processing || this.queue.length === 0)
1069
+ async process(chatId) {
1070
+ const queue = this.queues.get(chatId);
1071
+ const isProcessing = this.processing.get(chatId);
1072
+ if (!queue || isProcessing || queue.length === 0)
800
1073
  return;
801
- this.processing = true;
1074
+ this.processing.set(chatId, true);
802
1075
  try {
803
- const task = this.queue.shift();
1076
+ const task = queue.shift();
804
1077
  if (task) {
805
1078
  await task();
806
1079
  }
807
1080
  }
808
1081
  catch (err) {
809
- this.runtime.error?.(`[53aihub] MessageQueue error: ${String(err)}`);
1082
+ this.runtime.error?.(`[53aihub] MessageQueue error (chatId=${chatId}): ${String(err)}`);
810
1083
  }
811
1084
  finally {
812
- this.processing = false;
813
- // 继续处理队列中的下一条消息
814
- if (this.queue.length > 0) {
815
- this.process();
1085
+ this.processing.set(chatId, false);
1086
+ if (queue.length > 0) {
1087
+ this.process(chatId);
1088
+ }
1089
+ else {
1090
+ this.queues.delete(chatId);
1091
+ this.processing.delete(chatId);
816
1092
  }
817
1093
  }
818
1094
  }
819
1095
  clear() {
820
- this.queue = [];
1096
+ this.queues.clear();
1097
+ this.processing.clear();
821
1098
  }
822
1099
  }
823
1100
  function buildMessageContext(body, account, config, mediaList) {
@@ -882,20 +1159,26 @@ async function processMessage(params) {
882
1159
  runtime.log?.(`[53aihub] processMessage: chatId=${chatId}, msgId=${body.msgId}, text=${parsed.textParts.join(" ").substring(0, 50)}... images=${parsed.imageUrls.length} files=${parsed.fileUrls.length}`);
883
1160
  const core = getRuntime();
884
1161
  const streamId = `stream-${Date.now()}`;
1162
+ // 缓存感知的消息发送器 - 必须在所有 sendReply 调用前定义
1163
+ const cachedSend = async (sendParams) => {
1164
+ await sendWithCache(account.accountId, {
1165
+ ...sendParams,
1166
+ wsClient,
1167
+ runtime,
1168
+ }, sendReply);
1169
+ };
885
1170
  const accessResult = await checkAccessPolicy({
886
1171
  userId: body.userId,
887
1172
  account,
888
1173
  runtime,
889
1174
  });
890
1175
  if (!accessResult.allowed) {
891
- await sendReply({
892
- wsClient,
1176
+ await cachedSend({
893
1177
  text: accessResult.reason === "Pairing required"
894
1178
  ? "您尚未获得授权使用此机器人,请联系管理员进行审核。"
895
1179
  : `⚠️ 访问被拒绝: ${accessResult.reason || "未知原因"}`,
896
1180
  toChatId: chatId,
897
1181
  replyToMsgId: body.msgId,
898
- runtime,
899
1182
  finish: true,
900
1183
  streamId,
901
1184
  isError: true,
@@ -936,6 +1219,8 @@ async function processMessage(params) {
936
1219
  const mediaList = [...imageMediaList, ...fileMediaList];
937
1220
  const ctxPayload = buildMessageContext(body, account, config, mediaList);
938
1221
  let cleanedUp = false;
1222
+ let analysisTimeoutTimer = null;
1223
+ const analysisTimeoutMs = getRequestAnalysisTimeoutMs();
939
1224
  const safeCleanup = () => {
940
1225
  if (!cleanedUp) {
941
1226
  cleanedUp = true;
@@ -944,7 +1229,20 @@ async function processMessage(params) {
944
1229
  };
945
1230
  runtime.log?.(`[53aihub] processMessage: Starting dispatchReplyWithBufferedBlockDispatcher for msgId=${body.msgId}`);
946
1231
  try {
947
- await withTimeout(core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
1232
+ analysisTimeoutTimer = setTimeout(() => {
1233
+ runtime.log?.(`[53aihub] processMessage: analysis exceeded soft threshold ${analysisTimeoutMs}ms for msgId=${body.msgId}, keep waiting for completion`);
1234
+ if (wsClient.readyState !== WebSocket.OPEN) {
1235
+ return;
1236
+ }
1237
+ try {
1238
+ wsClient.ping();
1239
+ runtime.log?.(`[53aihub] processMessage: timeout keepalive ping sent for msgId=${body.msgId}`);
1240
+ }
1241
+ catch (err) {
1242
+ runtime.error?.(`[53aihub] processMessage: timeout keepalive ping failed for msgId=${body.msgId}: ${String(err)}`);
1243
+ }
1244
+ }, analysisTimeoutMs);
1245
+ await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
948
1246
  ctx: ctxPayload,
949
1247
  cfg: config,
950
1248
  dispatcherOptions: {
@@ -954,12 +1252,10 @@ async function processMessage(params) {
954
1252
  const errorMsg = payload.text || "Unknown error";
955
1253
  const errorCode = inferErrorCode(errorMsg);
956
1254
  runtime.error?.(`[53aihub] deliver ERROR: ${errorMsg}`);
957
- await sendReply({
958
- wsClient,
1255
+ await cachedSend({
959
1256
  text: `⚠️ ${errorMsg}`,
960
1257
  toChatId: chatId,
961
1258
  replyToMsgId: body.msgId,
962
- runtime,
963
1259
  finish: true,
964
1260
  streamId: state.streamId,
965
1261
  isError: true,
@@ -972,12 +1268,10 @@ async function processMessage(params) {
972
1268
  state.accumulatedText.startsWith("🧹 Compacting context");
973
1269
  if (isCompaction && info.kind !== "final") {
974
1270
  runtime.log?.(`[53aihub] deliver COMPACTION: text preview=${payload.text?.substring(0, 50)}...`);
975
- await sendReply({
976
- wsClient,
1271
+ await cachedSend({
977
1272
  text: payload.text || "",
978
1273
  toChatId: chatId,
979
1274
  replyToMsgId: body.msgId,
980
- runtime,
981
1275
  finish: false,
982
1276
  streamId: state.streamId,
983
1277
  isThinking: true,
@@ -987,12 +1281,10 @@ async function processMessage(params) {
987
1281
  state.accumulatedText += payload.text;
988
1282
  if (info.kind !== "final") {
989
1283
  runtime.log?.(`[53aihub] deliver STREAMING: accumulatedText preview=${state.accumulatedText.substring(0, 50)}...`);
990
- await sendReply({
991
- wsClient,
1284
+ await cachedSend({
992
1285
  text: state.accumulatedText,
993
1286
  toChatId: chatId,
994
1287
  replyToMsgId: body.msgId,
995
- runtime,
996
1288
  finish: false,
997
1289
  streamId: state.streamId,
998
1290
  });
@@ -1002,47 +1294,36 @@ async function processMessage(params) {
1002
1294
  runtime.error?.(`[53aihub] onError: kind=${info.kind}, error=${String(err)}`);
1003
1295
  const errorText = String(err);
1004
1296
  const errorCode = inferErrorCode(errorText);
1005
- try {
1006
- await sendReply({
1007
- wsClient,
1008
- text: `⚠️ 系统错误: ${errorText}`,
1009
- toChatId: chatId,
1010
- replyToMsgId: body.msgId,
1011
- runtime,
1012
- finish: true,
1013
- streamId: state.streamId,
1014
- isError: true,
1015
- errorCode,
1016
- errorDetails: `kind=${info.kind}, error=${errorText}`,
1017
- });
1018
- }
1019
- catch (sendErr) {
1020
- runtime.error?.(`[53aihub] Failed to send error notification: ${String(sendErr)}`);
1021
- }
1297
+ await cachedSend({
1298
+ text: `⚠️ 系统错误: ${errorText}`,
1299
+ toChatId: chatId,
1300
+ replyToMsgId: body.msgId,
1301
+ finish: true,
1302
+ streamId: state.streamId,
1303
+ isError: true,
1304
+ errorCode,
1305
+ errorDetails: `kind=${info.kind}, error=${errorText}`,
1306
+ });
1022
1307
  },
1023
1308
  },
1024
- }), MESSAGE_PROCESS_TIMEOUT_MS, `Message processing timed out (msgId=${body.msgId})`);
1309
+ });
1025
1310
  runtime.log?.(`[53aihub] processMessage: dispatchReply completed, accumulatedTextLen=${state.accumulatedText.length}`);
1026
1311
  if (state.accumulatedText) {
1027
1312
  runtime.log?.(`[53aihub] processMessage: Sending final reply with accumulatedText`);
1028
- await sendReply({
1029
- wsClient,
1313
+ await cachedSend({
1030
1314
  text: state.accumulatedText,
1031
1315
  toChatId: chatId,
1032
1316
  replyToMsgId: body.msgId,
1033
- runtime,
1034
1317
  finish: true,
1035
1318
  streamId: state.streamId,
1036
1319
  });
1037
1320
  }
1038
1321
  else {
1039
1322
  runtime.log?.(`[53aihub] processMessage: No accumulatedText, sending empty final reply`);
1040
- await sendReply({
1041
- wsClient,
1323
+ await cachedSend({
1042
1324
  text: "",
1043
1325
  toChatId: chatId,
1044
1326
  replyToMsgId: body.msgId,
1045
- runtime,
1046
1327
  finish: true,
1047
1328
  streamId: state.streamId,
1048
1329
  });
@@ -1055,12 +1336,10 @@ async function processMessage(params) {
1055
1336
  const errorCode = inferErrorCode(errorText);
1056
1337
  if (!cleanedUp) {
1057
1338
  try {
1058
- await sendReply({
1059
- wsClient,
1339
+ await cachedSend({
1060
1340
  text: `⚠️ 处理请求时发生异常: ${errorText}`,
1061
1341
  toChatId: chatId,
1062
1342
  replyToMsgId: body.msgId,
1063
- runtime,
1064
1343
  finish: true,
1065
1344
  streamId: state.streamId,
1066
1345
  isError: true,
@@ -1074,6 +1353,12 @@ async function processMessage(params) {
1074
1353
  }
1075
1354
  safeCleanup();
1076
1355
  }
1356
+ finally {
1357
+ if (analysisTimeoutTimer) {
1358
+ clearTimeout(analysisTimeoutTimer);
1359
+ analysisTimeoutTimer = null;
1360
+ }
1361
+ }
1077
1362
  }
1078
1363
  async function monitorProvider(options) {
1079
1364
  const { account, config, runtime, abortSignal } = options;
@@ -1085,17 +1370,6 @@ async function monitorProvider(options) {
1085
1370
  let pingInterval = null;
1086
1371
  let isAborted = false;
1087
1372
  let messageQueue = null;
1088
- const cleanup = async () => {
1089
- if (pingInterval) {
1090
- clearInterval(pingInterval);
1091
- pingInterval = null;
1092
- }
1093
- if (messageQueue) {
1094
- messageQueue.clear();
1095
- messageQueue = null;
1096
- }
1097
- await cleanupAccount(account.accountId);
1098
- };
1099
1373
  const connect = () => {
1100
1374
  if (isAborted)
1101
1375
  return;
@@ -1120,9 +1394,18 @@ async function monitorProvider(options) {
1120
1394
  setWebSocket(account.accountId, wsClient);
1121
1395
  // 初始化消息队列
1122
1396
  messageQueue = new MessageQueue(runtime);
1123
- wsClient.on("open", () => {
1397
+ wsClient.on("open", async () => {
1124
1398
  runtime.log?.(`[${account.accountId}] WebSocket connected successfully`);
1125
1399
  reconnectAttempts = 0;
1400
+ try {
1401
+ const replayedCount = await replayCachedMessages(account.accountId, wsClient, runtime, sendReply);
1402
+ if (replayedCount > 0) {
1403
+ runtime.log?.(`[${account.accountId}] Replayed ${replayedCount} cached messages`);
1404
+ }
1405
+ }
1406
+ catch (err) {
1407
+ runtime.error?.(`[${account.accountId}] Failed to replay cached messages: ${String(err)}`);
1408
+ }
1126
1409
  pingInterval = setInterval(() => {
1127
1410
  if (wsClient?.readyState === WebSocket.OPEN) {
1128
1411
  wsClient.ping();
@@ -1132,8 +1415,20 @@ async function monitorProvider(options) {
1132
1415
  wsClient.on("message", (data) => {
1133
1416
  const rawPayload = data.toString();
1134
1417
  runtime.log?.(`[${account.accountId}] Received WS message: ${rawPayload.substring(0, 200)}...`);
1135
- // 使用消息队列确保顺序处理,避免竞态条件
1136
- messageQueue?.enqueue(async () => {
1418
+ let chatId = "unknown";
1419
+ try {
1420
+ const msg = JSON.parse(rawPayload);
1421
+ if (msg.action === "chat") {
1422
+ chatId = msg.data?.conversation_id || msg.data?.user || "unknown";
1423
+ }
1424
+ else {
1425
+ chatId = msg.data?.chatId || msg.data?.userId || "unknown";
1426
+ }
1427
+ }
1428
+ catch {
1429
+ chatId = "unknown";
1430
+ }
1431
+ messageQueue?.enqueue(chatId, async () => {
1137
1432
  await processMessage({
1138
1433
  rawPayload,
1139
1434
  account,
@@ -1164,8 +1459,8 @@ async function monitorProvider(options) {
1164
1459
  setTimeout(connect, backoff);
1165
1460
  }
1166
1461
  else if (!isAborted) {
1167
- runtime.error?.(`[${account.accountId}] Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached`);
1168
- await cleanup();
1462
+ runtime.error?.(`[${account.accountId}] Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached, preserving cache for external recovery`);
1463
+ await cleanupAccount(account.accountId, false);
1169
1464
  reject(new Error(`Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached`));
1170
1465
  }
1171
1466
  });
@@ -1173,15 +1468,23 @@ async function monitorProvider(options) {
1173
1468
  if (abortSignal) {
1174
1469
  abortSignal.addEventListener("abort", async () => {
1175
1470
  isAborted = true;
1176
- await cleanup();
1471
+ if (pingInterval) {
1472
+ clearInterval(pingInterval);
1473
+ pingInterval = null;
1474
+ }
1475
+ if (messageQueue) {
1476
+ messageQueue.clear();
1477
+ messageQueue = null;
1478
+ }
1479
+ await cleanupAccount(account.accountId, true);
1177
1480
  resolve();
1178
1481
  });
1179
1482
  }
1180
1483
  warmupReqIdStore(account.accountId, (msg) => runtime.log?.(msg))
1181
1484
  .then(() => connect())
1182
1485
  .catch(async (err) => {
1183
- runtime.error?.(`[${account.accountId}] Failed to warmup ReqId store: ${String(err)}`);
1184
- await cleanup();
1486
+ runtime.error?.(`[${account.accountId}] Failed to warmup ReqId store: ${String(err)}, preserving cache for retry`);
1487
+ await cleanupAccount(account.accountId, false);
1185
1488
  reject(err);
1186
1489
  });
1187
1490
  });
@@ -1274,6 +1577,12 @@ async function promptWSUrl(prompter, account) {
1274
1577
  },
1275
1578
  })).trim();
1276
1579
  }
1580
+ async function promptSendThinkingMessage(prompter, account) {
1581
+ return await prompter.confirm({
1582
+ message: "Send a thinking message while the agent is processing?",
1583
+ initialValue: account?.sendThinkingMessage ?? true,
1584
+ });
1585
+ }
1277
1586
  function setAccessPolicy(cfg, accessPolicy) {
1278
1587
  const account = resolveAccount(cfg);
1279
1588
  const existingAllowFrom = account.config.allowFrom ?? [];
@@ -1333,6 +1642,7 @@ const aiHubOnboardingAdapter = {
1333
1642
  const botId = await promptBotId(prompter, account);
1334
1643
  const secret = await promptSecret(prompter, account);
1335
1644
  const WSUrl = await promptWSUrl(prompter, account);
1645
+ const sendThinkingMessage = await promptSendThinkingMessage(prompter, account);
1336
1646
  const cfgWithAccount = setAccount(cfg, {
1337
1647
  botId,
1338
1648
  secret,
@@ -1340,7 +1650,7 @@ const aiHubOnboardingAdapter = {
1340
1650
  enabled: true,
1341
1651
  accessPolicy: account.config.accessPolicy ?? "open",
1342
1652
  allowFrom: account.config.allowFrom ?? [],
1343
- sendThinkingMessage: account.sendThinkingMessage ?? true,
1653
+ sendThinkingMessage,
1344
1654
  });
1345
1655
  return { cfg: cfgWithAccount };
1346
1656
  },