@openclaw/matrix 2026.3.13 → 2026.5.9-beta.1

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 (206) hide show
  1. package/dist/account-config-D2W-V1eQ.js +96 -0
  2. package/dist/account-selection-BWwIruri.js +158 -0
  3. package/dist/accounts-Bm90Rzvp.js +130 -0
  4. package/dist/active-client-uhlxdhEy.js +20 -0
  5. package/dist/allowlist-sTzpCn5d.js +68 -0
  6. package/dist/api.js +12 -0
  7. package/dist/approval-handler.runtime-DWTQfd4m.js +370 -0
  8. package/dist/approval-ids-DoC2z7tR.js +7 -0
  9. package/dist/approval-reaction-auth-DbcA1gGd.js +27 -0
  10. package/dist/approval-reactions-o2_tuH8D.js +162 -0
  11. package/dist/async-lock-uQfhfQIY.js +19 -0
  12. package/dist/auth-presence.js +26 -0
  13. package/dist/backup-health-Cabu_WQC.js +60 -0
  14. package/dist/channel-DJNir3Rb.js +1116 -0
  15. package/dist/channel-plugin-api.js +2 -0
  16. package/dist/channel.runtime-BQu0hTih.js +246 -0
  17. package/dist/cli-BmfTmg7x.js +1340 -0
  18. package/dist/cli-metadata-B-PCEzrA.js +22 -0
  19. package/dist/cli-metadata.js +2 -0
  20. package/dist/client-DkcXnm0X.js +25 -0
  21. package/dist/client-_hckQNGW.js +31 -0
  22. package/dist/client-bootstrap-Rb8oHvhH.js +114 -0
  23. package/dist/config--5-S2Akv.js +452 -0
  24. package/dist/config-paths-nsVaysCu.js +19 -0
  25. package/dist/config-schema-nPLpEgHl.js +200 -0
  26. package/dist/config-secret-input.runtime-DiKFehsE.js +2 -0
  27. package/dist/config-update-wZX-HLMn.js +143 -0
  28. package/dist/contract-api.js +9 -0
  29. package/dist/create-client-DCnqDaqd.js +64 -0
  30. package/dist/credentials-DV6fWXhC.js +56 -0
  31. package/dist/credentials-read-cmHgousK.js +112 -0
  32. package/dist/credentials-write.runtime-zniTq-Gr.js +17 -0
  33. package/dist/crypto-node.runtime-pihzdpY7.js +12 -0
  34. package/dist/crypto-runtime-ZI0zAtn3.js +1214 -0
  35. package/dist/deps-C6WqKY7m.js +235 -0
  36. package/dist/device-health-UVYpbA_W.js +16 -0
  37. package/dist/direct-management-DMMMgtTB.js +249 -0
  38. package/dist/direct-room-XkutHjES.js +76 -0
  39. package/dist/directory-live-DmOtMhyr.js +150 -0
  40. package/dist/doctor-C4__7c-U.js +153 -0
  41. package/dist/doctor-contract-D4-64QuJ.js +246 -0
  42. package/dist/doctor-contract-api.js +2 -0
  43. package/dist/draft-stream-BE2QevQQ.js +144 -0
  44. package/dist/encryption-guidance-BPi3A_m3.js +15 -0
  45. package/dist/env-auth-BJqGI8M6.js +63 -0
  46. package/dist/env-vars-C7uQCTKn.js +63 -0
  47. package/dist/errors-CTcpEDq-.js +17 -0
  48. package/dist/exec-approval-resolver-Bza9Dhlm.js +15 -0
  49. package/dist/exec-approvals-Crnh543m.js +196 -0
  50. package/dist/helper-api.js +4 -0
  51. package/dist/http-client-C7AeVJay.js +319 -0
  52. package/dist/index.js +46 -0
  53. package/dist/legacy-crypto-inspector-poDWldgy.js +41 -0
  54. package/dist/legacy-crypto-restore-Biw-w2ng.js +85 -0
  55. package/dist/logger-CnZRVrux.js +78 -0
  56. package/dist/logging-DZHSPP5N.js +99 -0
  57. package/dist/matrix-migration.runtime-WY6ffcrf.js +525 -0
  58. package/dist/media-text-DU6nWZuj.js +146 -0
  59. package/dist/messages-BpihMh82.js +140 -0
  60. package/dist/migration-snapshot-backup-DaCHTp8C.js +69 -0
  61. package/dist/migration-snapshot.runtime-CKHE3xF9.js +2 -0
  62. package/dist/monitor-C_81r_Ck.js +4125 -0
  63. package/dist/plugin-entry.handlers.runtime.js +51 -0
  64. package/dist/probe.runtime-BvAzYAIe.js +3 -0
  65. package/dist/profile-BlHu0wDX.js +111 -0
  66. package/dist/profile-update-DjeBNgIV.js +69 -0
  67. package/dist/reaction-common-ejrL19w-.js +71 -0
  68. package/dist/reaction-events-CiARZfjk.js +121 -0
  69. package/dist/record-shared-CHWJCTWf.js +2 -0
  70. package/dist/recovery-key-store-BTJ6jz5v.js +294 -0
  71. package/dist/resolve-targets-YtJnw1Tb.js +140 -0
  72. package/dist/resolver.runtime-D9piiGEl.js +5 -0
  73. package/dist/rolldown-runtime-DUslC3ob.js +14 -0
  74. package/dist/route-D6rg-iXN.js +161 -0
  75. package/dist/runtime-C6X4h_SJ.js +6 -0
  76. package/dist/runtime-Dog86njy.js +8 -0
  77. package/dist/runtime-api-BXWBFIqm.js +25 -0
  78. package/dist/runtime-api.js +25 -0
  79. package/dist/runtime-heavy-api.js +3 -0
  80. package/dist/runtime-setter-api.js +2 -0
  81. package/dist/sdk-B2vZA27-.js +1416 -0
  82. package/dist/secret-contract-DcrJWCQI.js +120 -0
  83. package/dist/secret-contract-api.js +2 -0
  84. package/dist/send-Bo0DU1ca.js +1200 -0
  85. package/dist/session-store-metadata-DI5SCofx.js +77 -0
  86. package/dist/setup-bootstrap-ImenBsMt.js +62 -0
  87. package/dist/setup-core-CfZy05oW.js +116 -0
  88. package/dist/setup-dm-policy-2-r1FrQh.js +194 -0
  89. package/dist/setup-entry.js +19 -0
  90. package/dist/setup-plugin-api.js +44 -0
  91. package/dist/setup-surface-CqT_o61M.js +540 -0
  92. package/dist/shared-CpMoYKm1.js +195 -0
  93. package/dist/startup-abort-56edvmbM.js +32 -0
  94. package/dist/startup-verification-Demyp0bP.js +132 -0
  95. package/dist/storage-paths-BJLdnCjV.js +52 -0
  96. package/dist/storage-tC3ujLiW.js +281 -0
  97. package/dist/subagent-hooks-DQbyqq9V.js +149 -0
  98. package/dist/subagent-hooks-api.js +23 -0
  99. package/dist/sync-state-C_beeevA.js +12 -0
  100. package/dist/target-ids-80nQ2gql.js +77 -0
  101. package/dist/test-api.js +4 -0
  102. package/dist/thread-binding-api-Cq_E-E1K.js +17 -0
  103. package/dist/thread-binding-api.js +2 -0
  104. package/dist/thread-bindings-B9mesxXk.js +352 -0
  105. package/dist/thread-bindings-runtime.js +2 -0
  106. package/dist/thread-bindings-shared-DK-d-oYX.js +97 -0
  107. package/dist/timeout-abort-signal-CtaIaP1v.js +2 -0
  108. package/dist/tool-actions.runtime-BIH49vRr.js +532 -0
  109. package/dist/url-validation-DiK9j7jz.js +36 -0
  110. package/dist/verification-CZ2rDeHL.js +345 -0
  111. package/openclaw.plugin.json +788 -1
  112. package/package.json +82 -16
  113. package/CHANGELOG.md +0 -104
  114. package/index.ts +0 -22
  115. package/src/actions.ts +0 -195
  116. package/src/channel.directory.test.ts +0 -135
  117. package/src/channel.ts +0 -461
  118. package/src/config-schema.test.ts +0 -26
  119. package/src/config-schema.ts +0 -62
  120. package/src/directory-live.test.ts +0 -85
  121. package/src/directory-live.ts +0 -209
  122. package/src/group-mentions.ts +0 -52
  123. package/src/matrix/accounts.test.ts +0 -131
  124. package/src/matrix/accounts.ts +0 -114
  125. package/src/matrix/actions/client.ts +0 -47
  126. package/src/matrix/actions/limits.test.ts +0 -15
  127. package/src/matrix/actions/limits.ts +0 -6
  128. package/src/matrix/actions/messages.ts +0 -126
  129. package/src/matrix/actions/pins.test.ts +0 -74
  130. package/src/matrix/actions/pins.ts +0 -84
  131. package/src/matrix/actions/reactions.test.ts +0 -109
  132. package/src/matrix/actions/reactions.ts +0 -102
  133. package/src/matrix/actions/room.ts +0 -85
  134. package/src/matrix/actions/summary.ts +0 -75
  135. package/src/matrix/actions/types.ts +0 -85
  136. package/src/matrix/actions.ts +0 -15
  137. package/src/matrix/active-client.ts +0 -32
  138. package/src/matrix/client/config.ts +0 -245
  139. package/src/matrix/client/create-client.ts +0 -125
  140. package/src/matrix/client/logging.ts +0 -46
  141. package/src/matrix/client/runtime.ts +0 -4
  142. package/src/matrix/client/shared.test.ts +0 -85
  143. package/src/matrix/client/shared.ts +0 -210
  144. package/src/matrix/client/startup.test.ts +0 -49
  145. package/src/matrix/client/startup.ts +0 -29
  146. package/src/matrix/client/storage.ts +0 -131
  147. package/src/matrix/client/types.ts +0 -34
  148. package/src/matrix/client-bootstrap.ts +0 -47
  149. package/src/matrix/client.test.ts +0 -56
  150. package/src/matrix/client.ts +0 -14
  151. package/src/matrix/credentials.ts +0 -125
  152. package/src/matrix/deps.test.ts +0 -74
  153. package/src/matrix/deps.ts +0 -126
  154. package/src/matrix/format.test.ts +0 -33
  155. package/src/matrix/format.ts +0 -22
  156. package/src/matrix/index.ts +0 -11
  157. package/src/matrix/monitor/access-policy.ts +0 -126
  158. package/src/matrix/monitor/allowlist.test.ts +0 -45
  159. package/src/matrix/monitor/allowlist.ts +0 -94
  160. package/src/matrix/monitor/auto-join.ts +0 -72
  161. package/src/matrix/monitor/direct.test.ts +0 -396
  162. package/src/matrix/monitor/direct.ts +0 -152
  163. package/src/matrix/monitor/events.test.ts +0 -186
  164. package/src/matrix/monitor/events.ts +0 -168
  165. package/src/matrix/monitor/handler.body-for-agent.test.ts +0 -196
  166. package/src/matrix/monitor/handler.ts +0 -768
  167. package/src/matrix/monitor/inbound-body.test.ts +0 -73
  168. package/src/matrix/monitor/inbound-body.ts +0 -28
  169. package/src/matrix/monitor/index.test.ts +0 -18
  170. package/src/matrix/monitor/index.ts +0 -414
  171. package/src/matrix/monitor/location.ts +0 -100
  172. package/src/matrix/monitor/media.test.ts +0 -86
  173. package/src/matrix/monitor/media.ts +0 -118
  174. package/src/matrix/monitor/mentions.test.ts +0 -154
  175. package/src/matrix/monitor/mentions.ts +0 -62
  176. package/src/matrix/monitor/replies.test.ts +0 -184
  177. package/src/matrix/monitor/replies.ts +0 -124
  178. package/src/matrix/monitor/room-info.ts +0 -55
  179. package/src/matrix/monitor/rooms.test.ts +0 -124
  180. package/src/matrix/monitor/rooms.ts +0 -47
  181. package/src/matrix/monitor/threads.ts +0 -68
  182. package/src/matrix/monitor/types.ts +0 -39
  183. package/src/matrix/poll-types.test.ts +0 -21
  184. package/src/matrix/poll-types.ts +0 -167
  185. package/src/matrix/probe.ts +0 -69
  186. package/src/matrix/sdk-runtime.ts +0 -18
  187. package/src/matrix/send/client.ts +0 -99
  188. package/src/matrix/send/formatting.ts +0 -93
  189. package/src/matrix/send/media.ts +0 -230
  190. package/src/matrix/send/targets.test.ts +0 -98
  191. package/src/matrix/send/targets.ts +0 -150
  192. package/src/matrix/send/types.ts +0 -110
  193. package/src/matrix/send-queue.test.ts +0 -145
  194. package/src/matrix/send-queue.ts +0 -28
  195. package/src/matrix/send.test.ts +0 -319
  196. package/src/matrix/send.ts +0 -267
  197. package/src/onboarding.ts +0 -462
  198. package/src/outbound.test.ts +0 -159
  199. package/src/outbound.ts +0 -58
  200. package/src/resolve-targets.test.ts +0 -68
  201. package/src/resolve-targets.ts +0 -125
  202. package/src/runtime.ts +0 -6
  203. package/src/secret-input.ts +0 -13
  204. package/src/test-mocks.ts +0 -53
  205. package/src/tool-actions.ts +0 -164
  206. package/src/types.ts +0 -118
