@openclaw/qqbot 2026.5.2 → 2026.5.3-beta.2

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 (203) hide show
  1. package/dist/api.js +646 -0
  2. package/dist/approval-cg0SVahb.js +94 -0
  3. package/dist/channel-N6Y_Rcjp.js +551 -0
  4. package/dist/channel-plugin-api.js +2 -0
  5. package/dist/channel.setup-ByCRAe5H.js +25 -0
  6. package/dist/config-D6545NkC.js +94 -0
  7. package/dist/config-schema-DFcjQw73.js +309 -0
  8. package/dist/exec-approvals-COUsM6wZ.js +138 -0
  9. package/dist/gateway-CmSUJKSt.js +6726 -0
  10. package/dist/handler-runtime-Bqm6N0WG.js +120 -0
  11. package/dist/index.js +29 -0
  12. package/dist/narrowing-BoieBTIU.js +25 -0
  13. package/dist/outbound-BJfhwrPg.js +1357 -0
  14. package/dist/request-context-DXtpwWui.js +921 -0
  15. package/dist/resolve-D_06fV6-.js +284 -0
  16. package/dist/runtime-BJAS3eXW.js +18 -0
  17. package/dist/runtime-api.js +2 -0
  18. package/dist/secret-contract-api.js +56 -0
  19. package/dist/sender-p-B14eLG.js +1987 -0
  20. package/dist/setup-entry.js +15 -0
  21. package/dist/setup-plugin-api.js +2 -0
  22. package/dist/string-normalize-Ci6NM5DE.js +98 -0
  23. package/dist/target-parser-Y0prnrXD.js +245 -0
  24. package/package.json +15 -6
  25. package/api.ts +0 -56
  26. package/channel-plugin-api.ts +0 -1
  27. package/index.ts +0 -29
  28. package/runtime-api.ts +0 -9
  29. package/setup-entry.ts +0 -9
  30. package/setup-plugin-api.ts +0 -3
  31. package/src/bridge/approval/capability.ts +0 -237
  32. package/src/bridge/approval/handler-runtime.ts +0 -204
  33. package/src/bridge/bootstrap.ts +0 -135
  34. package/src/bridge/channel-entry.ts +0 -18
  35. package/src/bridge/commands/framework-context-adapter.ts +0 -60
  36. package/src/bridge/commands/framework-registration.ts +0 -47
  37. package/src/bridge/commands/from-parser.test.ts +0 -86
  38. package/src/bridge/commands/from-parser.ts +0 -60
  39. package/src/bridge/commands/result-dispatcher.ts +0 -76
  40. package/src/bridge/config-shared.ts +0 -132
  41. package/src/bridge/config.ts +0 -111
  42. package/src/bridge/gateway.ts +0 -174
  43. package/src/bridge/logger.ts +0 -31
  44. package/src/bridge/narrowing.ts +0 -31
  45. package/src/bridge/plugin-version.test.ts +0 -146
  46. package/src/bridge/plugin-version.ts +0 -102
  47. package/src/bridge/runtime.ts +0 -25
  48. package/src/bridge/sdk-adapter.ts +0 -131
  49. package/src/bridge/setup/finalize.ts +0 -144
  50. package/src/bridge/setup/surface.ts +0 -34
  51. package/src/bridge/tools/channel.ts +0 -58
  52. package/src/bridge/tools/index.ts +0 -15
  53. package/src/bridge/tools/remind.test.ts +0 -124
  54. package/src/bridge/tools/remind.ts +0 -91
  55. package/src/channel.setup.ts +0 -33
  56. package/src/channel.ts +0 -288
  57. package/src/command-auth.test.ts +0 -62
  58. package/src/config-schema.ts +0 -84
  59. package/src/config.test.ts +0 -364
  60. package/src/engine/access/access-control.test.ts +0 -198
  61. package/src/engine/access/access-control.ts +0 -226
  62. package/src/engine/access/index.ts +0 -16
  63. package/src/engine/access/resolve-policy.test.ts +0 -59
  64. package/src/engine/access/resolve-policy.ts +0 -57
  65. package/src/engine/access/sender-match.test.ts +0 -60
  66. package/src/engine/access/sender-match.ts +0 -55
  67. package/src/engine/access/types.ts +0 -53
  68. package/src/engine/adapter/audio.port.ts +0 -27
  69. package/src/engine/adapter/commands.port.ts +0 -22
  70. package/src/engine/adapter/history.port.ts +0 -52
  71. package/src/engine/adapter/index.ts +0 -139
  72. package/src/engine/adapter/mention-gate.port.ts +0 -50
  73. package/src/engine/adapter/types.ts +0 -38
  74. package/src/engine/api/api-client.ts +0 -212
  75. package/src/engine/api/media-chunked.test.ts +0 -336
  76. package/src/engine/api/media-chunked.ts +0 -622
  77. package/src/engine/api/media.ts +0 -218
  78. package/src/engine/api/messages.ts +0 -293
  79. package/src/engine/api/retry.ts +0 -217
  80. package/src/engine/api/routes.ts +0 -95
  81. package/src/engine/api/token.ts +0 -271
  82. package/src/engine/approval/index.test.ts +0 -22
  83. package/src/engine/approval/index.ts +0 -224
  84. package/src/engine/commands/builtin/log-helpers.ts +0 -319
  85. package/src/engine/commands/builtin/register-all.ts +0 -17
  86. package/src/engine/commands/builtin/register-approve.ts +0 -201
  87. package/src/engine/commands/builtin/register-basic.ts +0 -95
  88. package/src/engine/commands/builtin/register-clear-storage.ts +0 -187
  89. package/src/engine/commands/builtin/register-logs.ts +0 -20
  90. package/src/engine/commands/builtin/register-streaming.ts +0 -137
  91. package/src/engine/commands/builtin/state.ts +0 -31
  92. package/src/engine/commands/slash-command-auth.ts +0 -48
  93. package/src/engine/commands/slash-command-handler.ts +0 -146
  94. package/src/engine/commands/slash-commands-impl.test.ts +0 -8
  95. package/src/engine/commands/slash-commands-impl.ts +0 -61
  96. package/src/engine/commands/slash-commands.ts +0 -199
  97. package/src/engine/config/credential-backup.test.ts +0 -88
  98. package/src/engine/config/credential-backup.ts +0 -107
  99. package/src/engine/config/credentials.ts +0 -76
  100. package/src/engine/config/group.test.ts +0 -234
  101. package/src/engine/config/group.ts +0 -299
  102. package/src/engine/config/resolve.test.ts +0 -152
  103. package/src/engine/config/resolve.ts +0 -283
  104. package/src/engine/config/setup-logic.ts +0 -84
  105. package/src/engine/engine-import-boundary.test.ts +0 -73
  106. package/src/engine/gateway/codec.ts +0 -47
  107. package/src/engine/gateway/constants.ts +0 -117
  108. package/src/engine/gateway/event-dispatcher.ts +0 -177
  109. package/src/engine/gateway/gateway-connection.ts +0 -371
  110. package/src/engine/gateway/gateway.ts +0 -291
  111. package/src/engine/gateway/inbound-attachments.test.ts +0 -126
  112. package/src/engine/gateway/inbound-attachments.ts +0 -360
  113. package/src/engine/gateway/inbound-context.ts +0 -195
  114. package/src/engine/gateway/inbound-pipeline.self-echo.test.ts +0 -218
  115. package/src/engine/gateway/inbound-pipeline.ts +0 -235
  116. package/src/engine/gateway/interaction-handler.ts +0 -220
  117. package/src/engine/gateway/message-queue.test.ts +0 -282
  118. package/src/engine/gateway/message-queue.ts +0 -499
  119. package/src/engine/gateway/outbound-dispatch.test.ts +0 -231
  120. package/src/engine/gateway/outbound-dispatch.ts +0 -575
  121. package/src/engine/gateway/reconnect.ts +0 -199
  122. package/src/engine/gateway/stages/access-stage.ts +0 -132
  123. package/src/engine/gateway/stages/assembly-stage.ts +0 -156
  124. package/src/engine/gateway/stages/content-stage.test.ts +0 -77
  125. package/src/engine/gateway/stages/content-stage.ts +0 -77
  126. package/src/engine/gateway/stages/envelope-stage.test.ts +0 -152
  127. package/src/engine/gateway/stages/envelope-stage.ts +0 -144
  128. package/src/engine/gateway/stages/group-gate-stage.ts +0 -292
  129. package/src/engine/gateway/stages/index.ts +0 -18
  130. package/src/engine/gateway/stages/quote-stage.ts +0 -113
  131. package/src/engine/gateway/stages/refidx-stage.ts +0 -62
  132. package/src/engine/gateway/stages/stub-contexts.ts +0 -116
  133. package/src/engine/gateway/types.ts +0 -264
  134. package/src/engine/gateway/typing-keepalive.ts +0 -79
  135. package/src/engine/group/activation.test.ts +0 -114
  136. package/src/engine/group/activation.ts +0 -147
  137. package/src/engine/group/history.test.ts +0 -314
  138. package/src/engine/group/history.ts +0 -321
  139. package/src/engine/group/mention.test.ts +0 -141
  140. package/src/engine/group/mention.ts +0 -197
  141. package/src/engine/group/message-gating.test.ts +0 -188
  142. package/src/engine/group/message-gating.ts +0 -216
  143. package/src/engine/messaging/decode-media-path.ts +0 -82
  144. package/src/engine/messaging/media-source.ts +0 -215
  145. package/src/engine/messaging/media-type-detect.ts +0 -37
  146. package/src/engine/messaging/outbound-audio-port.ts +0 -38
  147. package/src/engine/messaging/outbound-deliver.ts +0 -810
  148. package/src/engine/messaging/outbound-media-send.ts +0 -702
  149. package/src/engine/messaging/outbound-reply.ts +0 -27
  150. package/src/engine/messaging/outbound-result-helpers.ts +0 -54
  151. package/src/engine/messaging/outbound-types.ts +0 -45
  152. package/src/engine/messaging/outbound.ts +0 -485
  153. package/src/engine/messaging/reply-dispatcher.ts +0 -597
  154. package/src/engine/messaging/reply-limiter.ts +0 -164
  155. package/src/engine/messaging/sender.ts +0 -729
  156. package/src/engine/messaging/streaming-c2c.ts +0 -1192
  157. package/src/engine/messaging/streaming-media-send.ts +0 -544
  158. package/src/engine/messaging/target-parser.ts +0 -104
  159. package/src/engine/ref/format-message-ref.ts +0 -142
  160. package/src/engine/ref/format-ref-entry.test.ts +0 -60
  161. package/src/engine/ref/format-ref-entry.ts +0 -27
  162. package/src/engine/ref/store.ts +0 -208
  163. package/src/engine/ref/types.ts +0 -27
  164. package/src/engine/session/known-users.ts +0 -137
  165. package/src/engine/session/session-store.ts +0 -204
  166. package/src/engine/tools/channel-api.ts +0 -244
  167. package/src/engine/tools/remind-logic.test.ts +0 -280
  168. package/src/engine/tools/remind-logic.ts +0 -377
  169. package/src/engine/types.ts +0 -313
  170. package/src/engine/utils/attachment-tags.test.ts +0 -186
  171. package/src/engine/utils/attachment-tags.ts +0 -174
  172. package/src/engine/utils/audio.test.ts +0 -250
  173. package/src/engine/utils/audio.ts +0 -585
  174. package/src/engine/utils/data-paths.ts +0 -38
  175. package/src/engine/utils/diagnostics.ts +0 -109
  176. package/src/engine/utils/file-utils.test.ts +0 -72
  177. package/src/engine/utils/file-utils.ts +0 -225
  178. package/src/engine/utils/format.test.ts +0 -68
  179. package/src/engine/utils/format.ts +0 -70
  180. package/src/engine/utils/image-size.test.ts +0 -158
  181. package/src/engine/utils/image-size.ts +0 -249
  182. package/src/engine/utils/log.test.ts +0 -28
  183. package/src/engine/utils/log.ts +0 -61
  184. package/src/engine/utils/media-tags.test.ts +0 -32
  185. package/src/engine/utils/media-tags.ts +0 -177
  186. package/src/engine/utils/payload.test.ts +0 -68
  187. package/src/engine/utils/payload.ts +0 -145
  188. package/src/engine/utils/platform-storage-laziness.test.ts +0 -63
  189. package/src/engine/utils/platform.test.ts +0 -148
  190. package/src/engine/utils/platform.ts +0 -343
  191. package/src/engine/utils/request-context.ts +0 -60
  192. package/src/engine/utils/string-normalize.ts +0 -91
  193. package/src/engine/utils/stt.test.ts +0 -104
  194. package/src/engine/utils/stt.ts +0 -100
  195. package/src/engine/utils/text-parsing.test.ts +0 -29
  196. package/src/engine/utils/text-parsing.ts +0 -155
  197. package/src/engine/utils/upload-cache.ts +0 -96
  198. package/src/engine/utils/voice-text.ts +0 -15
  199. package/src/exec-approvals.ts +0 -218
  200. package/src/manifest-schema.test.ts +0 -56
  201. package/src/qqbot-test-support.ts +0 -29
  202. package/src/types.ts +0 -210
  203. package/tsconfig.json +0 -16
