@ryantest/openclaw-qqbot 0.0.3 → 1.6.6-alpha.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/README.md +2 -15
- package/README.zh.md +3 -16
- package/dist/src/admin-resolver.d.ts +12 -6
- package/dist/src/admin-resolver.js +69 -34
- package/dist/src/api.d.ts +105 -1
- package/dist/src/api.js +164 -15
- package/dist/src/channel.js +13 -0
- package/dist/src/config.js +3 -10
- package/dist/src/deliver-debounce.d.ts +74 -0
- package/dist/src/deliver-debounce.js +174 -0
- package/dist/src/gateway.js +450 -248
- package/dist/src/image-server.d.ts +27 -8
- package/dist/src/image-server.js +179 -71
- package/dist/src/inbound-attachments.d.ts +3 -1
- package/dist/src/inbound-attachments.js +28 -14
- package/dist/src/outbound-deliver.js +77 -148
- package/dist/src/outbound.d.ts +6 -4
- package/dist/src/outbound.js +266 -442
- package/dist/src/reply-dispatcher.js +4 -4
- package/dist/src/request-context.d.ts +18 -0
- package/dist/src/request-context.js +30 -0
- package/dist/src/slash-commands.js +277 -32
- package/dist/src/startup-greeting.d.ts +5 -5
- package/dist/src/startup-greeting.js +32 -13
- package/dist/src/streaming.d.ts +244 -0
- package/dist/src/streaming.js +907 -0
- package/dist/src/tools/remind.js +11 -10
- package/dist/src/types.d.ts +101 -0
- package/dist/src/types.js +17 -1
- package/dist/src/update-checker.js +2 -8
- package/dist/src/utils/audio-convert.d.ts +9 -0
- package/dist/src/utils/audio-convert.js +51 -0
- package/dist/src/utils/chunked-upload.d.ts +59 -0
- package/dist/src/utils/chunked-upload.js +289 -0
- package/dist/src/utils/file-utils.d.ts +7 -1
- package/dist/src/utils/file-utils.js +24 -2
- package/dist/src/utils/media-send.d.ts +147 -0
- package/dist/src/utils/media-send.js +434 -0
- package/dist/src/utils/pkg-version.d.ts +5 -0
- package/dist/src/utils/pkg-version.js +51 -0
- package/dist/src/utils/ssrf-guard.d.ts +25 -0
- package/dist/src/utils/ssrf-guard.js +91 -0
- package/node_modules/ws/index.js +15 -6
- package/node_modules/ws/lib/permessage-deflate.js +6 -6
- package/node_modules/ws/lib/websocket-server.js +5 -5
- package/node_modules/ws/lib/websocket.js +6 -6
- package/node_modules/ws/package.json +4 -3
- package/node_modules/ws/wrapper.mjs +14 -1
- package/openclaw.plugin.json +1 -0
- package/package.json +11 -22
- package/scripts/postinstall-link-sdk.js +113 -0
- package/scripts/upgrade-via-npm.ps1 +161 -6
- package/scripts/upgrade-via-npm.sh +311 -104
- package/scripts/upgrade-via-source.sh +117 -0
- package/skills/qqbot-media/SKILL.md +9 -5
- package/skills/qqbot-remind/SKILL.md +3 -3
- package/src/admin-resolver.ts +76 -35
- package/src/api.ts +284 -12
- package/src/channel.ts +12 -0
- package/src/config.ts +3 -10
- package/src/deliver-debounce.ts +229 -0
- package/src/gateway.ts +277 -67
- package/src/image-server.ts +213 -77
- package/src/inbound-attachments.ts +32 -15
- package/src/outbound-deliver.ts +77 -157
- package/src/outbound.ts +304 -451
- package/src/reply-dispatcher.ts +4 -4
- package/src/request-context.ts +39 -0
- package/src/slash-commands.ts +303 -33
- package/src/startup-greeting.ts +35 -13
- package/src/streaming.ts +1096 -0
- package/src/tools/remind.ts +15 -11
- package/src/types.ts +111 -0
- package/src/update-checker.ts +2 -7
- package/src/utils/audio-convert.ts +56 -0
- package/src/utils/chunked-upload.ts +419 -0
- package/src/utils/file-utils.ts +28 -2
- package/src/utils/media-send.ts +563 -0
- package/src/utils/pkg-version.ts +54 -0
- package/src/utils/ssrf-guard.ts +102 -0
- package/clawdbot.plugin.json +0 -16
- package/dist/src/user-messages.d.ts +0 -8
- package/dist/src/user-messages.js +0 -8
- package/moltbot.plugin.json +0 -16
- package/scripts/upgrade-via-alt-pkg.sh +0 -307
- package/src/bot-logs-2026-03-21T11-21-47(2).txt +0 -46
- package/src/gateway.log +0 -43
- package/src/openclaw-2026-03-21.log +0 -3729
- package/src/user-messages.ts +0 -7
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQ Bot 流式消息控制器(简化版)
|
|
3
|
+
*
|
|
4
|
+
* 核心原则:
|
|
5
|
+
* 1. 绝对不修改原始内容(不 trim、不 strip),避免 PREFIX MISMATCH
|
|
6
|
+
* 2. 媒体标签同步等待发送完成
|
|
7
|
+
* 3. 碰到富媒体标签(包括未闭合前缀)时,先终结当前流式会话再处理
|
|
8
|
+
* 4. 纯空白分片处理:
|
|
9
|
+
* - 首分片空白 → 暂停发送(不开启流式),但内容保留
|
|
10
|
+
* - 被媒体标签打断或结束时,如果还都是空白 → 不发送
|
|
11
|
+
* - 结束时已有活跃流式会话(之前有非空白分片)→ 可以发送当前空白分片
|
|
12
|
+
* 5. 回复边界检测:通过前缀匹配判断(而非仅长度缩短),
|
|
13
|
+
* 如果新文本不是上次处理文本的前缀延续,视为新消息
|
|
14
|
+
*/
|
|
15
|
+
import type { ResolvedQQBotAccount } from "./types.js";
|
|
16
|
+
/** 流式状态机阶段 */
|
|
17
|
+
type StreamingPhase = "idle" | "streaming" | "completed" | "aborted";
|
|
18
|
+
/** StreamingController 的依赖注入 */
|
|
19
|
+
export interface StreamingControllerDeps {
|
|
20
|
+
/** QQ Bot 账户配置 */
|
|
21
|
+
account: ResolvedQQBotAccount;
|
|
22
|
+
/** 目标用户 openid(流式 API 仅支持 C2C) */
|
|
23
|
+
userId: string;
|
|
24
|
+
/** 被动回复的消息 ID */
|
|
25
|
+
replyToMsgId: string;
|
|
26
|
+
/** 事件 ID */
|
|
27
|
+
eventId: string;
|
|
28
|
+
/** 日志前缀 */
|
|
29
|
+
logPrefix?: string;
|
|
30
|
+
/** 日志对象(直接传 gateway 的 log) */
|
|
31
|
+
log?: {
|
|
32
|
+
info(msg: string): void;
|
|
33
|
+
error(msg: string): void;
|
|
34
|
+
warn?(msg: string): void;
|
|
35
|
+
debug?(msg: string): void;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 媒体发送上下文(用于在流式模式下发送富媒体)
|
|
39
|
+
* 如果不提供,遇到媒体标签时会抛出错误导致 fallback
|
|
40
|
+
*/
|
|
41
|
+
mediaContext?: StreamingMediaContext;
|
|
42
|
+
/**
|
|
43
|
+
* 回复边界回调:检测到 text 长度缩短(新回复开始)时触发。
|
|
44
|
+
*
|
|
45
|
+
* 触发时当前 controller 已经 finalize(终结当前流式会话,处理完之前的内容),
|
|
46
|
+
* 调用方应创建新的 StreamingController 并用 newReplyText 调其 onPartialReply。
|
|
47
|
+
*
|
|
48
|
+
* @param newReplyText 新回复的初始文本(已 strip reasoning tags)
|
|
49
|
+
*/
|
|
50
|
+
onReplyBoundary?: (newReplyText: string) => void | Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* QQ Bot 流式消息控制器
|
|
54
|
+
*
|
|
55
|
+
* 管理 C2C 流式消息的完整生命周期:
|
|
56
|
+
* 1. idle: 初始状态,等待首次文本
|
|
57
|
+
* 2. streaming: 流式发送中,通过 API 逐步更新消息内容
|
|
58
|
+
* 3. completed: 正常完成,已发送 input_state="10"
|
|
59
|
+
* 4. aborted: 中止(进程退出/错误)
|
|
60
|
+
*
|
|
61
|
+
* 富媒体标签处理流程:
|
|
62
|
+
* 当检测到富媒体标签时:
|
|
63
|
+
* 1. 将标签前的文本通过流式发完 → 结束当前流式会话 (input_state="10")
|
|
64
|
+
* 2. 同步等待媒体发送完成
|
|
65
|
+
* 3. 创建新的流式会话 → 继续发送标签后的剩余文本
|
|
66
|
+
*/
|
|
67
|
+
export declare class StreamingController {
|
|
68
|
+
private phase;
|
|
69
|
+
/**
|
|
70
|
+
* 最后一次收到的完整 normalized 全量文本。
|
|
71
|
+
* - onPartialReply 每次更新(回复边界时会拼接前缀)
|
|
72
|
+
* - performFlush 从 sentIndex 开始切片来获取当前会话的显示内容
|
|
73
|
+
* - onIdle 校验时用于前缀匹配
|
|
74
|
+
*/
|
|
75
|
+
private lastNormalizedFull;
|
|
76
|
+
/**
|
|
77
|
+
* 在 lastNormalizedFull 中已经"消费"到的位置。
|
|
78
|
+
* "消费"包括:已通过流式发送并终结的文本段、已处理的媒体标签。
|
|
79
|
+
* - 每次流式会话终结(endCurrentStreamIfNeeded)后推进到终结点
|
|
80
|
+
* - 每次媒体标签处理后推进到标签结束位置
|
|
81
|
+
* - resetStreamSession 后,新的流式会话从 sentIndex 开始
|
|
82
|
+
*/
|
|
83
|
+
private sentIndex;
|
|
84
|
+
private streamMsgId;
|
|
85
|
+
/** 当前流式会话的 msg_seq,同一会话内所有 chunk 共享;null 表示需要重新生成 */
|
|
86
|
+
private msgSeq;
|
|
87
|
+
private streamIndex;
|
|
88
|
+
private dispatchFullyComplete;
|
|
89
|
+
/** Promise 链,回调的实际逻辑都挂到链尾,保证串行 */
|
|
90
|
+
private _callbackChain;
|
|
91
|
+
/**
|
|
92
|
+
* 记录首先到达的回调来源,后续其他来源的回调将被忽略。
|
|
93
|
+
* - null: 尚未确定
|
|
94
|
+
* - 非 null: 已锁定,只有相同来源的回调才允许继续执行
|
|
95
|
+
*/
|
|
96
|
+
private firstCallbackSource;
|
|
97
|
+
/**
|
|
98
|
+
* 尝试获取回调互斥锁。
|
|
99
|
+
* - 尚未锁定 → 锁定为 source,返回 true
|
|
100
|
+
* - 已锁定且来源相同 → 返回 true
|
|
101
|
+
* - 已锁定且来源不同 → 返回 false(调用方应跳过)
|
|
102
|
+
*/
|
|
103
|
+
private acquireCallbackLock;
|
|
104
|
+
/** 成功发送的流式分片数或媒体数(用于 onDeliver 互斥判断 + 降级判断) */
|
|
105
|
+
private sentStreamChunkCount;
|
|
106
|
+
/** 是否成功发送过至少一个媒体文件 */
|
|
107
|
+
private sentMediaCount;
|
|
108
|
+
private startingPromise;
|
|
109
|
+
private flush;
|
|
110
|
+
private throttleMs;
|
|
111
|
+
private deps;
|
|
112
|
+
constructor(deps: StreamingControllerDeps);
|
|
113
|
+
get isTerminalPhase(): boolean;
|
|
114
|
+
get currentPhase(): StreamingPhase;
|
|
115
|
+
/**
|
|
116
|
+
* 是否应降级到非流式(普通消息)发送
|
|
117
|
+
*
|
|
118
|
+
* 条件:流式会话进入终态,且从未成功发出过任何一个流式分片或媒体
|
|
119
|
+
*/
|
|
120
|
+
get shouldFallbackToStatic(): boolean;
|
|
121
|
+
/** debug 用:暴露发送计数给 gateway 日志 */
|
|
122
|
+
get sentChunkCount_debug(): number;
|
|
123
|
+
private transition;
|
|
124
|
+
private onEnterTerminalPhase;
|
|
125
|
+
private get prefix();
|
|
126
|
+
private logInfo;
|
|
127
|
+
private logError;
|
|
128
|
+
private logWarn;
|
|
129
|
+
private logDebug;
|
|
130
|
+
/**
|
|
131
|
+
* 处理 onPartialReply 回调(流式文本全量更新)
|
|
132
|
+
*
|
|
133
|
+
* ★ 通过 Promise 链严格串行化:前一次处理完成后才执行下一次,
|
|
134
|
+
* 避免并发交叉导致的状态不一致。
|
|
135
|
+
*
|
|
136
|
+
* payload.text 是从头到尾的完整当前文本(每次回调都是全量)。
|
|
137
|
+
* 核心逻辑:normalize → 更新 lastNormalizedFull → 从 sentIndex 开始 processMediaTags
|
|
138
|
+
*/
|
|
139
|
+
onPartialReply(payload: {
|
|
140
|
+
text?: string;
|
|
141
|
+
}): Promise<void>;
|
|
142
|
+
/** onPartialReply 的实际逻辑(由 _callbackChain 保证串行调用) */
|
|
143
|
+
private _doPartialReply;
|
|
144
|
+
/**
|
|
145
|
+
* 处理 deliver 回调
|
|
146
|
+
*
|
|
147
|
+
* ★ 与 onPartialReply 互斥:首先到达的回调锁定控制权,后到的被忽略。
|
|
148
|
+
*/
|
|
149
|
+
onDeliver(payload: {
|
|
150
|
+
text?: string;
|
|
151
|
+
}): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* 处理 onIdle 回调(分发完成时调用)
|
|
154
|
+
*
|
|
155
|
+
* ★ 挂到 _callbackChain 上,保证在所有 onPartialReply 执行完之后才执行。
|
|
156
|
+
*
|
|
157
|
+
* onIdle 会传入最终的全量文本。如果该文本**包含**之前存储的 lastNormalizedFull,
|
|
158
|
+
* 说明一致,继续处理剩余内容;否则忽略(防止 onIdle 修改文本导致的不一致)。
|
|
159
|
+
*/
|
|
160
|
+
onIdle(payload?: {
|
|
161
|
+
text?: string;
|
|
162
|
+
}): Promise<void>;
|
|
163
|
+
/** onIdle 的实际逻辑(由 _callbackChain 保证在 onPartialReply 之后执行) */
|
|
164
|
+
private _doIdle;
|
|
165
|
+
/**
|
|
166
|
+
* onIdle 的终结逻辑:终结流式会话或标记完成/降级
|
|
167
|
+
*/
|
|
168
|
+
private finalizeOnIdle;
|
|
169
|
+
/**
|
|
170
|
+
* 处理错误
|
|
171
|
+
*/
|
|
172
|
+
onError(err: unknown): Promise<void>;
|
|
173
|
+
/** 标记分发已全部完成 */
|
|
174
|
+
markFullyComplete(): void;
|
|
175
|
+
/** 中止流式消息 */
|
|
176
|
+
abortStreaming(): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* 处理富媒体标签(循环消费模型)
|
|
179
|
+
*
|
|
180
|
+
* 从 sentIndex 开始,对增量文本:
|
|
181
|
+
* 1. 优先找闭合标签 → 终结当前流式 → 同步发媒体 → 推进 sentIndex → reset → 继续
|
|
182
|
+
* 2. 没有闭合标签但有未闭合前缀 → 标签前的安全文本仍需通过流式发送 → 推进 sentIndex → 等待标签闭合
|
|
183
|
+
* 3. 纯文本 → 触发流式发送(performFlush 会动态计算要发的内容)
|
|
184
|
+
*/
|
|
185
|
+
private processMediaTags;
|
|
186
|
+
/**
|
|
187
|
+
* 终结当前流式会话(如果有的话)
|
|
188
|
+
*
|
|
189
|
+
* @param caller 调用者标识(日志用)
|
|
190
|
+
* @param textEndInFull 本次终结需要发送到的全量文本位置(不含)。
|
|
191
|
+
* 终结分片的内容 = lastNormalizedFull.slice(sentIndex, textEndInFull)
|
|
192
|
+
*
|
|
193
|
+
* 逻辑:
|
|
194
|
+
* - 有活跃 streamMsgId → 等待 flush 完成 → 发 DONE 分片终结
|
|
195
|
+
* - 没有 streamMsgId 但有非空白文本 → 启动流式 → 立即终结
|
|
196
|
+
* - 纯空白且无活跃流式 → 不发送
|
|
197
|
+
*/
|
|
198
|
+
private endCurrentStreamIfNeeded;
|
|
199
|
+
/** 临时存储 endCurrentStreamIfNeeded 需要立即发送的文本(用于 doStartStreaming) */
|
|
200
|
+
private _pendingSessionText;
|
|
201
|
+
/**
|
|
202
|
+
* 重置流式会话状态(用于媒体中断后恢复)
|
|
203
|
+
*
|
|
204
|
+
* 只重置会话相关状态,不重置 sentIndex 和 dispatch 标记。
|
|
205
|
+
* 新流式会话从当前 sentIndex 开始(performFlush 动态计算内容)。
|
|
206
|
+
*/
|
|
207
|
+
private resetStreamSession;
|
|
208
|
+
/** 确保流式会话已开始(首次调用创建;并发调用者会等待首次完成) */
|
|
209
|
+
private ensureStreamingStarted;
|
|
210
|
+
/** 实际执行流式启动逻辑 */
|
|
211
|
+
private doStartStreaming;
|
|
212
|
+
/** 发送一个流式分片(不做任何文本修改) */
|
|
213
|
+
private sendStreamChunk;
|
|
214
|
+
/** 执行一次实际的流式内容更新 */
|
|
215
|
+
private performFlush;
|
|
216
|
+
}
|
|
217
|
+
/** 流式媒体发送上下文(由 gateway 注入到 StreamingController) */
|
|
218
|
+
export interface StreamingMediaContext {
|
|
219
|
+
/** 账户信息 */
|
|
220
|
+
account: ResolvedQQBotAccount;
|
|
221
|
+
/** 事件信息 */
|
|
222
|
+
event: {
|
|
223
|
+
type: "c2c" | "group" | "channel";
|
|
224
|
+
senderId: string;
|
|
225
|
+
messageId: string;
|
|
226
|
+
groupOpenid?: string;
|
|
227
|
+
channelId?: string;
|
|
228
|
+
};
|
|
229
|
+
/** 日志 */
|
|
230
|
+
log?: {
|
|
231
|
+
info: (msg: string) => void;
|
|
232
|
+
error: (msg: string) => void;
|
|
233
|
+
debug?: (msg: string) => void;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* 判断是否应该对当前消息使用流式模式
|
|
238
|
+
*
|
|
239
|
+
* 条件:
|
|
240
|
+
* 1. 账户配置 streaming 未显式设为 false(默认启用)
|
|
241
|
+
* 2. 目标类型为 c2c(私聊)—— 流式 API 仅支持 C2C
|
|
242
|
+
*/
|
|
243
|
+
export declare function shouldUseStreaming(account: ResolvedQQBotAccount, targetType: "c2c" | "group" | "channel"): boolean;
|
|
244
|
+
export {};
|