@@ -1,150 +0,0 @@
1
- import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import { EventType, type MatrixDirectAccountData } from "./types.js";
3
-
4
- function normalizeTarget(raw: string): string {
5
- const trimmed = raw.trim();
6
- if (!trimmed) {
7
- throw new Error("Matrix target is required (room:<id> or #alias)");
8
- }
9
- return trimmed;
10
- }
11
-
12
- export function normalizeThreadId(raw?: string | number | null): string | null {
13
- if (raw === undefined || raw === null) {
14
- return null;
15
- }
16
- const trimmed = String(raw).trim();
17
- return trimmed ? trimmed : null;
18
- }
19
-
20
- // Size-capped to prevent unbounded growth (#4948)
21
- const MAX_DIRECT_ROOM_CACHE_SIZE = 1024;
22
- const directRoomCache = new Map<string, string>();
23
- function setDirectRoomCached(key: string, value: string): void {
24
- directRoomCache.set(key, value);
25
- if (directRoomCache.size > MAX_DIRECT_ROOM_CACHE_SIZE) {
26
- const oldest = directRoomCache.keys().next().value;
27
- if (oldest !== undefined) {
28
- directRoomCache.delete(oldest);
29
- }
30
- }
31
- }
32
-
33
- async function persistDirectRoom(
34
- client: MatrixClient,
35
- userId: string,
36
- roomId: string,
37
- ): Promise<void> {
38
- let directContent: MatrixDirectAccountData | null = null;
39
- try {
40
- directContent = await client.getAccountData(EventType.Direct);
41
- } catch {
42
- // Ignore fetch errors and fall back to an empty map.
43
- }
44
- const existing = directContent && !Array.isArray(directContent) ? directContent : {};
45
- const current = Array.isArray(existing[userId]) ? existing[userId] : [];
46
- if (current[0] === roomId) {
47
- return;
48
- }
49
- const next = [roomId, ...current.filter((id) => id !== roomId)];
50
- try {
51
- await client.setAccountData(EventType.Direct, {
52
- ...existing,
53
- [userId]: next,
54
- });
55
- } catch {
56
- // Ignore persistence errors.
57
- }
58
- }
59
-
60
- async function resolveDirectRoomId(client: MatrixClient, userId: string): Promise<string> {
61
- const trimmed = userId.trim();
62
- if (!trimmed.startsWith("@")) {
63
- throw new Error(`Matrix user IDs must be fully qualified (got "${trimmed}")`);
64
- }
65
-
66
- const cached = directRoomCache.get(trimmed);
67
- if (cached) {
68
- return cached;
69
- }
70
-
71
- // 1) Fast path: use account data (m.direct) for *this* logged-in user (the bot).
72
- try {
73
- const directContent = (await client.getAccountData(EventType.Direct)) as Record<
74
- string,
75
- string[] | undefined
76
- >;
77
- const list = Array.isArray(directContent?.[trimmed]) ? directContent[trimmed] : [];
78
- if (list && list.length > 0) {
79
- setDirectRoomCached(trimmed, list[0]);
80
- return list[0];
81
- }
82
- } catch {
83
- // Ignore and fall back.
84
- }
85
-
86
- // 2) Fallback: look for an existing joined room that looks like a 1:1 with the user.
87
- // Many clients only maintain m.direct for *their own* account data, so relying on it is brittle.
88
- let fallbackRoom: string | null = null;
89
- try {
90
- const rooms = await client.getJoinedRooms();
91
- for (const roomId of rooms) {
92
- let members: string[];
93
- try {
94
- members = await client.getJoinedRoomMembers(roomId);
95
- } catch {
96
- continue;
97
- }
98
- if (!members.includes(trimmed)) {
99
- continue;
100
- }
101
- // Prefer classic 1:1 rooms, but allow larger rooms if requested.
102
- if (members.length === 2) {
103
- setDirectRoomCached(trimmed, roomId);
104
- await persistDirectRoom(client, trimmed, roomId);
105
- return roomId;
106
- }
107
- if (!fallbackRoom) {
108
- fallbackRoom = roomId;
109
- }
110
- }
111
- } catch {
112
- // Ignore and fall back.
113
- }
114
-
115
- if (fallbackRoom) {
116
- setDirectRoomCached(trimmed, fallbackRoom);
117
- await persistDirectRoom(client, trimmed, fallbackRoom);
118
- return fallbackRoom;
119
- }
120
-
121
- throw new Error(`No direct room found for ${trimmed} (m.direct missing)`);
122
- }
123
-
124
- export async function resolveMatrixRoomId(client: MatrixClient, raw: string): Promise<string> {
125
- const target = normalizeTarget(raw);
126
- const lowered = target.toLowerCase();
127
- if (lowered.startsWith("matrix:")) {
128
- return await resolveMatrixRoomId(client, target.slice("matrix:".length));
129
- }
130
- if (lowered.startsWith("room:")) {
131
- return await resolveMatrixRoomId(client, target.slice("room:".length));
132
- }
133
- if (lowered.startsWith("channel:")) {
134
- return await resolveMatrixRoomId(client, target.slice("channel:".length));
135
- }
136
- if (lowered.startsWith("user:")) {
137
- return await resolveDirectRoomId(client, target.slice("user:".length));
138
- }
139
- if (target.startsWith("@")) {
140
- return await resolveDirectRoomId(client, target);
141
- }
142
- if (target.startsWith("#")) {
143
- const resolved = await client.resolveRoom(target);
144
- if (!resolved) {
145
- throw new Error(`Matrix alias ${target} could not be resolved`);
146
- }
147
- return resolved;
148
- }
149
- return target;
150
- }
@@ -1,110 +0,0 @@
1
- import type {
2
- DimensionalFileInfo,
3
- EncryptedFile,
4
- FileWithThumbnailInfo,
5
- MessageEventContent,
6
- TextualMessageEventContent,
7
- TimedFileInfo,
8
- VideoFileInfo,
9
- } from "@vector-im/matrix-bot-sdk";
10
-
11
- // Message types
12
- export const MsgType = {
13
- Text: "m.text",
14
- Image: "m.image",
15
- Audio: "m.audio",
16
- Video: "m.video",
17
- File: "m.file",
18
- Notice: "m.notice",
19
- } as const;
20
-
21
- // Relation types
22
- export const RelationType = {
23
- Annotation: "m.annotation",
24
- Replace: "m.replace",
25
- Thread: "m.thread",
26
- } as const;
27
-
28
- // Event types
29
- export const EventType = {
30
- Direct: "m.direct",
31
- Reaction: "m.reaction",
32
- RoomMessage: "m.room.message",
33
- } as const;
34
-
35
- export type MatrixDirectAccountData = Record<string, string[]>;
36
-
37
- export type MatrixReplyRelation = {
38
- "m.in_reply_to": { event_id: string };
39
- };
40
-
41
- export type MatrixThreadRelation = {
42
- rel_type: typeof RelationType.Thread;
43
- event_id: string;
44
- is_falling_back?: boolean;
45
- "m.in_reply_to"?: { event_id: string };
46
- };
47
-
48
- export type MatrixRelation = MatrixReplyRelation | MatrixThreadRelation;
49
-
50
- export type MatrixReplyMeta = {
51
- "m.relates_to"?: MatrixRelation;
52
- };
53
-
54
- export type MatrixMediaInfo =
55
- | FileWithThumbnailInfo
56
- | DimensionalFileInfo
57
- | TimedFileInfo
58
- | VideoFileInfo;
59
-
60
- export type MatrixTextContent = TextualMessageEventContent & MatrixReplyMeta;
61
-
62
- export type MatrixMediaContent = MessageEventContent &
63
- MatrixReplyMeta & {
64
- info?: MatrixMediaInfo;
65
- url?: string;
66
- file?: EncryptedFile;
67
- filename?: string;
68
- "org.matrix.msc3245.voice"?: Record<string, never>;
69
- "org.matrix.msc1767.audio"?: { duration: number };
70
- };
71
-
72
- export type MatrixOutboundContent = MatrixTextContent | MatrixMediaContent;
73
-
74
- export type ReactionEventContent = {
75
- "m.relates_to": {
76
- rel_type: typeof RelationType.Annotation;
77
- event_id: string;
78
- key: string;
79
- };
80
- };
81
-
82
- export type MatrixSendResult = {
83
- messageId: string;
84
- roomId: string;
85
- };
86
-
87
- export type MatrixSendOpts = {
88
- cfg?: import("../../types.js").CoreConfig;
89
- client?: import("@vector-im/matrix-bot-sdk").MatrixClient;
90
- mediaUrl?: string;
91
- accountId?: string;
92
- replyToId?: string;
93
- threadId?: string | number | null;
94
- timeoutMs?: number;
95
- /** Send audio as voice message (voice bubble) instead of audio file. Defaults to false. */
96
- audioAsVoice?: boolean;
97
- };
98
-
99
- export type MatrixMediaMsgType =
100
- | typeof MsgType.Image
101
- | typeof MsgType.Audio
102
- | typeof MsgType.Video
103
- | typeof MsgType.File;
104
-
105
- export type MediaKind = "image" | "audio" | "video" | "document" | "unknown";
106
-
107
- export type MatrixFormattedContent = MessageEventContent & {
108
- format?: string;
109
- formatted_body?: string;
110
- };
@@ -1,145 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { createDeferred } from "../../../shared/deferred.js";
3
- import { DEFAULT_SEND_GAP_MS, enqueueSend } from "./send-queue.js";
4
-
5
- describe("enqueueSend", () => {
6
- beforeEach(() => {
7
- vi.useFakeTimers();
8
- });
9
-
10
- afterEach(() => {
11
- vi.useRealTimers();
12
- });
13
-
14
- it("serializes sends per room", async () => {
15
- const gate = createDeferred<void>();
16
- const events: string[] = [];
17
-
18
- const first = enqueueSend("!room:example.org", async () => {
19
- events.push("start1");
20
- await gate.promise;
21
- events.push("end1");
22
- return "one";
23
- });
24
- const second = enqueueSend("!room:example.org", async () => {
25
- events.push("start2");
26
- events.push("end2");
27
- return "two";
28
- });
29
-
30
- await vi.advanceTimersByTimeAsync(DEFAULT_SEND_GAP_MS);
31
- expect(events).toEqual(["start1"]);
32
-
33
- await vi.advanceTimersByTimeAsync(DEFAULT_SEND_GAP_MS * 2);
34
- expect(events).toEqual(["start1"]);
35
-
36
- gate.resolve();
37
- await first;
38
- await vi.advanceTimersByTimeAsync(DEFAULT_SEND_GAP_MS - 1);
39
- expect(events).toEqual(["start1", "end1"]);
40
- await vi.advanceTimersByTimeAsync(1);
41
- await second;
42
- expect(events).toEqual(["start1", "end1", "start2", "end2"]);
43
- });
44
-
45
- it("does not serialize across different rooms", async () => {
46
- const events: string[] = [];
47
-
48
- const a = enqueueSend("!a:example.org", async () => {
49
- events.push("a");
50
- return "a";
51
- });
52
- const b = enqueueSend("!b:example.org", async () => {
53
- events.push("b");
54
- return "b";
55
- });
56
-
57
- await vi.advanceTimersByTimeAsync(DEFAULT_SEND_GAP_MS);
58
- await Promise.all([a, b]);
59
- expect(events.sort()).toEqual(["a", "b"]);
60
- });
61
-
62
- it("continues queue after failures", async () => {
63
- const first = enqueueSend("!room:example.org", async () => {
64
- throw new Error("boom");
65
- }).then(
66
- () => ({ ok: true as const }),
67
- (error) => ({ ok: false as const, error }),
68
- );
69
-
70
- await vi.advanceTimersByTimeAsync(DEFAULT_SEND_GAP_MS);
71
- const firstResult = await first;
72
- expect(firstResult.ok).toBe(false);
73
- if (firstResult.ok) {
74
- throw new Error("expected first queue item to fail");
75
- }
76
- expect(firstResult.error).toBeInstanceOf(Error);
77
- expect(firstResult.error.message).toBe("boom");
78
-
79
- const second = enqueueSend("!room:example.org", async () => "ok");
80
- await vi.advanceTimersByTimeAsync(DEFAULT_SEND_GAP_MS);
81
- await expect(second).resolves.toBe("ok");
82
- });
83
-
84
- it("continues queued work when the head task fails", async () => {
85
- const gate = createDeferred<void>();
86
- const events: string[] = [];
87
-
88
- const first = enqueueSend("!room:example.org", async () => {
89
- events.push("start1");
90
- await gate.promise;
91
- throw new Error("boom");
92
- }).then(
93
- () => ({ ok: true as const }),
94
- (error) => ({ ok: false as const, error }),
95
- );
96
- const second = enqueueSend("!room:example.org", async () => {
97
- events.push("start2");
98
- return "two";
99
- });
100
-
101
- await vi.advanceTimersByTimeAsync(DEFAULT_SEND_GAP_MS);
102
- expect(events).toEqual(["start1"]);
103
-
104
- gate.resolve();
105
- const firstResult = await first;
106
- expect(firstResult.ok).toBe(false);
107
- if (firstResult.ok) {
108
- throw new Error("expected head queue item to fail");
109
- }
110
- expect(firstResult.error).toBeInstanceOf(Error);
111
-
112
- await vi.advanceTimersByTimeAsync(DEFAULT_SEND_GAP_MS);
113
- await expect(second).resolves.toBe("two");
114
- expect(events).toEqual(["start1", "start2"]);
115
- });
116
-
117
- it("supports custom gap and delay injection", async () => {
118
- const events: string[] = [];
119
- const delayFn = vi.fn(async (_ms: number) => {});
120
-
121
- const first = enqueueSend(
122
- "!room:example.org",
123
- async () => {
124
- events.push("first");
125
- return "one";
126
- },
127
- { gapMs: 7, delayFn },
128
- );
129
- const second = enqueueSend(
130
- "!room:example.org",
131
- async () => {
132
- events.push("second");
133
- return "two";
134
- },
135
- { gapMs: 7, delayFn },
136
- );
137
-
138
- await expect(first).resolves.toBe("one");
139
- await expect(second).resolves.toBe("two");
140
- expect(events).toEqual(["first", "second"]);
141
- expect(delayFn).toHaveBeenCalledTimes(2);
142
- expect(delayFn).toHaveBeenNthCalledWith(1, 7);
143
- expect(delayFn).toHaveBeenNthCalledWith(2, 7);
144
- });
145
- });
@@ -1,28 +0,0 @@
1
- import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
2
-
3
- export const DEFAULT_SEND_GAP_MS = 150;
4
-
5
- type MatrixSendQueueOptions = {
6
- gapMs?: number;
7
- delayFn?: (ms: number) => Promise<void>;
8
- };
9
-
10
- // Serialize sends per room to preserve Matrix delivery order.
11
- const roomQueues = new KeyedAsyncQueue();
12
-
13
- export function enqueueSend<T>(
14
- roomId: string,
15
- fn: () => Promise<T>,
16
- options?: MatrixSendQueueOptions,
17
- ): Promise<T> {
18
- const gapMs = options?.gapMs ?? DEFAULT_SEND_GAP_MS;
19
- const delayFn = options?.delayFn ?? delay;
20
- return roomQueues.enqueue(roomId, async () => {
21
- await delayFn(gapMs);
22
- return await fn();
23
- });
24
- }
25
-
26
- function delay(ms: number): Promise<void> {
27
- return new Promise((resolve) => setTimeout(resolve, ms));
28
- }