@53ai/53ai-openclaw 1.0.9 → 1.1.0
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/CHANGELOG.md +15 -0
- package/dist/index.cjs.js +342 -78
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +342 -78
- package/dist/index.esm.js.map +1 -1
- package/dist/src/const.d.ts +6 -0
- package/dist/src/message-cache.d.ts +119 -0
- package/dist/src/state-manager.d.ts +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -36,6 +36,12 @@ const WS_RECONNECT_BASE_DELAY_MS = 1000;
|
|
|
36
36
|
const TEXT_CHUNK_LIMIT = 4000;
|
|
37
37
|
/** 消息处理超时(毫秒) */
|
|
38
38
|
const MESSAGE_PROCESS_TIMEOUT_MS = 120000;
|
|
39
|
+
/**
|
|
40
|
+
* 消息缓存 TTL(毫秒)
|
|
41
|
+
* 设为 35 分钟,确保覆盖最大重连时间(约 28.5 分钟)+ 缓冲
|
|
42
|
+
* @see WS_MAX_RECONNECT_ATTEMPTS - 60 次,指数退避最大 30s
|
|
43
|
+
*/
|
|
44
|
+
const MESSAGE_CACHE_TTL_MS = 35 * 60 * 1000;
|
|
39
45
|
// ============================================================================
|
|
40
46
|
// 媒体处理配置
|
|
41
47
|
// ============================================================================
|
|
@@ -314,8 +320,9 @@ async function sendReply(params) {
|
|
|
314
320
|
const reqId = replyToMsgId || streamId;
|
|
315
321
|
runtime.log?.(`[53aihub] sendReply START: reqId=${reqId}, finish=${finish}, isError=${isError}, textLen=${text?.length || 0}, wsReadyState=${wsClient.readyState}`);
|
|
316
322
|
if (wsClient.readyState !== 1) {
|
|
317
|
-
|
|
318
|
-
|
|
323
|
+
const errorMsg = `WebSocket not connected (readyState=${wsClient.readyState}), cannot send message to ${toChatId}`;
|
|
324
|
+
runtime.error?.(`[53aihub] ${errorMsg}`);
|
|
325
|
+
throw new Error(`[53aihub] ${errorMsg}`);
|
|
319
326
|
}
|
|
320
327
|
if (isError) {
|
|
321
328
|
runtime.error?.(`[53aihub] sendReply ERROR: reqId=${reqId}, code=${errorCode}, text=${text?.substring(0, 100)}`);
|
|
@@ -426,8 +433,9 @@ async function sendThinkingMessage(wsClient, text, msgId, streamId, runtime) {
|
|
|
426
433
|
const wsState = wsClient.readyState;
|
|
427
434
|
runtime?.log?.(`[53aihub] sendThinkingMessage CALLED: msgId=${msgId}, streamId=${streamId}, text=${text}, wsReadyState=${wsState}`);
|
|
428
435
|
if (wsState !== 1) {
|
|
429
|
-
|
|
430
|
-
|
|
436
|
+
const errorMsg = `WebSocket not ready (state=${wsState})`;
|
|
437
|
+
runtime?.error?.(`[53aihub] sendThinkingMessage FAILED: ${errorMsg}`);
|
|
438
|
+
throw new Error(`[53aihub] ${errorMsg}`);
|
|
431
439
|
}
|
|
432
440
|
// 使用 OpenAI 兼容格式,确保 Go 后端能正确解析
|
|
433
441
|
// 关键:req_id 必须使用原始消息的 msgId,这样 Go 后端才能关联请求和响应
|
|
@@ -662,7 +670,7 @@ async function warmupReqIdStore(accountId = "default", log) {
|
|
|
662
670
|
const store = getOrCreateReqIdStore(accountId);
|
|
663
671
|
return store.warmup((err) => log?.(`ReqId warmup error: ${String(err)}`));
|
|
664
672
|
}
|
|
665
|
-
async function cleanupAccount(accountId) {
|
|
673
|
+
async function cleanupAccount(accountId, clearCache = false) {
|
|
666
674
|
const wsClient = wsClientInstances.get(accountId);
|
|
667
675
|
if (wsClient) {
|
|
668
676
|
try {
|
|
@@ -676,6 +684,10 @@ async function cleanupAccount(accountId) {
|
|
|
676
684
|
const store = reqIdStores.get(accountId);
|
|
677
685
|
if (store)
|
|
678
686
|
await store.flush();
|
|
687
|
+
if (clearCache) {
|
|
688
|
+
const { clearAccountCache } = await Promise.resolve().then(function () { return messageCache; });
|
|
689
|
+
clearAccountCache(accountId);
|
|
690
|
+
}
|
|
679
691
|
}
|
|
680
692
|
|
|
681
693
|
/**
|
|
@@ -783,41 +795,288 @@ async function downloadAndSaveFiles(params) {
|
|
|
783
795
|
return mediaList;
|
|
784
796
|
}
|
|
785
797
|
|
|
786
|
-
|
|
798
|
+
const DEFAULT_CONFIG = {
|
|
799
|
+
maxEntries: 100,
|
|
800
|
+
ttlMs: MESSAGE_CACHE_TTL_MS,
|
|
801
|
+
maxRetries: 3,
|
|
802
|
+
};
|
|
803
|
+
/**
|
|
804
|
+
* 按 accountId 隔离的消息缓存管理器
|
|
805
|
+
*/
|
|
806
|
+
class MessageCache {
|
|
807
|
+
cache = new Map();
|
|
808
|
+
config;
|
|
809
|
+
accountId;
|
|
810
|
+
runtime;
|
|
811
|
+
constructor(accountId, config = {}, runtime) {
|
|
812
|
+
this.accountId = accountId;
|
|
813
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
814
|
+
this.runtime = runtime;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* 生成唯一缓存 key
|
|
818
|
+
*/
|
|
819
|
+
generateCacheKey(reqId) {
|
|
820
|
+
return `${this.accountId}:${reqId}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* 添加消息到缓存,返回缓存 key
|
|
824
|
+
*/
|
|
825
|
+
add(params) {
|
|
826
|
+
this.prune();
|
|
827
|
+
if (this.cache.size >= this.config.maxEntries) {
|
|
828
|
+
// 优先淘汰非终态消息(finish=false 且 isError=false),保留终态消息
|
|
829
|
+
const entries = [...this.cache.entries()];
|
|
830
|
+
// 先找最老的非终态消息
|
|
831
|
+
const oldestNonFinal = entries
|
|
832
|
+
.filter(([, entry]) => !entry.finish && !entry.isError)
|
|
833
|
+
.sort((a, b) => a[1].createdAt - b[1].createdAt)[0];
|
|
834
|
+
if (oldestNonFinal) {
|
|
835
|
+
this.cache.delete(oldestNonFinal[0]);
|
|
836
|
+
this.runtime?.log?.(`[53aihub] MessageCache: evicted oldest non-final entry ${oldestNonFinal[0]}`);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
// 所有消息都是终态,才淘汰最老的
|
|
840
|
+
const oldest = entries.sort((a, b) => a[1].createdAt - b[1].createdAt)[0];
|
|
841
|
+
if (oldest) {
|
|
842
|
+
this.cache.delete(oldest[0]);
|
|
843
|
+
this.runtime?.log?.(`[53aihub] MessageCache: evicted oldest final entry ${oldest[0]} (all entries are final)`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const cacheKey = this.generateCacheKey(params.reqId);
|
|
848
|
+
this.cache.set(cacheKey, {
|
|
849
|
+
cacheKey,
|
|
850
|
+
...params,
|
|
851
|
+
createdAt: Date.now(),
|
|
852
|
+
retryCount: 0,
|
|
853
|
+
});
|
|
854
|
+
this.runtime?.log?.(`[53aihub] MessageCache: cached message reqId=${params.reqId}, cacheKey=${cacheKey}, cacheSize=${this.cache.size}`);
|
|
855
|
+
return cacheKey;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* 获取所有待重试的消息,返回 cacheKey + entry
|
|
859
|
+
*/
|
|
860
|
+
getPendingMessages() {
|
|
861
|
+
const now = Date.now();
|
|
862
|
+
const pending = [];
|
|
863
|
+
for (const [key, entry] of this.cache) {
|
|
864
|
+
if (now - entry.createdAt > this.config.ttlMs) {
|
|
865
|
+
this.cache.delete(key);
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
if (entry.retryCount >= this.config.maxRetries) {
|
|
869
|
+
this.cache.delete(key);
|
|
870
|
+
this.runtime?.log?.(`[53aihub] MessageCache: dropped message after max retries cacheKey=${key}`);
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
pending.push(entry);
|
|
874
|
+
}
|
|
875
|
+
return pending;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* 按 cacheKey 标记消息为已发送
|
|
879
|
+
*/
|
|
880
|
+
markSent(cacheKey) {
|
|
881
|
+
if (this.cache.has(cacheKey)) {
|
|
882
|
+
this.cache.delete(cacheKey);
|
|
883
|
+
this.runtime?.log?.(`[53aihub] MessageCache: marked as sent cacheKey=${cacheKey}`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* 按 cacheKey 增加重试计数
|
|
888
|
+
*/
|
|
889
|
+
incrementRetry(cacheKey) {
|
|
890
|
+
const entry = this.cache.get(cacheKey);
|
|
891
|
+
if (entry) {
|
|
892
|
+
entry.retryCount++;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* 清理过期条目
|
|
897
|
+
*/
|
|
898
|
+
prune() {
|
|
899
|
+
const now = Date.now();
|
|
900
|
+
let pruned = 0;
|
|
901
|
+
for (const [key, entry] of this.cache) {
|
|
902
|
+
if (now - entry.createdAt > this.config.ttlMs) {
|
|
903
|
+
this.cache.delete(key);
|
|
904
|
+
pruned++;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (pruned > 0) {
|
|
908
|
+
this.runtime?.log?.(`[53aihub] MessageCache: pruned ${pruned} expired entries`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
size() {
|
|
912
|
+
return this.cache.size;
|
|
913
|
+
}
|
|
914
|
+
clear() {
|
|
915
|
+
this.cache.clear();
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
// 按 accountId 隔离的缓存实例
|
|
919
|
+
const cacheInstances = new Map();
|
|
920
|
+
/**
|
|
921
|
+
* 获取或创建按账号隔离的缓存实例
|
|
922
|
+
*/
|
|
923
|
+
function getMessageCache(accountId, runtime) {
|
|
924
|
+
let cache = cacheInstances.get(accountId);
|
|
925
|
+
if (!cache) {
|
|
926
|
+
cache = new MessageCache(accountId, {}, runtime);
|
|
927
|
+
cacheInstances.set(accountId, cache);
|
|
928
|
+
}
|
|
929
|
+
return cache;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* 清理指定账号的缓存
|
|
933
|
+
*/
|
|
934
|
+
function clearAccountCache(accountId) {
|
|
935
|
+
const cache = cacheInstances.get(accountId);
|
|
936
|
+
if (cache) {
|
|
937
|
+
cache.clear();
|
|
938
|
+
cacheInstances.delete(accountId);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* 缓存感知的消息发送器
|
|
943
|
+
* 返回 [success, cacheKey?] - 成功时 cacheKey 为 undefined,失败时返回缓存 key
|
|
944
|
+
*/
|
|
945
|
+
async function sendWithCache(accountId, params, sendFn) {
|
|
946
|
+
const { wsClient, replyToMsgId, streamId, text, toChatId, finish, isError, errorCode, errorDetails, isThinking, runtime } = params;
|
|
947
|
+
const reqId = replyToMsgId || streamId;
|
|
948
|
+
if (wsClient.readyState !== 1) {
|
|
949
|
+
const cache = getMessageCache(accountId, runtime);
|
|
950
|
+
const cacheKey = cache.add({
|
|
951
|
+
reqId,
|
|
952
|
+
toChatId,
|
|
953
|
+
text,
|
|
954
|
+
finish,
|
|
955
|
+
isError,
|
|
956
|
+
errorCode,
|
|
957
|
+
errorDetails,
|
|
958
|
+
isThinking,
|
|
959
|
+
});
|
|
960
|
+
runtime.error?.(`[53aihub] sendWithCache: WebSocket not ready, cached message cacheKey=${cacheKey}`);
|
|
961
|
+
return { success: false, cacheKey };
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
await sendFn(params);
|
|
965
|
+
return { success: true };
|
|
966
|
+
}
|
|
967
|
+
catch (err) {
|
|
968
|
+
const cache = getMessageCache(accountId, runtime);
|
|
969
|
+
const cacheKey = cache.add({
|
|
970
|
+
reqId,
|
|
971
|
+
toChatId,
|
|
972
|
+
text,
|
|
973
|
+
finish,
|
|
974
|
+
isError,
|
|
975
|
+
errorCode,
|
|
976
|
+
errorDetails,
|
|
977
|
+
isThinking,
|
|
978
|
+
});
|
|
979
|
+
runtime.error?.(`[53aihub] sendWithCache: send failed, cached message cacheKey=${cacheKey}, error=${String(err)}`);
|
|
980
|
+
return { success: false, cacheKey };
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* 重放缓存的消息
|
|
985
|
+
*/
|
|
986
|
+
async function replayCachedMessages(accountId, wsClient, runtime, sendFn) {
|
|
987
|
+
const cache = getMessageCache(accountId, runtime);
|
|
988
|
+
const pending = cache.getPendingMessages();
|
|
989
|
+
if (pending.length === 0) {
|
|
990
|
+
return 0;
|
|
991
|
+
}
|
|
992
|
+
runtime.log?.(`[53aihub] replayCachedMessages: replaying ${pending.length} cached messages for account=${accountId}`);
|
|
993
|
+
let successCount = 0;
|
|
994
|
+
for (const entry of pending) {
|
|
995
|
+
if (wsClient.readyState !== 1) {
|
|
996
|
+
runtime.error?.(`[53aihub] replayCachedMessages: WebSocket not ready, stopping replay`);
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
await sendFn({
|
|
1001
|
+
wsClient,
|
|
1002
|
+
text: entry.text,
|
|
1003
|
+
toChatId: entry.toChatId,
|
|
1004
|
+
replyToMsgId: entry.reqId,
|
|
1005
|
+
runtime,
|
|
1006
|
+
finish: entry.finish,
|
|
1007
|
+
streamId: entry.reqId,
|
|
1008
|
+
isError: entry.isError,
|
|
1009
|
+
errorCode: entry.errorCode,
|
|
1010
|
+
errorDetails: entry.errorDetails,
|
|
1011
|
+
isThinking: entry.isThinking,
|
|
1012
|
+
});
|
|
1013
|
+
cache.markSent(entry.cacheKey);
|
|
1014
|
+
successCount++;
|
|
1015
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1016
|
+
}
|
|
1017
|
+
catch (err) {
|
|
1018
|
+
const newRetryCount = entry.retryCount + 1;
|
|
1019
|
+
cache.incrementRetry(entry.cacheKey);
|
|
1020
|
+
runtime.error?.(`[53aihub] replayCachedMessages: failed to send cacheKey=${entry.cacheKey}, retryCount=${newRetryCount}`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
runtime.log?.(`[53aihub] replayCachedMessages: completed, ${successCount}/${pending.length} sent`);
|
|
1024
|
+
return successCount;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
var messageCache = /*#__PURE__*/Object.freeze({
|
|
1028
|
+
__proto__: null,
|
|
1029
|
+
clearAccountCache: clearAccountCache,
|
|
1030
|
+
getMessageCache: getMessageCache,
|
|
1031
|
+
replayCachedMessages: replayCachedMessages,
|
|
1032
|
+
sendWithCache: sendWithCache
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
// 按 chatId 分队列的消息处理器 - 同一会话串行,不同会话并行
|
|
787
1036
|
class MessageQueue {
|
|
788
|
-
|
|
789
|
-
processing =
|
|
1037
|
+
queues = new Map();
|
|
1038
|
+
processing = new Map();
|
|
790
1039
|
runtime;
|
|
791
1040
|
constructor(runtime) {
|
|
792
1041
|
this.runtime = runtime;
|
|
793
1042
|
}
|
|
794
|
-
enqueue(task) {
|
|
795
|
-
this.
|
|
796
|
-
|
|
1043
|
+
enqueue(chatId, task) {
|
|
1044
|
+
if (!this.queues.has(chatId)) {
|
|
1045
|
+
this.queues.set(chatId, []);
|
|
1046
|
+
this.processing.set(chatId, false);
|
|
1047
|
+
}
|
|
1048
|
+
this.queues.get(chatId).push(task);
|
|
1049
|
+
this.process(chatId);
|
|
797
1050
|
}
|
|
798
|
-
async process() {
|
|
799
|
-
|
|
1051
|
+
async process(chatId) {
|
|
1052
|
+
const queue = this.queues.get(chatId);
|
|
1053
|
+
const isProcessing = this.processing.get(chatId);
|
|
1054
|
+
if (!queue || isProcessing || queue.length === 0)
|
|
800
1055
|
return;
|
|
801
|
-
this.processing
|
|
1056
|
+
this.processing.set(chatId, true);
|
|
802
1057
|
try {
|
|
803
|
-
const task =
|
|
1058
|
+
const task = queue.shift();
|
|
804
1059
|
if (task) {
|
|
805
1060
|
await task();
|
|
806
1061
|
}
|
|
807
1062
|
}
|
|
808
1063
|
catch (err) {
|
|
809
|
-
this.runtime.error?.(`[53aihub] MessageQueue error: ${String(err)}`);
|
|
1064
|
+
this.runtime.error?.(`[53aihub] MessageQueue error (chatId=${chatId}): ${String(err)}`);
|
|
810
1065
|
}
|
|
811
1066
|
finally {
|
|
812
|
-
this.processing
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1067
|
+
this.processing.set(chatId, false);
|
|
1068
|
+
if (queue.length > 0) {
|
|
1069
|
+
this.process(chatId);
|
|
1070
|
+
}
|
|
1071
|
+
else {
|
|
1072
|
+
this.queues.delete(chatId);
|
|
1073
|
+
this.processing.delete(chatId);
|
|
816
1074
|
}
|
|
817
1075
|
}
|
|
818
1076
|
}
|
|
819
1077
|
clear() {
|
|
820
|
-
this.
|
|
1078
|
+
this.queues.clear();
|
|
1079
|
+
this.processing.clear();
|
|
821
1080
|
}
|
|
822
1081
|
}
|
|
823
1082
|
function buildMessageContext(body, account, config, mediaList) {
|
|
@@ -882,20 +1141,26 @@ async function processMessage(params) {
|
|
|
882
1141
|
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
1142
|
const core = getRuntime();
|
|
884
1143
|
const streamId = `stream-${Date.now()}`;
|
|
1144
|
+
// 缓存感知的消息发送器 - 必须在所有 sendReply 调用前定义
|
|
1145
|
+
const cachedSend = async (sendParams) => {
|
|
1146
|
+
await sendWithCache(account.accountId, {
|
|
1147
|
+
...sendParams,
|
|
1148
|
+
wsClient,
|
|
1149
|
+
runtime,
|
|
1150
|
+
}, sendReply);
|
|
1151
|
+
};
|
|
885
1152
|
const accessResult = await checkAccessPolicy({
|
|
886
1153
|
userId: body.userId,
|
|
887
1154
|
account,
|
|
888
1155
|
runtime,
|
|
889
1156
|
});
|
|
890
1157
|
if (!accessResult.allowed) {
|
|
891
|
-
await
|
|
892
|
-
wsClient,
|
|
1158
|
+
await cachedSend({
|
|
893
1159
|
text: accessResult.reason === "Pairing required"
|
|
894
1160
|
? "您尚未获得授权使用此机器人,请联系管理员进行审核。"
|
|
895
1161
|
: `⚠️ 访问被拒绝: ${accessResult.reason || "未知原因"}`,
|
|
896
1162
|
toChatId: chatId,
|
|
897
1163
|
replyToMsgId: body.msgId,
|
|
898
|
-
runtime,
|
|
899
1164
|
finish: true,
|
|
900
1165
|
streamId,
|
|
901
1166
|
isError: true,
|
|
@@ -954,12 +1219,10 @@ async function processMessage(params) {
|
|
|
954
1219
|
const errorMsg = payload.text || "Unknown error";
|
|
955
1220
|
const errorCode = inferErrorCode(errorMsg);
|
|
956
1221
|
runtime.error?.(`[53aihub] deliver ERROR: ${errorMsg}`);
|
|
957
|
-
await
|
|
958
|
-
wsClient,
|
|
1222
|
+
await cachedSend({
|
|
959
1223
|
text: `⚠️ ${errorMsg}`,
|
|
960
1224
|
toChatId: chatId,
|
|
961
1225
|
replyToMsgId: body.msgId,
|
|
962
|
-
runtime,
|
|
963
1226
|
finish: true,
|
|
964
1227
|
streamId: state.streamId,
|
|
965
1228
|
isError: true,
|
|
@@ -972,12 +1235,10 @@ async function processMessage(params) {
|
|
|
972
1235
|
state.accumulatedText.startsWith("🧹 Compacting context");
|
|
973
1236
|
if (isCompaction && info.kind !== "final") {
|
|
974
1237
|
runtime.log?.(`[53aihub] deliver COMPACTION: text preview=${payload.text?.substring(0, 50)}...`);
|
|
975
|
-
await
|
|
976
|
-
wsClient,
|
|
1238
|
+
await cachedSend({
|
|
977
1239
|
text: payload.text || "",
|
|
978
1240
|
toChatId: chatId,
|
|
979
1241
|
replyToMsgId: body.msgId,
|
|
980
|
-
runtime,
|
|
981
1242
|
finish: false,
|
|
982
1243
|
streamId: state.streamId,
|
|
983
1244
|
isThinking: true,
|
|
@@ -987,12 +1248,10 @@ async function processMessage(params) {
|
|
|
987
1248
|
state.accumulatedText += payload.text;
|
|
988
1249
|
if (info.kind !== "final") {
|
|
989
1250
|
runtime.log?.(`[53aihub] deliver STREAMING: accumulatedText preview=${state.accumulatedText.substring(0, 50)}...`);
|
|
990
|
-
await
|
|
991
|
-
wsClient,
|
|
1251
|
+
await cachedSend({
|
|
992
1252
|
text: state.accumulatedText,
|
|
993
1253
|
toChatId: chatId,
|
|
994
1254
|
replyToMsgId: body.msgId,
|
|
995
|
-
runtime,
|
|
996
1255
|
finish: false,
|
|
997
1256
|
streamId: state.streamId,
|
|
998
1257
|
});
|
|
@@ -1002,47 +1261,36 @@ async function processMessage(params) {
|
|
|
1002
1261
|
runtime.error?.(`[53aihub] onError: kind=${info.kind}, error=${String(err)}`);
|
|
1003
1262
|
const errorText = String(err);
|
|
1004
1263
|
const errorCode = inferErrorCode(errorText);
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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
|
-
}
|
|
1264
|
+
await cachedSend({
|
|
1265
|
+
text: `⚠️ 系统错误: ${errorText}`,
|
|
1266
|
+
toChatId: chatId,
|
|
1267
|
+
replyToMsgId: body.msgId,
|
|
1268
|
+
finish: true,
|
|
1269
|
+
streamId: state.streamId,
|
|
1270
|
+
isError: true,
|
|
1271
|
+
errorCode,
|
|
1272
|
+
errorDetails: `kind=${info.kind}, error=${errorText}`,
|
|
1273
|
+
});
|
|
1022
1274
|
},
|
|
1023
1275
|
},
|
|
1024
1276
|
}), MESSAGE_PROCESS_TIMEOUT_MS, `Message processing timed out (msgId=${body.msgId})`);
|
|
1025
1277
|
runtime.log?.(`[53aihub] processMessage: dispatchReply completed, accumulatedTextLen=${state.accumulatedText.length}`);
|
|
1026
1278
|
if (state.accumulatedText) {
|
|
1027
1279
|
runtime.log?.(`[53aihub] processMessage: Sending final reply with accumulatedText`);
|
|
1028
|
-
await
|
|
1029
|
-
wsClient,
|
|
1280
|
+
await cachedSend({
|
|
1030
1281
|
text: state.accumulatedText,
|
|
1031
1282
|
toChatId: chatId,
|
|
1032
1283
|
replyToMsgId: body.msgId,
|
|
1033
|
-
runtime,
|
|
1034
1284
|
finish: true,
|
|
1035
1285
|
streamId: state.streamId,
|
|
1036
1286
|
});
|
|
1037
1287
|
}
|
|
1038
1288
|
else {
|
|
1039
1289
|
runtime.log?.(`[53aihub] processMessage: No accumulatedText, sending empty final reply`);
|
|
1040
|
-
await
|
|
1041
|
-
wsClient,
|
|
1290
|
+
await cachedSend({
|
|
1042
1291
|
text: "",
|
|
1043
1292
|
toChatId: chatId,
|
|
1044
1293
|
replyToMsgId: body.msgId,
|
|
1045
|
-
runtime,
|
|
1046
1294
|
finish: true,
|
|
1047
1295
|
streamId: state.streamId,
|
|
1048
1296
|
});
|
|
@@ -1055,12 +1303,10 @@ async function processMessage(params) {
|
|
|
1055
1303
|
const errorCode = inferErrorCode(errorText);
|
|
1056
1304
|
if (!cleanedUp) {
|
|
1057
1305
|
try {
|
|
1058
|
-
await
|
|
1059
|
-
wsClient,
|
|
1306
|
+
await cachedSend({
|
|
1060
1307
|
text: `⚠️ 处理请求时发生异常: ${errorText}`,
|
|
1061
1308
|
toChatId: chatId,
|
|
1062
1309
|
replyToMsgId: body.msgId,
|
|
1063
|
-
runtime,
|
|
1064
1310
|
finish: true,
|
|
1065
1311
|
streamId: state.streamId,
|
|
1066
1312
|
isError: true,
|
|
@@ -1085,17 +1331,6 @@ async function monitorProvider(options) {
|
|
|
1085
1331
|
let pingInterval = null;
|
|
1086
1332
|
let isAborted = false;
|
|
1087
1333
|
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
1334
|
const connect = () => {
|
|
1100
1335
|
if (isAborted)
|
|
1101
1336
|
return;
|
|
@@ -1120,9 +1355,18 @@ async function monitorProvider(options) {
|
|
|
1120
1355
|
setWebSocket(account.accountId, wsClient);
|
|
1121
1356
|
// 初始化消息队列
|
|
1122
1357
|
messageQueue = new MessageQueue(runtime);
|
|
1123
|
-
wsClient.on("open", () => {
|
|
1358
|
+
wsClient.on("open", async () => {
|
|
1124
1359
|
runtime.log?.(`[${account.accountId}] WebSocket connected successfully`);
|
|
1125
1360
|
reconnectAttempts = 0;
|
|
1361
|
+
try {
|
|
1362
|
+
const replayedCount = await replayCachedMessages(account.accountId, wsClient, runtime, sendReply);
|
|
1363
|
+
if (replayedCount > 0) {
|
|
1364
|
+
runtime.log?.(`[${account.accountId}] Replayed ${replayedCount} cached messages`);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
catch (err) {
|
|
1368
|
+
runtime.error?.(`[${account.accountId}] Failed to replay cached messages: ${String(err)}`);
|
|
1369
|
+
}
|
|
1126
1370
|
pingInterval = setInterval(() => {
|
|
1127
1371
|
if (wsClient?.readyState === WebSocket.OPEN) {
|
|
1128
1372
|
wsClient.ping();
|
|
@@ -1132,8 +1376,20 @@ async function monitorProvider(options) {
|
|
|
1132
1376
|
wsClient.on("message", (data) => {
|
|
1133
1377
|
const rawPayload = data.toString();
|
|
1134
1378
|
runtime.log?.(`[${account.accountId}] Received WS message: ${rawPayload.substring(0, 200)}...`);
|
|
1135
|
-
|
|
1136
|
-
|
|
1379
|
+
let chatId = "unknown";
|
|
1380
|
+
try {
|
|
1381
|
+
const msg = JSON.parse(rawPayload);
|
|
1382
|
+
if (msg.action === "chat") {
|
|
1383
|
+
chatId = msg.data?.conversation_id || msg.data?.user || "unknown";
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
chatId = msg.data?.chatId || msg.data?.userId || "unknown";
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
catch {
|
|
1390
|
+
chatId = "unknown";
|
|
1391
|
+
}
|
|
1392
|
+
messageQueue?.enqueue(chatId, async () => {
|
|
1137
1393
|
await processMessage({
|
|
1138
1394
|
rawPayload,
|
|
1139
1395
|
account,
|
|
@@ -1164,8 +1420,8 @@ async function monitorProvider(options) {
|
|
|
1164
1420
|
setTimeout(connect, backoff);
|
|
1165
1421
|
}
|
|
1166
1422
|
else if (!isAborted) {
|
|
1167
|
-
runtime.error?.(`[${account.accountId}] Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached`);
|
|
1168
|
-
await
|
|
1423
|
+
runtime.error?.(`[${account.accountId}] Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached, preserving cache for external recovery`);
|
|
1424
|
+
await cleanupAccount(account.accountId, false);
|
|
1169
1425
|
reject(new Error(`Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached`));
|
|
1170
1426
|
}
|
|
1171
1427
|
});
|
|
@@ -1173,15 +1429,23 @@ async function monitorProvider(options) {
|
|
|
1173
1429
|
if (abortSignal) {
|
|
1174
1430
|
abortSignal.addEventListener("abort", async () => {
|
|
1175
1431
|
isAborted = true;
|
|
1176
|
-
|
|
1432
|
+
if (pingInterval) {
|
|
1433
|
+
clearInterval(pingInterval);
|
|
1434
|
+
pingInterval = null;
|
|
1435
|
+
}
|
|
1436
|
+
if (messageQueue) {
|
|
1437
|
+
messageQueue.clear();
|
|
1438
|
+
messageQueue = null;
|
|
1439
|
+
}
|
|
1440
|
+
await cleanupAccount(account.accountId, true);
|
|
1177
1441
|
resolve();
|
|
1178
1442
|
});
|
|
1179
1443
|
}
|
|
1180
1444
|
warmupReqIdStore(account.accountId, (msg) => runtime.log?.(msg))
|
|
1181
1445
|
.then(() => connect())
|
|
1182
1446
|
.catch(async (err) => {
|
|
1183
|
-
runtime.error?.(`[${account.accountId}] Failed to warmup ReqId store: ${String(err)}`);
|
|
1184
|
-
await
|
|
1447
|
+
runtime.error?.(`[${account.accountId}] Failed to warmup ReqId store: ${String(err)}, preserving cache for retry`);
|
|
1448
|
+
await cleanupAccount(account.accountId, false);
|
|
1185
1449
|
reject(err);
|
|
1186
1450
|
});
|
|
1187
1451
|
});
|