@zaofan/dsh-qqbot 0.6.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.
Files changed (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/README_EN.md +273 -0
  4. package/client/qqbot-settings.js +1463 -0
  5. package/cordis.patch.yml +36 -0
  6. package/dist/api/group-admin.d.ts +112 -0
  7. package/dist/api/group-admin.d.ts.map +1 -0
  8. package/dist/api/group-admin.js +179 -0
  9. package/dist/api/group-admin.js.map +1 -0
  10. package/dist/channel-tools.d.ts +14 -0
  11. package/dist/channel-tools.d.ts.map +1 -0
  12. package/dist/channel-tools.js +880 -0
  13. package/dist/channel-tools.js.map +1 -0
  14. package/dist/commands/help.d.ts +11 -0
  15. package/dist/commands/help.d.ts.map +1 -0
  16. package/dist/commands/help.js +21 -0
  17. package/dist/commands/help.js.map +1 -0
  18. package/dist/commands/index.d.ts +13 -0
  19. package/dist/commands/index.d.ts.map +1 -0
  20. package/dist/commands/index.js +29 -0
  21. package/dist/commands/index.js.map +1 -0
  22. package/dist/commands/misc.d.ts +14 -0
  23. package/dist/commands/misc.d.ts.map +1 -0
  24. package/dist/commands/misc.js +39 -0
  25. package/dist/commands/misc.js.map +1 -0
  26. package/dist/commands/model.d.ts +7 -0
  27. package/dist/commands/model.d.ts.map +1 -0
  28. package/dist/commands/model.js +58 -0
  29. package/dist/commands/model.js.map +1 -0
  30. package/dist/commands/session.d.ts +10 -0
  31. package/dist/commands/session.d.ts.map +1 -0
  32. package/dist/commands/session.js +26 -0
  33. package/dist/commands/session.js.map +1 -0
  34. package/dist/commands/status.d.ts +8 -0
  35. package/dist/commands/status.d.ts.map +1 -0
  36. package/dist/commands/status.js +24 -0
  37. package/dist/commands/status.js.map +1 -0
  38. package/dist/commands/types.d.ts +12 -0
  39. package/dist/commands/types.d.ts.map +1 -0
  40. package/dist/commands/types.js +2 -0
  41. package/dist/commands/types.js.map +1 -0
  42. package/dist/config.d.ts +205 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +203 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/features/chat-ledger.d.ts +53 -0
  47. package/dist/features/chat-ledger.d.ts.map +1 -0
  48. package/dist/features/chat-ledger.js +185 -0
  49. package/dist/features/chat-ledger.js.map +1 -0
  50. package/dist/features/face-tags.d.ts +17 -0
  51. package/dist/features/face-tags.d.ts.map +1 -0
  52. package/dist/features/face-tags.js +79 -0
  53. package/dist/features/face-tags.js.map +1 -0
  54. package/dist/features/group-join-request.d.ts +65 -0
  55. package/dist/features/group-join-request.d.ts.map +1 -0
  56. package/dist/features/group-join-request.js +190 -0
  57. package/dist/features/group-join-request.js.map +1 -0
  58. package/dist/features/history-store.d.ts +8 -0
  59. package/dist/features/history-store.d.ts.map +1 -0
  60. package/dist/features/history-store.js +24 -0
  61. package/dist/features/history-store.js.map +1 -0
  62. package/dist/features/qq-approval.d.ts +78 -0
  63. package/dist/features/qq-approval.d.ts.map +1 -0
  64. package/dist/features/qq-approval.js +165 -0
  65. package/dist/features/qq-approval.js.map +1 -0
  66. package/dist/features/schedule-store.d.ts +62 -0
  67. package/dist/features/schedule-store.d.ts.map +1 -0
  68. package/dist/features/schedule-store.js +253 -0
  69. package/dist/features/schedule-store.js.map +1 -0
  70. package/dist/features/scheduler.d.ts +9 -0
  71. package/dist/features/scheduler.d.ts.map +1 -0
  72. package/dist/features/scheduler.js +146 -0
  73. package/dist/features/scheduler.js.map +1 -0
  74. package/dist/features/sticker-gate.d.ts +90 -0
  75. package/dist/features/sticker-gate.d.ts.map +1 -0
  76. package/dist/features/sticker-gate.js +391 -0
  77. package/dist/features/sticker-gate.js.map +1 -0
  78. package/dist/features/sticker-store.d.ts +158 -0
  79. package/dist/features/sticker-store.d.ts.map +1 -0
  80. package/dist/features/sticker-store.js +676 -0
  81. package/dist/features/sticker-store.js.map +1 -0
  82. package/dist/features/sticker-tagger.d.ts +10 -0
  83. package/dist/features/sticker-tagger.d.ts.map +1 -0
  84. package/dist/features/sticker-tagger.js +139 -0
  85. package/dist/features/sticker-tagger.js.map +1 -0
  86. package/dist/gateway/bootstrap.d.ts +12 -0
  87. package/dist/gateway/bootstrap.d.ts.map +1 -0
  88. package/dist/gateway/bootstrap.js +346 -0
  89. package/dist/gateway/bootstrap.js.map +1 -0
  90. package/dist/gateway/debounce.d.ts +40 -0
  91. package/dist/gateway/debounce.d.ts.map +1 -0
  92. package/dist/gateway/debounce.js +262 -0
  93. package/dist/gateway/debounce.js.map +1 -0
  94. package/dist/gateway/index.d.ts +3 -0
  95. package/dist/gateway/index.d.ts.map +1 -0
  96. package/dist/gateway/index.js +3 -0
  97. package/dist/gateway/index.js.map +1 -0
  98. package/dist/gateway/middleware-setup.d.ts +12 -0
  99. package/dist/gateway/middleware-setup.d.ts.map +1 -0
  100. package/dist/gateway/middleware-setup.js +156 -0
  101. package/dist/gateway/middleware-setup.js.map +1 -0
  102. package/dist/index.d.ts +14 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +187 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/middleware/attachment.d.ts +11 -0
  107. package/dist/middleware/attachment.d.ts.map +1 -0
  108. package/dist/middleware/attachment.js +17 -0
  109. package/dist/middleware/attachment.js.map +1 -0
  110. package/dist/middleware/media-history.d.ts +30 -0
  111. package/dist/middleware/media-history.d.ts.map +1 -0
  112. package/dist/middleware/media-history.js +54 -0
  113. package/dist/middleware/media-history.js.map +1 -0
  114. package/dist/middleware/sticker-capture.d.ts +17 -0
  115. package/dist/middleware/sticker-capture.d.ts.map +1 -0
  116. package/dist/middleware/sticker-capture.js +97 -0
  117. package/dist/middleware/sticker-capture.js.map +1 -0
  118. package/dist/model/index.d.ts +10 -0
  119. package/dist/model/index.d.ts.map +1 -0
  120. package/dist/model/index.js +9 -0
  121. package/dist/model/index.js.map +1 -0
  122. package/dist/model/model-resolver.d.ts +88 -0
  123. package/dist/model/model-resolver.d.ts.map +1 -0
  124. package/dist/model/model-resolver.js +151 -0
  125. package/dist/model/model-resolver.js.map +1 -0
  126. package/dist/model/prefs-store.d.ts +24 -0
  127. package/dist/model/prefs-store.d.ts.map +1 -0
  128. package/dist/model/prefs-store.js +126 -0
  129. package/dist/model/prefs-store.js.map +1 -0
  130. package/dist/model/settings-reader.d.ts +19 -0
  131. package/dist/model/settings-reader.d.ts.map +1 -0
  132. package/dist/model/settings-reader.js +76 -0
  133. package/dist/model/settings-reader.js.map +1 -0
  134. package/dist/model/types.d.ts +15 -0
  135. package/dist/model/types.d.ts.map +1 -0
  136. package/dist/model/types.js +5 -0
  137. package/dist/model/types.js.map +1 -0
  138. package/dist/session/idle-evictor.d.ts +18 -0
  139. package/dist/session/idle-evictor.d.ts.map +1 -0
  140. package/dist/session/idle-evictor.js +31 -0
  141. package/dist/session/idle-evictor.js.map +1 -0
  142. package/dist/session/index.d.ts +9 -0
  143. package/dist/session/index.d.ts.map +1 -0
  144. package/dist/session/index.js +8 -0
  145. package/dist/session/index.js.map +1 -0
  146. package/dist/session/session-manager.d.ts +97 -0
  147. package/dist/session/session-manager.d.ts.map +1 -0
  148. package/dist/session/session-manager.js +624 -0
  149. package/dist/session/session-manager.js.map +1 -0
  150. package/dist/session/types.d.ts +130 -0
  151. package/dist/session/types.d.ts.map +1 -0
  152. package/dist/session/types.js +2 -0
  153. package/dist/session/types.js.map +1 -0
  154. package/dist/session/workspace-attach.d.ts +22 -0
  155. package/dist/session/workspace-attach.d.ts.map +1 -0
  156. package/dist/session/workspace-attach.js +21 -0
  157. package/dist/session/workspace-attach.js.map +1 -0
  158. package/dist/setup.d.ts +25 -0
  159. package/dist/setup.d.ts.map +1 -0
  160. package/dist/setup.js +198 -0
  161. package/dist/setup.js.map +1 -0
  162. package/dist/shared/index.d.ts +7 -0
  163. package/dist/shared/index.d.ts.map +1 -0
  164. package/dist/shared/index.js +7 -0
  165. package/dist/shared/index.js.map +1 -0
  166. package/dist/shared/scope.d.ts +14 -0
  167. package/dist/shared/scope.d.ts.map +1 -0
  168. package/dist/shared/scope.js +12 -0
  169. package/dist/shared/scope.js.map +1 -0
  170. package/dist/shared/send-helper.d.ts +11 -0
  171. package/dist/shared/send-helper.d.ts.map +1 -0
  172. package/dist/shared/send-helper.js +13 -0
  173. package/dist/shared/send-helper.js.map +1 -0
  174. package/dist/shared/utils.d.ts +28 -0
  175. package/dist/shared/utils.d.ts.map +1 -0
  176. package/dist/shared/utils.js +143 -0
  177. package/dist/shared/utils.js.map +1 -0
  178. package/dist/transport/attachment.d.ts +18 -0
  179. package/dist/transport/attachment.d.ts.map +1 -0
  180. package/dist/transport/attachment.js +106 -0
  181. package/dist/transport/attachment.js.map +1 -0
  182. package/dist/transport/chunker.d.ts +14 -0
  183. package/dist/transport/chunker.d.ts.map +1 -0
  184. package/dist/transport/chunker.js +78 -0
  185. package/dist/transport/chunker.js.map +1 -0
  186. package/dist/transport/events.d.ts +80 -0
  187. package/dist/transport/events.d.ts.map +1 -0
  188. package/dist/transport/events.js +62 -0
  189. package/dist/transport/events.js.map +1 -0
  190. package/dist/transport/inbound.d.ts +8 -0
  191. package/dist/transport/inbound.d.ts.map +1 -0
  192. package/dist/transport/inbound.js +292 -0
  193. package/dist/transport/inbound.js.map +1 -0
  194. package/dist/transport/index.d.ts +10 -0
  195. package/dist/transport/index.d.ts.map +1 -0
  196. package/dist/transport/index.js +10 -0
  197. package/dist/transport/index.js.map +1 -0
  198. package/dist/transport/inject-rules.d.ts +24 -0
  199. package/dist/transport/inject-rules.d.ts.map +1 -0
  200. package/dist/transport/inject-rules.js +72 -0
  201. package/dist/transport/inject-rules.js.map +1 -0
  202. package/dist/transport/outbound-buffer.d.ts +56 -0
  203. package/dist/transport/outbound-buffer.d.ts.map +1 -0
  204. package/dist/transport/outbound-buffer.js +131 -0
  205. package/dist/transport/outbound-buffer.js.map +1 -0
  206. package/dist/transport/outbound.d.ts +30 -0
  207. package/dist/transport/outbound.d.ts.map +1 -0
  208. package/dist/transport/outbound.js +139 -0
  209. package/dist/transport/outbound.js.map +1 -0
  210. package/dist/transport/rich-media.d.ts +41 -0
  211. package/dist/transport/rich-media.d.ts.map +1 -0
  212. package/dist/transport/rich-media.js +104 -0
  213. package/dist/transport/rich-media.js.map +1 -0
  214. package/dist/transport/streaming-writer.d.ts +47 -0
  215. package/dist/transport/streaming-writer.d.ts.map +1 -0
  216. package/dist/transport/streaming-writer.js +110 -0
  217. package/dist/transport/streaming-writer.js.map +1 -0
  218. package/dist/transport/tool-presenter.d.ts +38 -0
  219. package/dist/transport/tool-presenter.d.ts.map +1 -0
  220. package/dist/transport/tool-presenter.js +121 -0
  221. package/dist/transport/tool-presenter.js.map +1 -0
  222. package/dist/types.d.ts +45 -0
  223. package/dist/types.d.ts.map +1 -0
  224. package/dist/types.js +5 -0
  225. package/dist/types.js.map +1 -0
  226. package/package.json +106 -0
  227. package/settings-host.js +927 -0
@@ -0,0 +1,292 @@
1
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
2
+ import { clearGroupHistory } from '../features/history-store.js';
3
+ import { applyInjectRules } from './inject-rules.js';
4
+ // ── 主处理函数 ──
5
+ /**
6
+ * 处理 QQ 入站消息(已经过 SDK 中间件链)
7
+ */
8
+ export async function handleInbound(rawMsg, manager, config, logger, state) {
9
+ const msg = rawMsg;
10
+ const mwState = (state ?? {});
11
+ const scope = msg.kind === 'group' ? 'group' : 'c2c';
12
+ const peerId = scope === 'group' ? (msg.groupOpenid ?? msg.senderId) : msg.senderId;
13
+ const replyTarget = {
14
+ scope,
15
+ targetId: peerId,
16
+ msgId: msg.messageId,
17
+ };
18
+ // ── 读取中间件已下载的文件(attachmentProcessor 写入 state.downloadedFiles) ──
19
+ const downloaded = mwState.downloadedFiles ?? [];
20
+ // ── 组装 agentBody(对齐 openclaw-qqbot body-assembler) ──
21
+ let agentBody = assembleAgentBody(msg, mwState, scope, logger, downloaded);
22
+ if (!agentBody)
23
+ return;
24
+ // 条件注入规则(配置化): 消息含图/链接/自定义条件时追加 [系统提示]。
25
+ // 含内置读图兜底(未配置 hasImage 规则时自动生效, 行为与旧写死版一致)。
26
+ // ⚠️ 本地手改功能(曾被重编译冲掉),改完务必保持 src 与部署 dist 同步。
27
+ agentBody = applyInjectRules(agentBody, msg, config.injectRules, logger);
28
+ // 群聊时间戳(原"群守则"拼接位): 守则已迁 systemPrompt.section(session-manager 装配期注册,
29
+ // 每请求进 system, 不再每轮塞 user 历史); 此处改为注入当前系统时间, 让 AI 每轮知道日期/星期/时刻。
30
+ if (scope === 'group') {
31
+ const _now = new Date();
32
+ const _p = (n) => String(n).padStart(2, '0');
33
+ const _wd = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][_now.getDay()];
34
+ agentBody = `[当前时间 ${_now.getFullYear()}-${_p(_now.getMonth() + 1)}-${_p(_now.getDate())} ${_wd} ${_p(_now.getHours())}:${_p(_now.getMinutes())}]\n\n${agentBody}`;
35
+ }
36
+ logger.info(`Processing: scope=${scope} peerId=${peerId} body="${agentBody.slice(0, 200)}"`);
37
+ // ── 获取或创建会话 ──
38
+ let record;
39
+ try {
40
+ record = await manager.getOrCreate(scope, peerId, msg.senderId, replyTarget);
41
+ }
42
+ catch (err) {
43
+ logger.error(`ERROR creating session: ${err instanceof Error ? err.message : String(err)}`);
44
+ return;
45
+ }
46
+ // ── 工具自愈:setup 竞态漏装时补注册通道工具(幂等,几乎零开销) ──
47
+ try {
48
+ await manager.ensureChannelTools(record);
49
+ }
50
+ catch (err) {
51
+ logger.debug(`ensureChannelTools error: ${err instanceof Error ? err.message : String(err)}`);
52
+ }
53
+ // ── qqChannel 上下文自愈: 恢复会话可能没 provide, 工具按本实例路由图库/定时需要它(幂等) ──
54
+ try {
55
+ await manager.ensureChannelContext(record);
56
+ }
57
+ catch (err) {
58
+ logger.debug(`ensureChannelContext error: ${err instanceof Error ? err.message : String(err)}`);
59
+ }
60
+ // ── 守则/身份 context 注册自愈(幂等; 重启恢复会话也覆盖, 保证 systemPrompt.context 注入生效) ──
61
+ try {
62
+ await manager.ensureGroupRules(record);
63
+ }
64
+ catch (err) {
65
+ logger.debug(`ensureGroupRules error: ${err instanceof Error ? err.message : String(err)}`);
66
+ }
67
+ // ── 构建 UserMessage → followup ──
68
+ const content = [{ type: 'text', text: agentBody }];
69
+ const message = createUserMessage({
70
+ content,
71
+ source: { kind: 'user' },
72
+ });
73
+ record.agent.followup(message);
74
+ logger.info(`→ followup sent: key=${scope}:${peerId}`);
75
+ // 群消息回复后清空历史缓存(避免下次 @ 时重复组包,对齐 openclaw-qqbot dispatch)
76
+ if (scope === 'group') {
77
+ clearGroupHistory(config.appId, msg.groupOpenid ?? msg.senderId);
78
+ }
79
+ }
80
+ // ══════════════════════════════════════════════════════════════
81
+ // Body Assembly(对齐 openclaw-qqbot 5 层组装)
82
+ // ══════════════════════════════════════════════════════════════
83
+ /**
84
+ * 组装 agentBody — AI 实际看到的完整上下文
85
+ */
86
+ function assembleAgentBody(msg, state, scope, logger, downloaded) {
87
+ const userContent = buildUserContent(msg, state, logger);
88
+ if (!userContent && (!msg.attachments || msg.attachments.length === 0))
89
+ return null;
90
+ const quotePart = buildQuotePart(state.quote);
91
+ const isGroup = scope === 'group';
92
+ const wasMentioned = state.mention?.wasMentioned ?? false;
93
+ const batchDispatch = state.batchDispatch === true;
94
+ const userMessage = buildUserMessage(userContent, quotePart, msg.senderId, msg.senderName, isGroup, wasMentioned);
95
+ const dynamicCtx = buildDynamicCtx(msg, state, downloaded);
96
+ const base = dynamicCtx ? `${dynamicCtx}${userMessage}` : userMessage;
97
+ const agentBody = buildAgentBody(base, state.history, isGroup, wasMentioned, batchDispatch);
98
+ return agentBody;
99
+ }
100
+ /**
101
+ * Layer 1: 用户文本内容 + 语音 + 附件
102
+ */
103
+ function buildUserContent(msg, state, logger) {
104
+ const parts = [];
105
+ const text = (msg.content ?? '').trim();
106
+ if (text) {
107
+ parts.push(text);
108
+ }
109
+ const voiceTexts = extractVoiceTexts(msg.attachments, state.processedAttachments, logger);
110
+ if (voiceTexts.length > 0) {
111
+ for (const vt of voiceTexts) {
112
+ const durationTag = vt.duration ? ` (${vt.duration}s)` : '';
113
+ parts.push(`[Voice message${durationTag}] ${vt.text}`);
114
+ }
115
+ }
116
+ const otherAttachments = describeAttachments(msg.attachments, state.processedAttachments);
117
+ if (otherAttachments) {
118
+ parts.push(otherAttachments);
119
+ }
120
+ return parts.join('\n');
121
+ }
122
+ /**
123
+ * Layer 2: 引用消息块
124
+ */
125
+ function buildQuotePart(quote) {
126
+ if (!quote?.text && !quote?.entry?.content)
127
+ return '';
128
+ const quoteText = quote.text || quote.entry?.content || 'Original content unavailable';
129
+ return `[Quoted message begins]\n${quoteText}\n[Quoted message ends]\n[Current message]\n`;
130
+ }
131
+ /**
132
+ * Layer 3: 带发送者标签的用户消息
133
+ */
134
+ function buildUserMessage(userContent, quotePart, senderId, senderName, isGroup, wasMentioned) {
135
+ if (!isGroup) {
136
+ return `${quotePart}${userContent}`;
137
+ }
138
+ const mentionTag = wasMentioned ? ' (@you)' : '';
139
+ const displayName = senderName ?? shortSenderId(senderId);
140
+ const senderTag = `[${displayName} (${senderId})]`;
141
+ return `${quotePart}${senderTag} ${userContent}${mentionTag}`;
142
+ }
143
+ /**
144
+ * Layer 4: 媒体元数据上下文
145
+ */
146
+ function buildDynamicCtx(msg, state, downloaded) {
147
+ const lines = [];
148
+ if (!msg.attachments || msg.attachments.length === 0)
149
+ return '';
150
+ const images = msg.attachments.filter(a => a.content_type === 'image');
151
+ if (images.length > 0) {
152
+ const urls = images.map(a => a.url).filter(Boolean);
153
+ if (urls.length > 0) {
154
+ lines.push(`- Images: ${urls.join(', ')}`);
155
+ }
156
+ }
157
+ const voices = msg.attachments.filter(a => a.content_type === 'voice');
158
+ if (voices.length > 0) {
159
+ const asrTexts = voices.map(a => a.asr_refer_text).filter(Boolean);
160
+ if (asrTexts.length > 0) {
161
+ lines.push(`- ASR: ${asrTexts.join(' | ')}`);
162
+ }
163
+ else {
164
+ // 无 ASR 识别结果时才带链接(纯文本模型无法消费音频,链接无意义)
165
+ const urls = voices.map(a => a.url).filter(Boolean);
166
+ if (urls.length > 0) {
167
+ lines.push(`- Voice: ${urls.join(', ')}`);
168
+ }
169
+ }
170
+ }
171
+ const videos = msg.attachments.filter(a => a.content_type === 'video');
172
+ if (videos.length > 0) {
173
+ lines.push(`- Videos: ${videos.map(a => a.filename).join(', ')}`);
174
+ }
175
+ const files = msg.attachments.filter(a => a.content_type === 'file');
176
+ if (files.length > 0) {
177
+ for (const file of files) {
178
+ const d = downloaded.find(x => x.filename === file.filename);
179
+ if (d) {
180
+ lines.push(`- File: ${file.filename} → ${d.displayPath}`);
181
+ }
182
+ else {
183
+ lines.push(`- File: ${file.filename} (${formatFileSize(file.size)})`);
184
+ }
185
+ }
186
+ }
187
+ // 兜底:把所有带 URL 的附件链接暴露给 AI(未归类为 image 的也带上),方便桥接工具直接看图/附件
188
+ const otherUrls = msg.attachments
189
+ .filter(a => a.content_type !== 'image' && !!a.url)
190
+ .map(a => a.url);
191
+ if (otherUrls.length > 0) {
192
+ lines.push(`- Attachment URLs: ${otherUrls.join(', ')}`);
193
+ }
194
+ if (lines.length === 0)
195
+ return '';
196
+ const quoteAttachments = state.quote?.attachments;
197
+ if (quoteAttachments && quoteAttachments.length > 0) {
198
+ lines.push('[Reference attachments]');
199
+ for (const qa of quoteAttachments) {
200
+ const label = qa.asrText ? `Voice: ${qa.asrText}` : (qa.filename ?? qa.contentType ?? 'attachment');
201
+ lines.push(` - ${label}`);
202
+ }
203
+ }
204
+ return lines.join('\n') + '\n\n';
205
+ }
206
+ /**
207
+ * Layer 5: 最终 agentBody 拼合
208
+ */
209
+ function buildAgentBody(base, history, isGroup, wasMentioned, batchDispatch = false) {
210
+ // 群内:真实 @ 或冷却派发(batchDispatch)时,把累积的群历史打包给 AI(后者不打假 @you 标签)
211
+ const includeHistory = isGroup && (wasMentioned || batchDispatch) && !!history && history.length > 0;
212
+ if (!includeHistory) {
213
+ return base;
214
+ }
215
+ const historyLines = history.map(h => {
216
+ const name = h.senderName ?? shortSenderId(h.senderId);
217
+ return `[${name} (${h.senderId})] ${h.content}`;
218
+ });
219
+ return [
220
+ '[Chat history begins]',
221
+ ...historyLines,
222
+ '',
223
+ '[Chat history ends]',
224
+ '[Current message]',
225
+ base,
226
+ ].join('\n');
227
+ }
228
+ function extractVoiceTexts(attachments, processed, _logger) {
229
+ const results = [];
230
+ if (processed) {
231
+ for (const pa of processed) {
232
+ if (pa.type === 'voice' && pa.voiceText) {
233
+ results.push({
234
+ text: pa.voiceText,
235
+ duration: pa.duration,
236
+ source: pa.voiceSource ?? 'stt',
237
+ });
238
+ }
239
+ }
240
+ }
241
+ if (results.length === 0 && attachments) {
242
+ for (const att of attachments) {
243
+ if (att.content_type === 'voice' && att.asr_refer_text) {
244
+ results.push({
245
+ text: att.asr_refer_text.trim(),
246
+ source: 'asr',
247
+ });
248
+ }
249
+ }
250
+ }
251
+ return results;
252
+ }
253
+ function describeAttachments(attachments, _processed) {
254
+ if (!attachments || attachments.length === 0)
255
+ return '';
256
+ const parts = [];
257
+ for (const att of attachments) {
258
+ switch (att.content_type) {
259
+ case 'voice':
260
+ break;
261
+ case 'image': {
262
+ const dim = att.width && att.height ? ` ${att.width}×${att.height}` : '';
263
+ parts.push(`[Image: ${att.filename}${dim}]`);
264
+ break;
265
+ }
266
+ case 'video':
267
+ parts.push(`[Video: ${att.filename}]`);
268
+ break;
269
+ case 'file':
270
+ parts.push(`[File: ${att.filename} (${formatFileSize(att.size)})]`);
271
+ break;
272
+ default:
273
+ parts.push(`[Attachment: ${att.filename ?? att.content_type}${att.url ? ` -> ${att.url}` : ''}]`);
274
+ break;
275
+ }
276
+ }
277
+ return parts.join('\n');
278
+ }
279
+ function formatFileSize(bytes) {
280
+ if (bytes < 1024)
281
+ return `${bytes}B`;
282
+ if (bytes < 1024 * 1024)
283
+ return `${(bytes / 1024).toFixed(1)}KB`;
284
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
285
+ }
286
+ /** 发送者短标识长度(openid 前 N 位,无昵称时兜底) */
287
+ const SENDER_SHORT_ID_LEN = 8;
288
+ /** 无昵称时用 openid 前 N 位作为匿名标识 */
289
+ function shortSenderId(senderId) {
290
+ return senderId.slice(0, SENDER_SHORT_ID_LEN);
291
+ }
292
+ //# sourceMappingURL=inbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.js","sourceRoot":"","sources":["../../src/transport/inbound.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAKzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AA6DrD,cAAc;AAEd;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAe,EACf,OAAuB,EACvB,MAAqB,EACrB,MAAc,EACd,KAA+B;IAE/B,MAAM,GAAG,GAAG,MAA0B,CAAC;IACvC,MAAM,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE,CAAoB,CAAC;IAEjD,MAAM,KAAK,GAAc,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAChE,MAAM,MAAM,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;IAEpF,MAAM,WAAW,GAAgB;QAC/B,KAAK;QACL,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,GAAG,CAAC,SAAS;KACrB,CAAC;IAEF,kEAAkE;IAClE,MAAM,UAAU,GAAG,OAAO,CAAC,eAAe,IAAI,EAAE,CAAC;IAEjD,uDAAuD;IACvD,IAAI,SAAS,GAAG,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAE3E,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,wCAAwC;IACxC,4CAA4C;IAC5C,6CAA6C;IAC7C,SAAS,GAAG,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAEzE,qEAAqE;IACrE,gEAAgE;IAChE,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACtE,SAAS,GAAG,SAAS,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,QAAQ,SAAS,EAAE,CAAC;IACrK,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,qBAAqB,KAAK,WAAW,MAAM,UAAU,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAE7F,gBAAgB;IAChB,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC/E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,2BAA2B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5F,OAAO;IACT,CAAC;IAED,0CAA0C;IAC1C,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,+DAA+D;IAC/D,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClG,CAAC;IACD,wEAAwE;IACxE,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,2BAA2B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,kCAAkC;IAClC,MAAM,OAAO,GAAmB,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAE7E,MAAM,OAAO,GAAG,iBAAiB,CAAC;QAChC,OAAO;QACP,MAAM,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE;KAClC,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,CAAC,IAAI,CAAC,wBAAwB,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC;IAEvD,wDAAwD;IACxD,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACtB,iBAAiB,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,yCAAyC;AACzC,iEAAiE;AAEjE;;GAEG;AACH,SAAS,iBAAiB,CACxB,GAAqB,EACrB,KAAsB,EACtB,KAAgB,EAChB,MAAc,EACd,UAA4B;IAE5B,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAEzD,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpF,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,KAAK,KAAK,OAAO,CAAC;IAClC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,EAAE,YAAY,IAAI,KAAK,CAAC;IAC1D,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IACnD,MAAM,WAAW,GAAG,gBAAgB,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;IAElH,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IAE3D,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;IACtE,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;IAE5F,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CAAC,GAAqB,EAAE,KAAsB,EAAE,MAAc;IACrF,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,IAAI,IAAI,EAAE,CAAC;QACT,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;IAC1F,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;YAC5B,MAAM,WAAW,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,KAAK,CAAC,IAAI,CAAC,iBAAiB,WAAW,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC1F,IAAI,gBAAgB,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,KAAqB;IAC3C,IAAI,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO;QAAE,OAAO,EAAE,CAAC;IAEtD,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,IAAI,8BAA8B,CAAC;IAEvF,OAAO,4BAA4B,SAAS,8CAA8C,CAAC;AAC7F,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CACvB,WAAmB,EACnB,SAAiB,EACjB,QAAgB,EAChB,UAA8B,EAC9B,OAAgB,EAChB,YAAqB;IAErB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,GAAG,SAAS,GAAG,WAAW,EAAE,CAAC;IACtC,CAAC;IAED,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,MAAM,WAAW,GAAG,UAAU,IAAI,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAI,WAAW,KAAK,QAAQ,IAAI,CAAC;IACnD,OAAO,GAAG,SAAS,GAAG,SAAS,IAAI,WAAW,GAAG,UAAU,EAAE,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,GAAqB,EAAE,KAAsB,EAAE,UAA4B;IAClG,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEhE,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,OAAO,CAAC,CAAC;IACvE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,OAAO,CAAC,CAAC;IACvE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACnE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,UAAU,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/C,CAAC;aAAM,CAAC;YACN,qCAAqC;YACrC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACpD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,KAAK,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,OAAO,CAAC,CAAC;IACvE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,MAAM,CAAC,CAAC;IACrE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7D,IAAI,CAAC,EAAE,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YAC5D,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,KAAK,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW;SAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;SAClD,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,sBAAsB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAElC,MAAM,gBAAgB,GAAG,KAAK,CAAC,KAAK,EAAE,WAAW,CAAC;IAClD,IAAI,gBAAgB,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACtC,KAAK,MAAM,EAAE,IAAI,gBAAgB,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,WAAW,IAAI,YAAY,CAAC,CAAC;YACpG,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CACrB,IAAY,EACZ,OAAmC,EACnC,OAAgB,EAChB,YAAqB,EACrB,aAAa,GAAG,KAAK;IAErB,6DAA6D;IAC7D,MAAM,cAAc,GAAG,OAAO,IAAI,CAAC,YAAY,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IACrG,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QACnC,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,IAAI,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACvD,OAAO,IAAI,IAAI,KAAK,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,uBAAuB;QACvB,GAAG,YAAY;QACf,EAAE;QACF,qBAAqB;QACrB,mBAAmB;QACnB,IAAI;KACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAYD,SAAS,iBAAiB,CACxB,WAA6B,EAC7B,SAAiC,EACjC,OAAgB;IAEhB,MAAM,OAAO,GAAgB,EAAE,CAAC;IAEhC,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;YAC3B,IAAI,EAAE,CAAC,IAAI,KAAK,OAAO,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC;gBACxC,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,EAAE,CAAC,SAAS;oBAClB,QAAQ,EAAE,EAAE,CAAC,QAAQ;oBACrB,MAAM,EAAE,EAAE,CAAC,WAAW,IAAI,KAAK;iBAChC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,IAAI,GAAG,CAAC,YAAY,KAAK,OAAO,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;gBACvD,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE;oBAC/B,MAAM,EAAE,KAAK;iBACd,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,mBAAmB,CAC1B,WAA6B,EAC7B,UAAkC;IAElC,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAExD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,QAAQ,GAAG,CAAC,YAAY,EAAE,CAAC;YACzB,KAAK,OAAO;gBACV,MAAM;YACR,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzE,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,QAAQ,GAAG,GAAG,GAAG,CAAC,CAAC;gBAC7C,MAAM;YACR,CAAC;YACD,KAAK,OAAO;gBACV,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;gBACvC,MAAM;YACR,KAAK,MAAM;gBACT,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,QAAQ,KAAK,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpE,MAAM;YACR;gBACE,KAAK,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBAClG,MAAM;QACV,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,GAAG,CAAC;IACrC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,oCAAoC;AACpC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,+BAA+B;AAC/B,SAAS,aAAa,CAAC,QAAgB;IACrC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 传输层
3
+ *
4
+ * 协议对接:QQ 消息入站 / 出站 / Markdown 切分。
5
+ */
6
+ export { handleInbound } from './inbound.js';
7
+ export { createOutboundHandler } from './outbound.js';
8
+ export { OutboundBuffer, type QQBotSender } from './outbound-buffer.js';
9
+ export { chunkMarkdownText } from './chunker.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 传输层
3
+ *
4
+ * 协议对接:QQ 消息入站 / 出站 / Markdown 切分。
5
+ */
6
+ export { handleInbound } from './inbound.js';
7
+ export { createOutboundHandler } from './outbound.js';
8
+ export { OutboundBuffer } from './outbound-buffer.js';
9
+ export { chunkMarkdownText } from './chunker.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,cAAc,EAAoB,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * inject-rules.ts — 条件注入规则引擎(配置化)
3
+ *
4
+ * 用户可配: 当入站消息满足条件(含图片/含链接/正文匹配正则或关键词)时,
5
+ * 在 agentBody 末尾追加一行 "[系统提示] <prompt>" 自定义指令。
6
+ * 取代原先 inbound.ts 写死的"读图提示", 并作为内置兜底规则保留默认行为。
7
+ *
8
+ * ⚠️ 本地手改功能(fork 新增, 配置见 config.injectRules):
9
+ * 维护清单见工作区根《插件改动维护注意事项.md》。
10
+ */
11
+ import type { InjectRuleConfig } from '../config.js';
12
+ import type { Logger, RawAttachment } from '../types.js';
13
+ /** 消息最小形状(只读所需) */
14
+ interface RuleMsg {
15
+ content?: string;
16
+ attachments?: RawAttachment[];
17
+ }
18
+ /**
19
+ * 对组装好的 agentBody 应用全部启用的注入规则, 返回新 body。
20
+ * 规则数组里没有任何 enabled 的 hasImage 规则时, 自动追加内置读图兜底(默认行为与旧版一致)。
21
+ */
22
+ export declare function applyInjectRules(body: string, msg: RuleMsg, rules: InjectRuleConfig[] | undefined, logger?: Logger): string;
23
+ export {};
24
+ //# sourceMappingURL=inject-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject-rules.d.ts","sourceRoot":"","sources":["../../src/transport/inject-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKzD,mBAAmB;AACnB,UAAU,OAAO;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,aAAa,EAAE,CAAC;CAC/B;AAiDD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,gBAAgB,EAAE,GAAG,SAAS,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAmBR"}
@@ -0,0 +1,72 @@
1
+ /** 单轮累计注入预算(字符), 防爆上下文 */
2
+ const BUDGET_CHARS = 1500;
3
+ /** 原写死读图提示(内置兜底规则: 用户未配任何 enabled 的 hasImage 规则时自动追加) */
4
+ const LEGACY_IMAGE_RULE = {
5
+ id: 'builtin-image',
6
+ enabled: true,
7
+ conditions: {
8
+ hasImage: true,
9
+ hasLink: false,
10
+ contentRegex: '',
11
+ contentKeywords: [],
12
+ matchScope: 'any',
13
+ },
14
+ prompt: '上方消息包含图片链接(URL),请直接把该URL传给桥接视觉工具(modlens_read_image / analyze_image)看图并描述内容,不要只复述链接。',
15
+ };
16
+ /** 消息是否含附件图片(URL 兜底: content_type 是 MIME 如 image/jpeg) */
17
+ function hasImage(msg) {
18
+ return (msg.attachments ?? []).some(a => (a.url && String(a.content_type ?? '').toLowerCase().includes('image')) || (a.url && /\.(jpe?g|png|gif|webp|bmp)(\?|$)/i.test(a.url)));
19
+ }
20
+ /** 消息正文是否含 http(s) 链接 */
21
+ function hasLink(text) {
22
+ return /https?:\/\/|www\./i.test(text);
23
+ }
24
+ /** 单条规则命中判定(正则编译失败降级 false) */
25
+ function matchRule(rule, text, msg, logger) {
26
+ const c = rule.conditions;
27
+ const parts = [];
28
+ if (c.hasImage)
29
+ parts.push(hasImage(msg));
30
+ if (c.hasLink)
31
+ parts.push(hasLink(text));
32
+ if (c.contentRegex) {
33
+ try {
34
+ parts.push(new RegExp(c.contentRegex, 'i').test(text));
35
+ }
36
+ catch (err) {
37
+ logger?.warn?.(`[inject-rules] 正则编译失败 rule=${rule.id}: ${err instanceof Error ? err.message : String(err)}`);
38
+ }
39
+ }
40
+ if (c.contentKeywords.length > 0) {
41
+ const tl = text.toLowerCase();
42
+ parts.push(c.contentKeywords.some(k => k && tl.includes(k.toLowerCase())));
43
+ }
44
+ if (parts.length === 0)
45
+ return false; // 无条件=永不触发
46
+ return c.matchScope === 'all' ? parts.every(Boolean) : parts.some(Boolean);
47
+ }
48
+ /**
49
+ * 对组装好的 agentBody 应用全部启用的注入规则, 返回新 body。
50
+ * 规则数组里没有任何 enabled 的 hasImage 规则时, 自动追加内置读图兜底(默认行为与旧版一致)。
51
+ */
52
+ export function applyInjectRules(body, msg, rules, logger) {
53
+ const text = (msg.content ?? '') || '';
54
+ const enabled = (rules ?? []).filter(r => r.enabled);
55
+ const hasUserImageRule = enabled.some(r => r.conditions.hasImage);
56
+ const active = hasUserImageRule ? enabled : [...enabled, LEGACY_IMAGE_RULE];
57
+ let budget = BUDGET_CHARS;
58
+ let out = body;
59
+ for (const rule of active) {
60
+ if (budget <= 0) {
61
+ logger?.warn?.('[inject-rules] 预算耗尽, 停止注入');
62
+ break;
63
+ }
64
+ if (!matchRule(rule, text, msg, logger))
65
+ continue;
66
+ const inj = `[系统提示] ${rule.prompt}`;
67
+ out += `\n\n${inj}`;
68
+ budget -= inj.length;
69
+ }
70
+ return out;
71
+ }
72
+ //# sourceMappingURL=inject-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject-rules.js","sourceRoot":"","sources":["../../src/transport/inject-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * OutboundBuffer — 出站文本缓冲
3
+ *
4
+ * 收集流式 chunk,流式优先投递(StreamingWriter),降级为静态发送。
5
+ * 独立文件便于单测。
6
+ */
7
+ import type { SessionRecord } from '../session/index.js';
8
+ import type { Logger, ReplyTarget } from '../types.js';
9
+ /** QQ 流式会话(openStream 返回) */
10
+ export interface StreamSessionLike {
11
+ update(content: string): Promise<unknown>;
12
+ complete(): Promise<unknown>;
13
+ }
14
+ /** QQ Bot 发送接口 */
15
+ export interface QQBotSender {
16
+ sendMarkdown(target: ReplyTarget, content: string): Promise<unknown>;
17
+ openStream(target: ReplyTarget): StreamSessionLike;
18
+ /** 发送富媒体(图片/语音/视频/文件);url/localPath 二选一;返回新消息 id(若可得) */
19
+ sendMedia(target: ReplyTarget, kind: 'image' | 'video' | 'voice' | 'file', source: {
20
+ url?: string;
21
+ localPath?: string;
22
+ }): Promise<{
23
+ id?: string;
24
+ }>;
25
+ /** 撤本 bot 最近发给该 peer 的消息;成功返回 true */
26
+ recallLast(target: ReplyTarget): Promise<boolean>;
27
+ /** 撤本 bot 发给该 peer 的最近第 index 条(index=1 最近一条, 2 倒数第二条...);成功返回 true */
28
+ recallByIndex(target: ReplyTarget, index: number): Promise<boolean>;
29
+ }
30
+ /**
31
+ * 富媒体感知发送一段完整文本:
32
+ * 1) 若整段为 [RECALL] 指令(去掉指令后无实质内容) → 触发 bot.recallLast
33
+ * 2) 否则解析 [MEDIA:kind|src],媒体段逐个 bot.sendMedia,文本段照常分块 sendMarkdown
34
+ * 富媒体指令与 [RECALL] 从展示文本中剔除;任一失败不抛(逐项记录由调用方/logger)。
35
+ */
36
+ export declare function sendRichOutbound(bot: QQBotSender, target: ReplyTarget, text: string, limit: number, cwd: string | undefined, logError?: (msg: string) => void): Promise<void>;
37
+ export declare class OutboundBuffer {
38
+ private readonly record;
39
+ private readonly bot;
40
+ private readonly limit;
41
+ private readonly logger;
42
+ private readonly cwd;
43
+ private buffer;
44
+ private flushing;
45
+ private readonly writer;
46
+ constructor(record: SessionRecord, bot: QQBotSender, limit: number, logger: Logger, streamingEnabled: boolean, cwd?: string | undefined);
47
+ /** 追加文本增量 */
48
+ append(text: string): void;
49
+ /** 获取当前累积文本 */
50
+ get text(): string;
51
+ /** 发送所有累积文本:流式优先,降级静态 */
52
+ flush(): Promise<void>;
53
+ /** 取消(异常/丢弃),中止流式并清空缓冲 */
54
+ cancel(): void;
55
+ }
56
+ //# sourceMappingURL=outbound-buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-buffer.d.ts","sourceRoot":"","sources":["../../src/transport/outbound-buffer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAevD,6BAA6B;AAC7B,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9B;AAED,kBAAkB;AAClB,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,CAAC;IACnD,yDAAyD;IACzD,SAAS,CACP,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,EAC1C,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5B,sCAAsC;IACtC,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,uEAAuE;IACvE,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrE;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED,qBAAa,cAAc;IAMvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAVtB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;gBAG7B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAC/B,gBAAgB,EAAE,OAAO,EACR,GAAG,GAAE,MAAM,GAAG,SAAqB;IAOtD,aAAa;IACN,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKjC,eAAe;IACf,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,yBAAyB;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BnC,0BAA0B;IACnB,MAAM,IAAI,IAAI;CAItB"}
@@ -0,0 +1,131 @@
1
+ import { chunkMarkdownText } from './chunker.js';
2
+ import { StreamingWriter } from './streaming-writer.js';
3
+ import { containsRecall, collectRecallIndices, parseOutbound, stripDirectives, resolveSource, } from './rich-media.js';
4
+ import { stickerPerTurnCtx } from '../features/sticker-gate.js';
5
+ /** 流式输出节流间隔(ms):连续 chunk 累积后停顿该间隔才推送 */
6
+ const STREAM_THROTTLE_MS = 200;
7
+ /**
8
+ * 富媒体感知发送一段完整文本:
9
+ * 1) 若整段为 [RECALL] 指令(去掉指令后无实质内容) → 触发 bot.recallLast
10
+ * 2) 否则解析 [MEDIA:kind|src],媒体段逐个 bot.sendMedia,文本段照常分块 sendMarkdown
11
+ * 富媒体指令与 [RECALL] 从展示文本中剔除;任一失败不抛(逐项记录由调用方/logger)。
12
+ */
13
+ export async function sendRichOutbound(bot, target, text, limit, cwd, logError) {
14
+ const hasRecall = containsRecall(text);
15
+ const displayable = stripDirectives(text);
16
+ // 只要含 [RECALL(:N)] 就执行,一条回复里多个 [RECALL] 逐个撤(可一次撤多条);
17
+ // 纯召回(去掉指令后无正文)且全未撤到 → 记录并停;否则继续发正文(正文已清所有指令,不会外显)
18
+ if (hasRecall) {
19
+ const indices = collectRecallIndices(text);
20
+ let any = false;
21
+ for (const idx of indices) {
22
+ if (await bot.recallByIndex(target, idx))
23
+ any = true;
24
+ }
25
+ if (!any && !displayable.trim()) {
26
+ logError?.('im-qqbot: [RECALL] 无可撤回消息或已超时');
27
+ return;
28
+ }
29
+ }
30
+ else if (!displayable.trim()) {
31
+ return;
32
+ }
33
+ // 用原始 text 解析(保留 [MEDIA]):媒体段单独发送,文本段各自剔除残留 [RECALL]
34
+ const segments = parseOutbound(text);
35
+ // P1 表情包闸门: perTurn(每轮最多 N 张)。每轮=一次 sendRichOutbound(一轮 assistant 回复文本)。
36
+ // 非表情包图(URL/库外文件/音视频)不计;真实闸门(禁群/频率/预算/去重/活性)在 sender.sendMedia 单点。
37
+ const perTurn = stickerPerTurnCtx(cwd);
38
+ let turnStickerSent = 0;
39
+ for (const seg of segments) {
40
+ if (seg.type === 'media') {
41
+ const isStickerSeg = perTurn.isSticker(seg.kind, seg.source);
42
+ if (isStickerSeg && perTurn.perTurnMax > 0 && turnStickerSent >= perTurn.perTurnMax) {
43
+ // 本轮已达每轮上限:吞掉后续表情包图(不打扰正文/不回执, 防 LLM 换图重试刷闸)
44
+ logError?.(`im-qqbot: 表情包闸门: 本轮已达每轮上限 ${perTurn.perTurnMax} 张, 已忽略后续表情包`);
45
+ continue;
46
+ }
47
+ const src = resolveSource(seg.source, cwd);
48
+ const sourceOpt = src.kind === 'url' ? { url: src.url } : { localPath: src.path };
49
+ try {
50
+ await bot.sendMedia(target, seg.kind, sourceOpt);
51
+ if (isStickerSeg)
52
+ turnStickerSent += 1;
53
+ }
54
+ catch (err) {
55
+ if (err instanceof Error && err.name === 'StickerGateDenied') {
56
+ logError?.(`im-qqbot: 表情包闸门拦截: ${err.message}`);
57
+ }
58
+ else {
59
+ logError?.(`im-qqbot: 富媒体发送失败(${seg.kind}): ${err instanceof Error ? err.message : String(err)}`);
60
+ }
61
+ }
62
+ }
63
+ else {
64
+ const clean = stripDirectives(seg.text);
65
+ if (!clean.trim())
66
+ continue;
67
+ for (const chunk of chunkMarkdownText(clean, limit)) {
68
+ if (chunk.trim())
69
+ await bot.sendMarkdown(target, chunk);
70
+ }
71
+ }
72
+ }
73
+ }
74
+ export class OutboundBuffer {
75
+ record;
76
+ bot;
77
+ limit;
78
+ logger;
79
+ cwd;
80
+ buffer = '';
81
+ flushing = false;
82
+ writer;
83
+ constructor(record, bot, limit, logger, streamingEnabled, cwd = undefined) {
84
+ this.record = record;
85
+ this.bot = bot;
86
+ this.limit = limit;
87
+ this.logger = logger;
88
+ this.cwd = cwd;
89
+ this.writer = streamingEnabled
90
+ ? new StreamingWriter({ bot, target: record.replyTarget, logger, throttleMs: STREAM_THROTTLE_MS })
91
+ : null;
92
+ }
93
+ /** 追加文本增量 */
94
+ append(text) {
95
+ this.buffer += text;
96
+ this.writer?.append(text);
97
+ }
98
+ /** 获取当前累积文本 */
99
+ get text() {
100
+ return this.buffer;
101
+ }
102
+ /** 发送所有累积文本:流式优先,降级静态 */
103
+ async flush() {
104
+ if (this.flushing || !this.buffer.trim())
105
+ return;
106
+ this.flushing = true;
107
+ try {
108
+ if (this.writer) {
109
+ await this.writer.finish();
110
+ // 流式成功(未降级)→ 直接返回
111
+ if (!this.writer.shouldFallback)
112
+ return;
113
+ }
114
+ // 降级:静态发送(writer 不存在 or 流式失败)→ 富媒体感知发送
115
+ await sendRichOutbound(this.bot, this.record.replyTarget, this.buffer, this.limit, this.cwd, (m) => this.logger.error(m));
116
+ }
117
+ catch (err) {
118
+ this.logger.error(`im-qqbot: flush failed: ${err instanceof Error ? err.message : String(err)}`);
119
+ }
120
+ finally {
121
+ this.buffer = '';
122
+ this.flushing = false;
123
+ }
124
+ }
125
+ /** 取消(异常/丢弃),中止流式并清空缓冲 */
126
+ cancel() {
127
+ this.writer?.abort();
128
+ this.buffer = '';
129
+ }
130
+ }
131
+ //# sourceMappingURL=outbound-buffer.js.map