@@ -1,282 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createMessageQueue, mergeGroupMessages, type QueuedMessage } from "./message-queue.js";
3
-
4
- function groupMsg(overrides: Partial<QueuedMessage> = {}): QueuedMessage {
5
- return {
6
- type: "group",
7
- senderId: "U1",
8
- senderName: "Alice",
9
- content: "hello",
10
- messageId: "M1",
11
- timestamp: "2026-01-01T00:00:00Z",
12
- groupOpenid: "G1",
13
- ...overrides,
14
- };
15
- }
16
-
17
- describe("engine/gateway/message-queue", () => {
18
- describe("mergeGroupMessages", () => {
19
- it("returns the single message unchanged", () => {
20
- const m = groupMsg();
21
- const merged = mergeGroupMessages([m]);
22
- expect(merged).toBe(m);
23
- });
24
-
25
- it("joins content with sender prefix per line", () => {
26
- const merged = mergeGroupMessages([
27
- groupMsg({ senderName: "A", content: "hi" }),
28
- groupMsg({ senderName: "B", content: "yo" }),
29
- ]);
30
- expect(merged.content).toBe("[A]: hi\n[B]: yo");
31
- expect(merged.merge?.count).toBe(2);
32
- expect(merged.merge?.messages).toHaveLength(2);
33
- });
34
-
35
- it("takes messageId / msgIdx / timestamp from the last message", () => {
36
- const merged = mergeGroupMessages([
37
- groupMsg({ messageId: "M1", msgIdx: "I1", timestamp: "T1" }),
38
- groupMsg({ messageId: "M2", msgIdx: "I2", timestamp: "T2" }),
39
- ]);
40
- expect(merged.messageId).toBe("M2");
41
- expect(merged.msgIdx).toBe("I2");
42
- expect(merged.timestamp).toBe("T2");
43
- });
44
-
45
- it("takes refMsgIdx from the first message", () => {
46
- const merged = mergeGroupMessages([
47
- groupMsg({ refMsgIdx: "R1" }),
48
- groupMsg({ refMsgIdx: "R2" }),
49
- ]);
50
- expect(merged.refMsgIdx).toBe("R1");
51
- });
52
-
53
- it("concatenates attachments in order", () => {
54
- const merged = mergeGroupMessages([
55
- groupMsg({
56
- attachments: [{ content_type: "image/png", url: "a" }],
57
- }),
58
- groupMsg({
59
- attachments: [
60
- { content_type: "image/png", url: "b" },
61
- { content_type: "image/png", url: "c" },
62
- ],
63
- }),
64
- ]);
65
- expect(merged.attachments?.map((a) => a.url)).toEqual(["a", "b", "c"]);
66
- });
67
-
68
- it("deduplicates mentions by member/user openid", () => {
69
- const merged = mergeGroupMessages([
70
- groupMsg({ mentions: [{ member_openid: "X" }, { member_openid: "Y" }] }),
71
- groupMsg({ mentions: [{ member_openid: "X" }, { member_openid: "Z" }] }),
72
- ]);
73
- expect(merged.mentions?.map((m) => m.member_openid)).toEqual(["X", "Y", "Z"]);
74
- });
75
-
76
- it("flags merged turn as @bot when ANY source was GROUP_AT_MESSAGE_CREATE", () => {
77
- const merged = mergeGroupMessages([
78
- groupMsg({ eventType: "GROUP_MESSAGE_CREATE" }),
79
- groupMsg({ eventType: "GROUP_AT_MESSAGE_CREATE" }),
80
- ]);
81
- expect(merged.eventType).toBe("GROUP_AT_MESSAGE_CREATE");
82
- });
83
-
84
- it("keeps last eventType when no @bot event was present", () => {
85
- const merged = mergeGroupMessages([
86
- groupMsg({ eventType: "GROUP_MESSAGE_CREATE" }),
87
- groupMsg({ eventType: "GROUP_MESSAGE_CREATE" }),
88
- ]);
89
- expect(merged.eventType).toBe("GROUP_MESSAGE_CREATE");
90
- });
91
-
92
- it("marks as bot only when every source is a bot", () => {
93
- expect(
94
- mergeGroupMessages([groupMsg({ senderIsBot: true }), groupMsg({ senderIsBot: false })])
95
- .senderIsBot,
96
- ).toBe(false);
97
-
98
- expect(
99
- mergeGroupMessages([groupMsg({ senderIsBot: true }), groupMsg({ senderIsBot: true })])
100
- .senderIsBot,
101
- ).toBe(true);
102
- });
103
- });
104
-
105
- describe("createMessageQueue enqueue / evict", () => {
106
- it("uses group peerId for group messages", () => {
107
- const q = createMessageQueue({ accountId: "a", isAborted: () => true });
108
- expect(q.getMessagePeerId(groupMsg({ groupOpenid: "G9" }))).toBe("group:G9");
109
- });
110
-
111
- it("uses dm peerId for c2c messages", () => {
112
- const q = createMessageQueue({ accountId: "a", isAborted: () => true });
113
- expect(
114
- q.getMessagePeerId({
115
- ...groupMsg(),
116
- type: "c2c",
117
- groupOpenid: undefined,
118
- senderId: "U9",
119
- }),
120
- ).toBe("dm:U9");
121
- });
122
-
123
- it("enqueue without processor still drains (no-op when fn is null)", async () => {
124
- // When no processor is attached, drain shifts messages but does
125
- // nothing with them. The queue ends empty on the next microtask.
126
- const q = createMessageQueue({ accountId: "a", isAborted: () => false });
127
- q.enqueue(groupMsg({ messageId: "M1" }));
128
- q.enqueue(groupMsg({ messageId: "M2" }));
129
- await Promise.resolve();
130
- await Promise.resolve();
131
- expect(q.getSnapshot("group:G1").senderPending).toBe(0);
132
- });
133
-
134
- it("group overflow evicts a bot message first (eviction is synchronous)", () => {
135
- // Use isAborted=true so drain exits immediately on the first
136
- // microtask. Our `eviction` logic runs synchronously inside
137
- // enqueue, BEFORE drain kicks in, so the 4th enqueue still has to
138
- // evict even though we never actually process anything.
139
- const q = createMessageQueue({
140
- accountId: "a",
141
- isAborted: () => true,
142
- groupQueueSize: 3,
143
- });
144
- // Fill the queue to the cap (3), then enqueue one more to trigger
145
- // eviction. The first three enqueues trigger drainUserQueue which
146
- // synchronously deletes the empty queue in its finally block when
147
- // isAborted=true. We bypass that by calling enqueue then reading
148
- // inside the same synchronous tick via getSnapshot is NOT viable,
149
- // so we instead observe the eviction by counting what ends up
150
- // visible after the queue has stabilized.
151
- q.enqueue(groupMsg({ messageId: "H1" }));
152
- q.enqueue(groupMsg({ messageId: "B1", senderIsBot: true }));
153
- q.enqueue(groupMsg({ messageId: "H2" }));
154
- q.enqueue(groupMsg({ messageId: "H3" }));
155
- // With isAborted=true the drain deletes the queue after each
156
- // enqueue, so the snapshot just confirms we didn't throw. The
157
- // actual eviction logic is covered by the "group overflow via
158
- // processor" scenario below.
159
- expect(q.getSnapshot("group:G1").senderPending).toBe(0);
160
- });
161
-
162
- it("group overflow drops bot messages first (via processor)", async () => {
163
- const seen: QueuedMessage[] = [];
164
- let gate!: (value?: unknown) => void;
165
- const blocker = new Promise((res) => {
166
- gate = res;
167
- });
168
- const q = createMessageQueue({
169
- accountId: "a",
170
- isAborted: () => false,
171
- groupQueueSize: 3,
172
- });
173
- q.startProcessor(async (msg) => {
174
- seen.push(msg);
175
- // Hold the processor until we've filled the queue to capacity.
176
- await blocker;
177
- });
178
- // First enqueue starts processing immediately (blocker held).
179
- q.enqueue(groupMsg({ messageId: "First" }));
180
- await Promise.resolve();
181
- // Now fill the queue with 3 more (cap=3).
182
- q.enqueue(groupMsg({ messageId: "H1" }));
183
- q.enqueue(groupMsg({ messageId: "B1", senderIsBot: true }));
184
- q.enqueue(groupMsg({ messageId: "H2" }));
185
- expect(q.getSnapshot("group:G1").senderPending).toBe(3);
186
- // 5th enqueue → eviction. Bot message (B1) should be the victim.
187
- q.enqueue(groupMsg({ messageId: "H3" }));
188
- const peerQueueIds = q.getSnapshot("group:G1");
189
- expect(peerQueueIds.senderPending).toBe(3);
190
- // Release the processor and drain.
191
- gate();
192
- await new Promise((res) => setTimeout(res, 0));
193
- const seenIds = seen.map((m) => m.messageId);
194
- expect(seenIds).toContain("First");
195
- // The bot message should NOT have been processed — it was evicted.
196
- // (Note: The first batch ran merged, so the exact count of calls
197
- // varies; we only assert the bot message id never appeared.)
198
- const mergedCall = seen.find((m) => (m.merge?.count ?? 0) > 1);
199
- if (mergedCall) {
200
- expect(mergedCall.merge?.messages.map((m) => m.messageId)).not.toContain("B1");
201
- } else {
202
- expect(seenIds).not.toContain("B1");
203
- }
204
- });
205
-
206
- it("clearUserQueue drops buffered items before drain runs", () => {
207
- // Use a processor that never resolves so enqueued messages stay
208
- // buffered behind a single active worker — then clearUserQueue
209
- // should drop the rest.
210
- let release!: () => void;
211
- const blocker = new Promise<void>((res) => {
212
- release = res;
213
- });
214
- const q = createMessageQueue({ accountId: "a", isAborted: () => false });
215
- q.startProcessor(async () => {
216
- await blocker;
217
- });
218
- q.enqueue(groupMsg({ messageId: "M1" }));
219
- q.enqueue(groupMsg({ messageId: "M2" }));
220
- q.enqueue(groupMsg({ messageId: "M3" }));
221
- // First message is being processed; remaining two are queued.
222
- expect(q.getSnapshot("group:G1").senderPending).toBeGreaterThanOrEqual(0);
223
- const dropped = q.clearUserQueue("group:G1");
224
- expect(dropped).toBeGreaterThanOrEqual(0);
225
- release();
226
- });
227
- });
228
-
229
- describe("drainGroupBatch merging", () => {
230
- it("merges multiple normal group messages into one processor call", async () => {
231
- const seen: QueuedMessage[] = [];
232
- let aborted = false;
233
- const q = createMessageQueue({
234
- accountId: "a",
235
- isAborted: () => aborted,
236
- });
237
- q.startProcessor(async (msg) => {
238
- seen.push(msg);
239
- });
240
- // Enqueue three normal group messages synchronously so they batch
241
- // before the drain loop kicks in — the first enqueue starts the
242
- // drain, but the synchronous enqueues land before the first await.
243
- q.enqueue(groupMsg({ messageId: "M1", content: "hi" }));
244
- q.enqueue(groupMsg({ messageId: "M2", content: "yo" }));
245
- q.enqueue(groupMsg({ messageId: "M3", content: "!!" }));
246
- // Allow microtasks to flush.
247
- await Promise.resolve();
248
- await Promise.resolve();
249
- aborted = true;
250
- // Depending on timing the first message may have been processed solo;
251
- // what we guarantee is that the total processor calls are fewer than
252
- // three and the remaining messages were merged.
253
- expect(seen.length).toBeGreaterThanOrEqual(1);
254
- expect(seen.length).toBeLessThan(3);
255
- const mergedCall = seen.find((m) => (m.merge?.count ?? 0) > 1);
256
- expect(mergedCall).toBeDefined();
257
- expect(mergedCall?.content).toContain("[Alice]:");
258
- });
259
-
260
- it("processes slash commands independently from regular messages", async () => {
261
- const seen: QueuedMessage[] = [];
262
- let aborted = false;
263
- const q = createMessageQueue({
264
- accountId: "a",
265
- isAborted: () => aborted,
266
- });
267
- q.startProcessor(async (msg) => {
268
- seen.push(msg);
269
- });
270
- q.enqueue(groupMsg({ messageId: "M1", content: "hi" }));
271
- q.enqueue(groupMsg({ messageId: "M2", content: "/stop" }));
272
- q.enqueue(groupMsg({ messageId: "M3", content: "yo" }));
273
- await Promise.resolve();
274
- await Promise.resolve();
275
- aborted = true;
276
- // Command should appear as its own call (not merged with the others).
277
- const cmdCall = seen.find((m) => m.content === "/stop");
278
- expect(cmdCall).toBeDefined();
279
- expect(cmdCall?.merge).toBeUndefined();
280
- });
281
- });
282
- });