@53ai/53ai-openclaw 1.0.8 → 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 +31 -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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
6
6
|
|
|
7
|
+
## [1.1.0] - 2026-03-27
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- 新增 `message-cache.ts` 模块,实现按账号隔离的消息缓存、重试机制与重连后的消息回放能力。
|
|
11
|
+
- 新增 `MESSAGE_CACHE_TTL_MS` 常量配置,用于统一控制消息缓存保留时长。
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- `monitor.ts`:消息队列从全局串行改为按 `chatId` 分队列处理,实现同会话串行、跨会话并行,并在队列空闲时自动回收。
|
|
15
|
+
- `monitor.ts`:发送链路接入缓存感知发送器,WebSocket 不可用或发送失败时自动缓存消息,连接恢复后自动回放。
|
|
16
|
+
- `monitor.ts` / `state-manager.ts`:重连上限和预热失败场景默认保留缓存,便于外部恢复后继续补发消息。
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `message-sender.ts`:`sendReply` 与 `sendThinkingMessage` 在 WebSocket 未就绪时改为抛出错误,避免发送失败被静默忽略。
|
|
20
|
+
- `message-cache.ts`:缓存满时优先淘汰非终态消息,降低最终完成态消息被淘汰导致会话状态不完整的风险。
|
|
21
|
+
|
|
22
|
+
## [1.0.9] - 2026-03-26
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- 更新 `homepage` 字段为 `https://www.53ai.com`。
|
|
26
|
+
|
|
27
|
+
## [1.0.8] - 2026-03-26
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- 新增压缩前后钩子(`handleBeforeCompaction` / `handleAfterCompaction`),支持在会话消息压缩前后向用户发送状态通知。
|
|
31
|
+
- 新增 `compaction-hooks.ts` 模块,实现从 `sessionKey` 解析 `chatId` 的逻辑,并对非本渠道会话自动跳过处理。
|
|
32
|
+
- 消息发送逻辑新增思考状态(thinking)支持,压缩过程中可向用户推送"正在整理对话记忆"等提示。
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- `message-sender.ts`:优化消息发送调用,支持传递思考状态标志。
|
|
36
|
+
- `monitor.ts`:增强监控逻辑以适配压缩钩子的事件上报。
|
|
37
|
+
|
|
7
38
|
## [1.0.7] - 2026-03-19
|
|
8
39
|
|
|
9
40
|
### Changed
|
package/dist/index.cjs.js
CHANGED
|
@@ -60,6 +60,12 @@ const WS_RECONNECT_BASE_DELAY_MS = 1000;
|
|
|
60
60
|
const TEXT_CHUNK_LIMIT = 4000;
|
|
61
61
|
/** 消息处理超时(毫秒) */
|
|
62
62
|
const MESSAGE_PROCESS_TIMEOUT_MS = 120000;
|
|
63
|
+
/**
|
|
64
|
+
* 消息缓存 TTL(毫秒)
|
|
65
|
+
* 设为 35 分钟,确保覆盖最大重连时间(约 28.5 分钟)+ 缓冲
|
|
66
|
+
* @see WS_MAX_RECONNECT_ATTEMPTS - 60 次,指数退避最大 30s
|
|
67
|
+
*/
|
|
68
|
+
const MESSAGE_CACHE_TTL_MS = 35 * 60 * 1000;
|
|
63
69
|
// ============================================================================
|
|
64
70
|
// 媒体处理配置
|
|
65
71
|
// ============================================================================
|
|
@@ -338,8 +344,9 @@ async function sendReply(params) {
|
|
|
338
344
|
const reqId = replyToMsgId || streamId;
|
|
339
345
|
runtime.log?.(`[53aihub] sendReply START: reqId=${reqId}, finish=${finish}, isError=${isError}, textLen=${text?.length || 0}, wsReadyState=${wsClient.readyState}`);
|
|
340
346
|
if (wsClient.readyState !== 1) {
|
|
341
|
-
|
|
342
|
-
|
|
347
|
+
const errorMsg = `WebSocket not connected (readyState=${wsClient.readyState}), cannot send message to ${toChatId}`;
|
|
348
|
+
runtime.error?.(`[53aihub] ${errorMsg}`);
|
|
349
|
+
throw new Error(`[53aihub] ${errorMsg}`);
|
|
343
350
|
}
|
|
344
351
|
if (isError) {
|
|
345
352
|
runtime.error?.(`[53aihub] sendReply ERROR: reqId=${reqId}, code=${errorCode}, text=${text?.substring(0, 100)}`);
|
|
@@ -450,8 +457,9 @@ async function sendThinkingMessage(wsClient, text, msgId, streamId, runtime) {
|
|
|
450
457
|
const wsState = wsClient.readyState;
|
|
451
458
|
runtime?.log?.(`[53aihub] sendThinkingMessage CALLED: msgId=${msgId}, streamId=${streamId}, text=${text}, wsReadyState=${wsState}`);
|
|
452
459
|
if (wsState !== 1) {
|
|
453
|
-
|
|
454
|
-
|
|
460
|
+
const errorMsg = `WebSocket not ready (state=${wsState})`;
|
|
461
|
+
runtime?.error?.(`[53aihub] sendThinkingMessage FAILED: ${errorMsg}`);
|
|
462
|
+
throw new Error(`[53aihub] ${errorMsg}`);
|
|
455
463
|
}
|
|
456
464
|
// 使用 OpenAI 兼容格式,确保 Go 后端能正确解析
|
|
457
465
|
// 关键:req_id 必须使用原始消息的 msgId,这样 Go 后端才能关联请求和响应
|
|
@@ -686,7 +694,7 @@ async function warmupReqIdStore(accountId = "default", log) {
|
|
|
686
694
|
const store = getOrCreateReqIdStore(accountId);
|
|
687
695
|
return store.warmup((err) => log?.(`ReqId warmup error: ${String(err)}`));
|
|
688
696
|
}
|
|
689
|
-
async function cleanupAccount(accountId) {
|
|
697
|
+
async function cleanupAccount(accountId, clearCache = false) {
|
|
690
698
|
const wsClient = wsClientInstances.get(accountId);
|
|
691
699
|
if (wsClient) {
|
|
692
700
|
try {
|
|
@@ -700,6 +708,10 @@ async function cleanupAccount(accountId) {
|
|
|
700
708
|
const store = reqIdStores.get(accountId);
|
|
701
709
|
if (store)
|
|
702
710
|
await store.flush();
|
|
711
|
+
if (clearCache) {
|
|
712
|
+
const { clearAccountCache } = await Promise.resolve().then(function () { return messageCache; });
|
|
713
|
+
clearAccountCache(accountId);
|
|
714
|
+
}
|
|
703
715
|
}
|
|
704
716
|
|
|
705
717
|
/**
|
|
@@ -807,41 +819,288 @@ async function downloadAndSaveFiles(params) {
|
|
|
807
819
|
return mediaList;
|
|
808
820
|
}
|
|
809
821
|
|
|
810
|
-
|
|
822
|
+
const DEFAULT_CONFIG = {
|
|
823
|
+
maxEntries: 100,
|
|
824
|
+
ttlMs: MESSAGE_CACHE_TTL_MS,
|
|
825
|
+
maxRetries: 3,
|
|
826
|
+
};
|
|
827
|
+
/**
|
|
828
|
+
* 按 accountId 隔离的消息缓存管理器
|
|
829
|
+
*/
|
|
830
|
+
class MessageCache {
|
|
831
|
+
cache = new Map();
|
|
832
|
+
config;
|
|
833
|
+
accountId;
|
|
834
|
+
runtime;
|
|
835
|
+
constructor(accountId, config = {}, runtime) {
|
|
836
|
+
this.accountId = accountId;
|
|
837
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
838
|
+
this.runtime = runtime;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* 生成唯一缓存 key
|
|
842
|
+
*/
|
|
843
|
+
generateCacheKey(reqId) {
|
|
844
|
+
return `${this.accountId}:${reqId}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* 添加消息到缓存,返回缓存 key
|
|
848
|
+
*/
|
|
849
|
+
add(params) {
|
|
850
|
+
this.prune();
|
|
851
|
+
if (this.cache.size >= this.config.maxEntries) {
|
|
852
|
+
// 优先淘汰非终态消息(finish=false 且 isError=false),保留终态消息
|
|
853
|
+
const entries = [...this.cache.entries()];
|
|
854
|
+
// 先找最老的非终态消息
|
|
855
|
+
const oldestNonFinal = entries
|
|
856
|
+
.filter(([, entry]) => !entry.finish && !entry.isError)
|
|
857
|
+
.sort((a, b) => a[1].createdAt - b[1].createdAt)[0];
|
|
858
|
+
if (oldestNonFinal) {
|
|
859
|
+
this.cache.delete(oldestNonFinal[0]);
|
|
860
|
+
this.runtime?.log?.(`[53aihub] MessageCache: evicted oldest non-final entry ${oldestNonFinal[0]}`);
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
// 所有消息都是终态,才淘汰最老的
|
|
864
|
+
const oldest = entries.sort((a, b) => a[1].createdAt - b[1].createdAt)[0];
|
|
865
|
+
if (oldest) {
|
|
866
|
+
this.cache.delete(oldest[0]);
|
|
867
|
+
this.runtime?.log?.(`[53aihub] MessageCache: evicted oldest final entry ${oldest[0]} (all entries are final)`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const cacheKey = this.generateCacheKey(params.reqId);
|
|
872
|
+
this.cache.set(cacheKey, {
|
|
873
|
+
cacheKey,
|
|
874
|
+
...params,
|
|
875
|
+
createdAt: Date.now(),
|
|
876
|
+
retryCount: 0,
|
|
877
|
+
});
|
|
878
|
+
this.runtime?.log?.(`[53aihub] MessageCache: cached message reqId=${params.reqId}, cacheKey=${cacheKey}, cacheSize=${this.cache.size}`);
|
|
879
|
+
return cacheKey;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* 获取所有待重试的消息,返回 cacheKey + entry
|
|
883
|
+
*/
|
|
884
|
+
getPendingMessages() {
|
|
885
|
+
const now = Date.now();
|
|
886
|
+
const pending = [];
|
|
887
|
+
for (const [key, entry] of this.cache) {
|
|
888
|
+
if (now - entry.createdAt > this.config.ttlMs) {
|
|
889
|
+
this.cache.delete(key);
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (entry.retryCount >= this.config.maxRetries) {
|
|
893
|
+
this.cache.delete(key);
|
|
894
|
+
this.runtime?.log?.(`[53aihub] MessageCache: dropped message after max retries cacheKey=${key}`);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
pending.push(entry);
|
|
898
|
+
}
|
|
899
|
+
return pending;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* 按 cacheKey 标记消息为已发送
|
|
903
|
+
*/
|
|
904
|
+
markSent(cacheKey) {
|
|
905
|
+
if (this.cache.has(cacheKey)) {
|
|
906
|
+
this.cache.delete(cacheKey);
|
|
907
|
+
this.runtime?.log?.(`[53aihub] MessageCache: marked as sent cacheKey=${cacheKey}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* 按 cacheKey 增加重试计数
|
|
912
|
+
*/
|
|
913
|
+
incrementRetry(cacheKey) {
|
|
914
|
+
const entry = this.cache.get(cacheKey);
|
|
915
|
+
if (entry) {
|
|
916
|
+
entry.retryCount++;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* 清理过期条目
|
|
921
|
+
*/
|
|
922
|
+
prune() {
|
|
923
|
+
const now = Date.now();
|
|
924
|
+
let pruned = 0;
|
|
925
|
+
for (const [key, entry] of this.cache) {
|
|
926
|
+
if (now - entry.createdAt > this.config.ttlMs) {
|
|
927
|
+
this.cache.delete(key);
|
|
928
|
+
pruned++;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (pruned > 0) {
|
|
932
|
+
this.runtime?.log?.(`[53aihub] MessageCache: pruned ${pruned} expired entries`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
size() {
|
|
936
|
+
return this.cache.size;
|
|
937
|
+
}
|
|
938
|
+
clear() {
|
|
939
|
+
this.cache.clear();
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
// 按 accountId 隔离的缓存实例
|
|
943
|
+
const cacheInstances = new Map();
|
|
944
|
+
/**
|
|
945
|
+
* 获取或创建按账号隔离的缓存实例
|
|
946
|
+
*/
|
|
947
|
+
function getMessageCache(accountId, runtime) {
|
|
948
|
+
let cache = cacheInstances.get(accountId);
|
|
949
|
+
if (!cache) {
|
|
950
|
+
cache = new MessageCache(accountId, {}, runtime);
|
|
951
|
+
cacheInstances.set(accountId, cache);
|
|
952
|
+
}
|
|
953
|
+
return cache;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* 清理指定账号的缓存
|
|
957
|
+
*/
|
|
958
|
+
function clearAccountCache(accountId) {
|
|
959
|
+
const cache = cacheInstances.get(accountId);
|
|
960
|
+
if (cache) {
|
|
961
|
+
cache.clear();
|
|
962
|
+
cacheInstances.delete(accountId);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* 缓存感知的消息发送器
|
|
967
|
+
* 返回 [success, cacheKey?] - 成功时 cacheKey 为 undefined,失败时返回缓存 key
|
|
968
|
+
*/
|
|
969
|
+
async function sendWithCache(accountId, params, sendFn) {
|
|
970
|
+
const { wsClient, replyToMsgId, streamId, text, toChatId, finish, isError, errorCode, errorDetails, isThinking, runtime } = params;
|
|
971
|
+
const reqId = replyToMsgId || streamId;
|
|
972
|
+
if (wsClient.readyState !== 1) {
|
|
973
|
+
const cache = getMessageCache(accountId, runtime);
|
|
974
|
+
const cacheKey = cache.add({
|
|
975
|
+
reqId,
|
|
976
|
+
toChatId,
|
|
977
|
+
text,
|
|
978
|
+
finish,
|
|
979
|
+
isError,
|
|
980
|
+
errorCode,
|
|
981
|
+
errorDetails,
|
|
982
|
+
isThinking,
|
|
983
|
+
});
|
|
984
|
+
runtime.error?.(`[53aihub] sendWithCache: WebSocket not ready, cached message cacheKey=${cacheKey}`);
|
|
985
|
+
return { success: false, cacheKey };
|
|
986
|
+
}
|
|
987
|
+
try {
|
|
988
|
+
await sendFn(params);
|
|
989
|
+
return { success: true };
|
|
990
|
+
}
|
|
991
|
+
catch (err) {
|
|
992
|
+
const cache = getMessageCache(accountId, runtime);
|
|
993
|
+
const cacheKey = cache.add({
|
|
994
|
+
reqId,
|
|
995
|
+
toChatId,
|
|
996
|
+
text,
|
|
997
|
+
finish,
|
|
998
|
+
isError,
|
|
999
|
+
errorCode,
|
|
1000
|
+
errorDetails,
|
|
1001
|
+
isThinking,
|
|
1002
|
+
});
|
|
1003
|
+
runtime.error?.(`[53aihub] sendWithCache: send failed, cached message cacheKey=${cacheKey}, error=${String(err)}`);
|
|
1004
|
+
return { success: false, cacheKey };
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* 重放缓存的消息
|
|
1009
|
+
*/
|
|
1010
|
+
async function replayCachedMessages(accountId, wsClient, runtime, sendFn) {
|
|
1011
|
+
const cache = getMessageCache(accountId, runtime);
|
|
1012
|
+
const pending = cache.getPendingMessages();
|
|
1013
|
+
if (pending.length === 0) {
|
|
1014
|
+
return 0;
|
|
1015
|
+
}
|
|
1016
|
+
runtime.log?.(`[53aihub] replayCachedMessages: replaying ${pending.length} cached messages for account=${accountId}`);
|
|
1017
|
+
let successCount = 0;
|
|
1018
|
+
for (const entry of pending) {
|
|
1019
|
+
if (wsClient.readyState !== 1) {
|
|
1020
|
+
runtime.error?.(`[53aihub] replayCachedMessages: WebSocket not ready, stopping replay`);
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
await sendFn({
|
|
1025
|
+
wsClient,
|
|
1026
|
+
text: entry.text,
|
|
1027
|
+
toChatId: entry.toChatId,
|
|
1028
|
+
replyToMsgId: entry.reqId,
|
|
1029
|
+
runtime,
|
|
1030
|
+
finish: entry.finish,
|
|
1031
|
+
streamId: entry.reqId,
|
|
1032
|
+
isError: entry.isError,
|
|
1033
|
+
errorCode: entry.errorCode,
|
|
1034
|
+
errorDetails: entry.errorDetails,
|
|
1035
|
+
isThinking: entry.isThinking,
|
|
1036
|
+
});
|
|
1037
|
+
cache.markSent(entry.cacheKey);
|
|
1038
|
+
successCount++;
|
|
1039
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1040
|
+
}
|
|
1041
|
+
catch (err) {
|
|
1042
|
+
const newRetryCount = entry.retryCount + 1;
|
|
1043
|
+
cache.incrementRetry(entry.cacheKey);
|
|
1044
|
+
runtime.error?.(`[53aihub] replayCachedMessages: failed to send cacheKey=${entry.cacheKey}, retryCount=${newRetryCount}`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
runtime.log?.(`[53aihub] replayCachedMessages: completed, ${successCount}/${pending.length} sent`);
|
|
1048
|
+
return successCount;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
var messageCache = /*#__PURE__*/Object.freeze({
|
|
1052
|
+
__proto__: null,
|
|
1053
|
+
clearAccountCache: clearAccountCache,
|
|
1054
|
+
getMessageCache: getMessageCache,
|
|
1055
|
+
replayCachedMessages: replayCachedMessages,
|
|
1056
|
+
sendWithCache: sendWithCache
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
// 按 chatId 分队列的消息处理器 - 同一会话串行,不同会话并行
|
|
811
1060
|
class MessageQueue {
|
|
812
|
-
|
|
813
|
-
processing =
|
|
1061
|
+
queues = new Map();
|
|
1062
|
+
processing = new Map();
|
|
814
1063
|
runtime;
|
|
815
1064
|
constructor(runtime) {
|
|
816
1065
|
this.runtime = runtime;
|
|
817
1066
|
}
|
|
818
|
-
enqueue(task) {
|
|
819
|
-
this.
|
|
820
|
-
|
|
1067
|
+
enqueue(chatId, task) {
|
|
1068
|
+
if (!this.queues.has(chatId)) {
|
|
1069
|
+
this.queues.set(chatId, []);
|
|
1070
|
+
this.processing.set(chatId, false);
|
|
1071
|
+
}
|
|
1072
|
+
this.queues.get(chatId).push(task);
|
|
1073
|
+
this.process(chatId);
|
|
821
1074
|
}
|
|
822
|
-
async process() {
|
|
823
|
-
|
|
1075
|
+
async process(chatId) {
|
|
1076
|
+
const queue = this.queues.get(chatId);
|
|
1077
|
+
const isProcessing = this.processing.get(chatId);
|
|
1078
|
+
if (!queue || isProcessing || queue.length === 0)
|
|
824
1079
|
return;
|
|
825
|
-
this.processing
|
|
1080
|
+
this.processing.set(chatId, true);
|
|
826
1081
|
try {
|
|
827
|
-
const task =
|
|
1082
|
+
const task = queue.shift();
|
|
828
1083
|
if (task) {
|
|
829
1084
|
await task();
|
|
830
1085
|
}
|
|
831
1086
|
}
|
|
832
1087
|
catch (err) {
|
|
833
|
-
this.runtime.error?.(`[53aihub] MessageQueue error: ${String(err)}`);
|
|
1088
|
+
this.runtime.error?.(`[53aihub] MessageQueue error (chatId=${chatId}): ${String(err)}`);
|
|
834
1089
|
}
|
|
835
1090
|
finally {
|
|
836
|
-
this.processing
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1091
|
+
this.processing.set(chatId, false);
|
|
1092
|
+
if (queue.length > 0) {
|
|
1093
|
+
this.process(chatId);
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
this.queues.delete(chatId);
|
|
1097
|
+
this.processing.delete(chatId);
|
|
840
1098
|
}
|
|
841
1099
|
}
|
|
842
1100
|
}
|
|
843
1101
|
clear() {
|
|
844
|
-
this.
|
|
1102
|
+
this.queues.clear();
|
|
1103
|
+
this.processing.clear();
|
|
845
1104
|
}
|
|
846
1105
|
}
|
|
847
1106
|
function buildMessageContext(body, account, config, mediaList) {
|
|
@@ -906,20 +1165,26 @@ async function processMessage(params) {
|
|
|
906
1165
|
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
1166
|
const core = getRuntime();
|
|
908
1167
|
const streamId = `stream-${Date.now()}`;
|
|
1168
|
+
// 缓存感知的消息发送器 - 必须在所有 sendReply 调用前定义
|
|
1169
|
+
const cachedSend = async (sendParams) => {
|
|
1170
|
+
await sendWithCache(account.accountId, {
|
|
1171
|
+
...sendParams,
|
|
1172
|
+
wsClient,
|
|
1173
|
+
runtime,
|
|
1174
|
+
}, sendReply);
|
|
1175
|
+
};
|
|
909
1176
|
const accessResult = await checkAccessPolicy({
|
|
910
1177
|
userId: body.userId,
|
|
911
1178
|
account,
|
|
912
1179
|
runtime,
|
|
913
1180
|
});
|
|
914
1181
|
if (!accessResult.allowed) {
|
|
915
|
-
await
|
|
916
|
-
wsClient,
|
|
1182
|
+
await cachedSend({
|
|
917
1183
|
text: accessResult.reason === "Pairing required"
|
|
918
1184
|
? "您尚未获得授权使用此机器人,请联系管理员进行审核。"
|
|
919
1185
|
: `⚠️ 访问被拒绝: ${accessResult.reason || "未知原因"}`,
|
|
920
1186
|
toChatId: chatId,
|
|
921
1187
|
replyToMsgId: body.msgId,
|
|
922
|
-
runtime,
|
|
923
1188
|
finish: true,
|
|
924
1189
|
streamId,
|
|
925
1190
|
isError: true,
|
|
@@ -978,12 +1243,10 @@ async function processMessage(params) {
|
|
|
978
1243
|
const errorMsg = payload.text || "Unknown error";
|
|
979
1244
|
const errorCode = inferErrorCode(errorMsg);
|
|
980
1245
|
runtime.error?.(`[53aihub] deliver ERROR: ${errorMsg}`);
|
|
981
|
-
await
|
|
982
|
-
wsClient,
|
|
1246
|
+
await cachedSend({
|
|
983
1247
|
text: `⚠️ ${errorMsg}`,
|
|
984
1248
|
toChatId: chatId,
|
|
985
1249
|
replyToMsgId: body.msgId,
|
|
986
|
-
runtime,
|
|
987
1250
|
finish: true,
|
|
988
1251
|
streamId: state.streamId,
|
|
989
1252
|
isError: true,
|
|
@@ -996,12 +1259,10 @@ async function processMessage(params) {
|
|
|
996
1259
|
state.accumulatedText.startsWith("🧹 Compacting context");
|
|
997
1260
|
if (isCompaction && info.kind !== "final") {
|
|
998
1261
|
runtime.log?.(`[53aihub] deliver COMPACTION: text preview=${payload.text?.substring(0, 50)}...`);
|
|
999
|
-
await
|
|
1000
|
-
wsClient,
|
|
1262
|
+
await cachedSend({
|
|
1001
1263
|
text: payload.text || "",
|
|
1002
1264
|
toChatId: chatId,
|
|
1003
1265
|
replyToMsgId: body.msgId,
|
|
1004
|
-
runtime,
|
|
1005
1266
|
finish: false,
|
|
1006
1267
|
streamId: state.streamId,
|
|
1007
1268
|
isThinking: true,
|
|
@@ -1011,12 +1272,10 @@ async function processMessage(params) {
|
|
|
1011
1272
|
state.accumulatedText += payload.text;
|
|
1012
1273
|
if (info.kind !== "final") {
|
|
1013
1274
|
runtime.log?.(`[53aihub] deliver STREAMING: accumulatedText preview=${state.accumulatedText.substring(0, 50)}...`);
|
|
1014
|
-
await
|
|
1015
|
-
wsClient,
|
|
1275
|
+
await cachedSend({
|
|
1016
1276
|
text: state.accumulatedText,
|
|
1017
1277
|
toChatId: chatId,
|
|
1018
1278
|
replyToMsgId: body.msgId,
|
|
1019
|
-
runtime,
|
|
1020
1279
|
finish: false,
|
|
1021
1280
|
streamId: state.streamId,
|
|
1022
1281
|
});
|
|
@@ -1026,47 +1285,36 @@ async function processMessage(params) {
|
|
|
1026
1285
|
runtime.error?.(`[53aihub] onError: kind=${info.kind}, error=${String(err)}`);
|
|
1027
1286
|
const errorText = String(err);
|
|
1028
1287
|
const errorCode = inferErrorCode(errorText);
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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
|
-
}
|
|
1288
|
+
await cachedSend({
|
|
1289
|
+
text: `⚠️ 系统错误: ${errorText}`,
|
|
1290
|
+
toChatId: chatId,
|
|
1291
|
+
replyToMsgId: body.msgId,
|
|
1292
|
+
finish: true,
|
|
1293
|
+
streamId: state.streamId,
|
|
1294
|
+
isError: true,
|
|
1295
|
+
errorCode,
|
|
1296
|
+
errorDetails: `kind=${info.kind}, error=${errorText}`,
|
|
1297
|
+
});
|
|
1046
1298
|
},
|
|
1047
1299
|
},
|
|
1048
1300
|
}), MESSAGE_PROCESS_TIMEOUT_MS, `Message processing timed out (msgId=${body.msgId})`);
|
|
1049
1301
|
runtime.log?.(`[53aihub] processMessage: dispatchReply completed, accumulatedTextLen=${state.accumulatedText.length}`);
|
|
1050
1302
|
if (state.accumulatedText) {
|
|
1051
1303
|
runtime.log?.(`[53aihub] processMessage: Sending final reply with accumulatedText`);
|
|
1052
|
-
await
|
|
1053
|
-
wsClient,
|
|
1304
|
+
await cachedSend({
|
|
1054
1305
|
text: state.accumulatedText,
|
|
1055
1306
|
toChatId: chatId,
|
|
1056
1307
|
replyToMsgId: body.msgId,
|
|
1057
|
-
runtime,
|
|
1058
1308
|
finish: true,
|
|
1059
1309
|
streamId: state.streamId,
|
|
1060
1310
|
});
|
|
1061
1311
|
}
|
|
1062
1312
|
else {
|
|
1063
1313
|
runtime.log?.(`[53aihub] processMessage: No accumulatedText, sending empty final reply`);
|
|
1064
|
-
await
|
|
1065
|
-
wsClient,
|
|
1314
|
+
await cachedSend({
|
|
1066
1315
|
text: "",
|
|
1067
1316
|
toChatId: chatId,
|
|
1068
1317
|
replyToMsgId: body.msgId,
|
|
1069
|
-
runtime,
|
|
1070
1318
|
finish: true,
|
|
1071
1319
|
streamId: state.streamId,
|
|
1072
1320
|
});
|
|
@@ -1079,12 +1327,10 @@ async function processMessage(params) {
|
|
|
1079
1327
|
const errorCode = inferErrorCode(errorText);
|
|
1080
1328
|
if (!cleanedUp) {
|
|
1081
1329
|
try {
|
|
1082
|
-
await
|
|
1083
|
-
wsClient,
|
|
1330
|
+
await cachedSend({
|
|
1084
1331
|
text: `⚠️ 处理请求时发生异常: ${errorText}`,
|
|
1085
1332
|
toChatId: chatId,
|
|
1086
1333
|
replyToMsgId: body.msgId,
|
|
1087
|
-
runtime,
|
|
1088
1334
|
finish: true,
|
|
1089
1335
|
streamId: state.streamId,
|
|
1090
1336
|
isError: true,
|
|
@@ -1109,17 +1355,6 @@ async function monitorProvider(options) {
|
|
|
1109
1355
|
let pingInterval = null;
|
|
1110
1356
|
let isAborted = false;
|
|
1111
1357
|
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
1358
|
const connect = () => {
|
|
1124
1359
|
if (isAborted)
|
|
1125
1360
|
return;
|
|
@@ -1144,9 +1379,18 @@ async function monitorProvider(options) {
|
|
|
1144
1379
|
setWebSocket(account.accountId, wsClient);
|
|
1145
1380
|
// 初始化消息队列
|
|
1146
1381
|
messageQueue = new MessageQueue(runtime);
|
|
1147
|
-
wsClient.on("open", () => {
|
|
1382
|
+
wsClient.on("open", async () => {
|
|
1148
1383
|
runtime.log?.(`[${account.accountId}] WebSocket connected successfully`);
|
|
1149
1384
|
reconnectAttempts = 0;
|
|
1385
|
+
try {
|
|
1386
|
+
const replayedCount = await replayCachedMessages(account.accountId, wsClient, runtime, sendReply);
|
|
1387
|
+
if (replayedCount > 0) {
|
|
1388
|
+
runtime.log?.(`[${account.accountId}] Replayed ${replayedCount} cached messages`);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
catch (err) {
|
|
1392
|
+
runtime.error?.(`[${account.accountId}] Failed to replay cached messages: ${String(err)}`);
|
|
1393
|
+
}
|
|
1150
1394
|
pingInterval = setInterval(() => {
|
|
1151
1395
|
if (wsClient?.readyState === WebSocket.OPEN) {
|
|
1152
1396
|
wsClient.ping();
|
|
@@ -1156,8 +1400,20 @@ async function monitorProvider(options) {
|
|
|
1156
1400
|
wsClient.on("message", (data) => {
|
|
1157
1401
|
const rawPayload = data.toString();
|
|
1158
1402
|
runtime.log?.(`[${account.accountId}] Received WS message: ${rawPayload.substring(0, 200)}...`);
|
|
1159
|
-
|
|
1160
|
-
|
|
1403
|
+
let chatId = "unknown";
|
|
1404
|
+
try {
|
|
1405
|
+
const msg = JSON.parse(rawPayload);
|
|
1406
|
+
if (msg.action === "chat") {
|
|
1407
|
+
chatId = msg.data?.conversation_id || msg.data?.user || "unknown";
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
chatId = msg.data?.chatId || msg.data?.userId || "unknown";
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
catch {
|
|
1414
|
+
chatId = "unknown";
|
|
1415
|
+
}
|
|
1416
|
+
messageQueue?.enqueue(chatId, async () => {
|
|
1161
1417
|
await processMessage({
|
|
1162
1418
|
rawPayload,
|
|
1163
1419
|
account,
|
|
@@ -1188,8 +1444,8 @@ async function monitorProvider(options) {
|
|
|
1188
1444
|
setTimeout(connect, backoff);
|
|
1189
1445
|
}
|
|
1190
1446
|
else if (!isAborted) {
|
|
1191
|
-
runtime.error?.(`[${account.accountId}] Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached`);
|
|
1192
|
-
await
|
|
1447
|
+
runtime.error?.(`[${account.accountId}] Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached, preserving cache for external recovery`);
|
|
1448
|
+
await cleanupAccount(account.accountId, false);
|
|
1193
1449
|
reject(new Error(`Max reconnect attempts (${WS_MAX_RECONNECT_ATTEMPTS}) reached`));
|
|
1194
1450
|
}
|
|
1195
1451
|
});
|
|
@@ -1197,15 +1453,23 @@ async function monitorProvider(options) {
|
|
|
1197
1453
|
if (abortSignal) {
|
|
1198
1454
|
abortSignal.addEventListener("abort", async () => {
|
|
1199
1455
|
isAborted = true;
|
|
1200
|
-
|
|
1456
|
+
if (pingInterval) {
|
|
1457
|
+
clearInterval(pingInterval);
|
|
1458
|
+
pingInterval = null;
|
|
1459
|
+
}
|
|
1460
|
+
if (messageQueue) {
|
|
1461
|
+
messageQueue.clear();
|
|
1462
|
+
messageQueue = null;
|
|
1463
|
+
}
|
|
1464
|
+
await cleanupAccount(account.accountId, true);
|
|
1201
1465
|
resolve();
|
|
1202
1466
|
});
|
|
1203
1467
|
}
|
|
1204
1468
|
warmupReqIdStore(account.accountId, (msg) => runtime.log?.(msg))
|
|
1205
1469
|
.then(() => connect())
|
|
1206
1470
|
.catch(async (err) => {
|
|
1207
|
-
runtime.error?.(`[${account.accountId}] Failed to warmup ReqId store: ${String(err)}`);
|
|
1208
|
-
await
|
|
1471
|
+
runtime.error?.(`[${account.accountId}] Failed to warmup ReqId store: ${String(err)}, preserving cache for retry`);
|
|
1472
|
+
await cleanupAccount(account.accountId, false);
|
|
1209
1473
|
reject(err);
|
|
1210
1474
|
});
|
|
1211
1475
|
});
|