@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,319 +0,0 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk/matrix";
2
- import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
3
- import { setMatrixRuntime } from "../runtime.js";
4
- import { createMatrixBotSdkMock } from "../test-mocks.js";
5
-
6
- vi.mock("music-metadata", () => ({
7
- // `resolveMediaDurationMs` lazily imports `music-metadata`; in tests we don't
8
- // need real duration parsing and the real module is expensive to load.
9
- parseBuffer: vi.fn().mockResolvedValue({ format: {} }),
10
- }));
11
-
12
- vi.mock("@vector-im/matrix-bot-sdk", () =>
13
- createMatrixBotSdkMock({
14
- matrixClient: vi.fn(),
15
- simpleFsStorageProvider: vi.fn(),
16
- rustSdkCryptoStorageProvider: vi.fn(),
17
- }),
18
- );
19
-
20
- vi.mock("./send-queue.js", () => ({
21
- enqueueSend: async <T>(_roomId: string, fn: () => Promise<T>) => await fn(),
22
- }));
23
-
24
- const loadWebMediaMock = vi.fn().mockResolvedValue({
25
- buffer: Buffer.from("media"),
26
- fileName: "photo.png",
27
- contentType: "image/png",
28
- kind: "image",
29
- });
30
- const runtimeLoadConfigMock = vi.fn(() => ({}));
31
- const mediaKindFromMimeMock = vi.fn(() => "image");
32
- const isVoiceCompatibleAudioMock = vi.fn(() => false);
33
- const getImageMetadataMock = vi.fn().mockResolvedValue(null);
34
- const resizeToJpegMock = vi.fn();
35
-
36
- const runtimeStub = {
37
- config: {
38
- loadConfig: runtimeLoadConfigMock,
39
- },
40
- media: {
41
- loadWebMedia: loadWebMediaMock as unknown as PluginRuntime["media"]["loadWebMedia"],
42
- mediaKindFromMime:
43
- mediaKindFromMimeMock as unknown as PluginRuntime["media"]["mediaKindFromMime"],
44
- isVoiceCompatibleAudio:
45
- isVoiceCompatibleAudioMock as unknown as PluginRuntime["media"]["isVoiceCompatibleAudio"],
46
- getImageMetadata: getImageMetadataMock as unknown as PluginRuntime["media"]["getImageMetadata"],
47
- resizeToJpeg: resizeToJpegMock as unknown as PluginRuntime["media"]["resizeToJpeg"],
48
- },
49
- channel: {
50
- text: {
51
- resolveTextChunkLimit: () => 4000,
52
- resolveChunkMode: () => "length",
53
- chunkMarkdownText: (text: string) => (text ? [text] : []),
54
- chunkMarkdownTextWithMode: (text: string) => (text ? [text] : []),
55
- resolveMarkdownTableMode: () => "code",
56
- convertMarkdownTables: (text: string) => text,
57
- },
58
- },
59
- } as unknown as PluginRuntime;
60
-
61
- let sendMessageMatrix: typeof import("./send.js").sendMessageMatrix;
62
- let resolveMediaMaxBytes: typeof import("./send/client.js").resolveMediaMaxBytes;
63
-
64
- const makeClient = () => {
65
- const sendMessage = vi.fn().mockResolvedValue("evt1");
66
- const uploadContent = vi.fn().mockResolvedValue("mxc://example/file");
67
- const client = {
68
- sendMessage,
69
- uploadContent,
70
- getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
71
- } as unknown as import("@vector-im/matrix-bot-sdk").MatrixClient;
72
- return { client, sendMessage, uploadContent };
73
- };
74
-
75
- beforeAll(async () => {
76
- setMatrixRuntime(runtimeStub);
77
- ({ sendMessageMatrix } = await import("./send.js"));
78
- ({ resolveMediaMaxBytes } = await import("./send/client.js"));
79
- });
80
-
81
- describe("sendMessageMatrix media", () => {
82
- beforeEach(() => {
83
- vi.clearAllMocks();
84
- runtimeLoadConfigMock.mockReset();
85
- runtimeLoadConfigMock.mockReturnValue({});
86
- mediaKindFromMimeMock.mockReturnValue("image");
87
- isVoiceCompatibleAudioMock.mockReturnValue(false);
88
- setMatrixRuntime(runtimeStub);
89
- });
90
-
91
- it("uploads media with url payloads", async () => {
92
- const { client, sendMessage, uploadContent } = makeClient();
93
-
94
- await sendMessageMatrix("room:!room:example", "caption", {
95
- client,
96
- mediaUrl: "file:///tmp/photo.png",
97
- });
98
-
99
- const uploadArg = uploadContent.mock.calls[0]?.[0];
100
- expect(Buffer.isBuffer(uploadArg)).toBe(true);
101
-
102
- const content = sendMessage.mock.calls[0]?.[1] as {
103
- url?: string;
104
- msgtype?: string;
105
- format?: string;
106
- formatted_body?: string;
107
- };
108
- expect(content.msgtype).toBe("m.image");
109
- expect(content.format).toBe("org.matrix.custom.html");
110
- expect(content.formatted_body).toContain("caption");
111
- expect(content.url).toBe("mxc://example/file");
112
- });
113
-
114
- it("uploads encrypted media with file payloads", async () => {
115
- const { client, sendMessage, uploadContent } = makeClient();
116
- (client as { crypto?: object }).crypto = {
117
- isRoomEncrypted: vi.fn().mockResolvedValue(true),
118
- encryptMedia: vi.fn().mockResolvedValue({
119
- buffer: Buffer.from("encrypted"),
120
- file: {
121
- key: {
122
- kty: "oct",
123
- key_ops: ["encrypt", "decrypt"],
124
- alg: "A256CTR",
125
- k: "secret",
126
- ext: true,
127
- },
128
- iv: "iv",
129
- hashes: { sha256: "hash" },
130
- v: "v2",
131
- },
132
- }),
133
- };
134
-
135
- await sendMessageMatrix("room:!room:example", "caption", {
136
- client,
137
- mediaUrl: "file:///tmp/photo.png",
138
- });
139
-
140
- const uploadArg = uploadContent.mock.calls[0]?.[0] as Buffer | undefined;
141
- expect(uploadArg?.toString()).toBe("encrypted");
142
-
143
- const content = sendMessage.mock.calls[0]?.[1] as {
144
- url?: string;
145
- file?: { url?: string };
146
- };
147
- expect(content.url).toBeUndefined();
148
- expect(content.file?.url).toBe("mxc://example/file");
149
- });
150
-
151
- it("marks voice metadata and sends caption follow-up when audioAsVoice is compatible", async () => {
152
- const { client, sendMessage } = makeClient();
153
- mediaKindFromMimeMock.mockReturnValue("audio");
154
- isVoiceCompatibleAudioMock.mockReturnValue(true);
155
- loadWebMediaMock.mockResolvedValueOnce({
156
- buffer: Buffer.from("audio"),
157
- fileName: "clip.mp3",
158
- contentType: "audio/mpeg",
159
- kind: "audio",
160
- });
161
-
162
- await sendMessageMatrix("room:!room:example", "voice caption", {
163
- client,
164
- mediaUrl: "file:///tmp/clip.mp3",
165
- audioAsVoice: true,
166
- });
167
-
168
- expect(isVoiceCompatibleAudioMock).toHaveBeenCalledWith({
169
- contentType: "audio/mpeg",
170
- fileName: "clip.mp3",
171
- });
172
- expect(sendMessage).toHaveBeenCalledTimes(2);
173
- const mediaContent = sendMessage.mock.calls[0]?.[1] as {
174
- msgtype?: string;
175
- body?: string;
176
- "org.matrix.msc3245.voice"?: Record<string, never>;
177
- };
178
- expect(mediaContent.msgtype).toBe("m.audio");
179
- expect(mediaContent.body).toBe("Voice message");
180
- expect(mediaContent["org.matrix.msc3245.voice"]).toEqual({});
181
- });
182
-
183
- it("keeps regular audio payload when audioAsVoice media is incompatible", async () => {
184
- const { client, sendMessage } = makeClient();
185
- mediaKindFromMimeMock.mockReturnValue("audio");
186
- isVoiceCompatibleAudioMock.mockReturnValue(false);
187
- loadWebMediaMock.mockResolvedValueOnce({
188
- buffer: Buffer.from("audio"),
189
- fileName: "clip.wav",
190
- contentType: "audio/wav",
191
- kind: "audio",
192
- });
193
-
194
- await sendMessageMatrix("room:!room:example", "voice caption", {
195
- client,
196
- mediaUrl: "file:///tmp/clip.wav",
197
- audioAsVoice: true,
198
- });
199
-
200
- expect(sendMessage).toHaveBeenCalledTimes(1);
201
- const mediaContent = sendMessage.mock.calls[0]?.[1] as {
202
- msgtype?: string;
203
- body?: string;
204
- "org.matrix.msc3245.voice"?: Record<string, never>;
205
- };
206
- expect(mediaContent.msgtype).toBe("m.audio");
207
- expect(mediaContent.body).toBe("voice caption");
208
- expect(mediaContent["org.matrix.msc3245.voice"]).toBeUndefined();
209
- });
210
- });
211
-
212
- describe("sendMessageMatrix threads", () => {
213
- beforeEach(() => {
214
- vi.clearAllMocks();
215
- runtimeLoadConfigMock.mockReset();
216
- runtimeLoadConfigMock.mockReturnValue({});
217
- setMatrixRuntime(runtimeStub);
218
- });
219
-
220
- it("includes thread relation metadata when threadId is set", async () => {
221
- const { client, sendMessage } = makeClient();
222
-
223
- await sendMessageMatrix("room:!room:example", "hello thread", {
224
- client,
225
- threadId: "$thread",
226
- });
227
-
228
- const content = sendMessage.mock.calls[0]?.[1] as {
229
- "m.relates_to"?: {
230
- rel_type?: string;
231
- event_id?: string;
232
- "m.in_reply_to"?: { event_id?: string };
233
- };
234
- };
235
-
236
- expect(content["m.relates_to"]).toMatchObject({
237
- rel_type: "m.thread",
238
- event_id: "$thread",
239
- "m.in_reply_to": { event_id: "$thread" },
240
- });
241
- });
242
- });
243
-
244
- describe("sendMessageMatrix cfg threading", () => {
245
- beforeEach(() => {
246
- vi.clearAllMocks();
247
- runtimeLoadConfigMock.mockReset();
248
- runtimeLoadConfigMock.mockReturnValue({
249
- channels: {
250
- matrix: {
251
- mediaMaxMb: 7,
252
- },
253
- },
254
- });
255
- setMatrixRuntime(runtimeStub);
256
- });
257
-
258
- it("does not call runtime loadConfig when cfg is provided", async () => {
259
- const { client } = makeClient();
260
- const providedCfg = {
261
- channels: {
262
- matrix: {
263
- mediaMaxMb: 4,
264
- },
265
- },
266
- };
267
-
268
- await sendMessageMatrix("room:!room:example", "hello cfg", {
269
- client,
270
- cfg: providedCfg as any,
271
- });
272
-
273
- expect(runtimeLoadConfigMock).not.toHaveBeenCalled();
274
- });
275
-
276
- it("falls back to runtime loadConfig when cfg is omitted", async () => {
277
- const { client } = makeClient();
278
-
279
- await sendMessageMatrix("room:!room:example", "hello runtime", { client });
280
-
281
- expect(runtimeLoadConfigMock).toHaveBeenCalledTimes(1);
282
- });
283
- });
284
-
285
- describe("resolveMediaMaxBytes cfg threading", () => {
286
- beforeEach(() => {
287
- runtimeLoadConfigMock.mockReset();
288
- runtimeLoadConfigMock.mockReturnValue({
289
- channels: {
290
- matrix: {
291
- mediaMaxMb: 9,
292
- },
293
- },
294
- });
295
- setMatrixRuntime(runtimeStub);
296
- });
297
-
298
- it("uses provided cfg and skips runtime loadConfig", () => {
299
- const providedCfg = {
300
- channels: {
301
- matrix: {
302
- mediaMaxMb: 3,
303
- },
304
- },
305
- };
306
-
307
- const maxBytes = resolveMediaMaxBytes(undefined, providedCfg as any);
308
-
309
- expect(maxBytes).toBe(3 * 1024 * 1024);
310
- expect(runtimeLoadConfigMock).not.toHaveBeenCalled();
311
- });
312
-
313
- it("falls back to runtime loadConfig when cfg is omitted", () => {
314
- const maxBytes = resolveMediaMaxBytes();
315
-
316
- expect(maxBytes).toBe(9 * 1024 * 1024);
317
- expect(runtimeLoadConfigMock).toHaveBeenCalledTimes(1);
318
- });
319
- });
@@ -1,267 +0,0 @@
1
- import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { PollInput } from "openclaw/plugin-sdk/matrix";
3
- import { getMatrixRuntime } from "../runtime.js";
4
- import { buildPollStartContent, M_POLL_START } from "./poll-types.js";
5
- import { enqueueSend } from "./send-queue.js";
6
- import { resolveMatrixClient, resolveMediaMaxBytes } from "./send/client.js";
7
- import {
8
- buildReplyRelation,
9
- buildTextContent,
10
- buildThreadRelation,
11
- resolveMatrixMsgType,
12
- resolveMatrixVoiceDecision,
13
- } from "./send/formatting.js";
14
- import {
15
- buildMediaContent,
16
- prepareImageInfo,
17
- resolveMediaDurationMs,
18
- uploadMediaMaybeEncrypted,
19
- } from "./send/media.js";
20
- import { normalizeThreadId, resolveMatrixRoomId } from "./send/targets.js";
21
- import {
22
- EventType,
23
- MsgType,
24
- RelationType,
25
- type MatrixOutboundContent,
26
- type MatrixSendOpts,
27
- type MatrixSendResult,
28
- type ReactionEventContent,
29
- } from "./send/types.js";
30
-
31
- const MATRIX_TEXT_LIMIT = 4000;
32
- const getCore = () => getMatrixRuntime();
33
-
34
- export type { MatrixSendOpts, MatrixSendResult } from "./send/types.js";
35
- export { resolveMatrixRoomId } from "./send/targets.js";
36
-
37
- export async function sendMessageMatrix(
38
- to: string,
39
- message: string,
40
- opts: MatrixSendOpts = {},
41
- ): Promise<MatrixSendResult> {
42
- const trimmedMessage = message?.trim() ?? "";
43
- if (!trimmedMessage && !opts.mediaUrl) {
44
- throw new Error("Matrix send requires text or media");
45
- }
46
- const { client, stopOnDone } = await resolveMatrixClient({
47
- client: opts.client,
48
- timeoutMs: opts.timeoutMs,
49
- accountId: opts.accountId,
50
- cfg: opts.cfg,
51
- });
52
- const cfg = opts.cfg ?? getCore().config.loadConfig();
53
- try {
54
- const roomId = await resolveMatrixRoomId(client, to);
55
- return await enqueueSend(roomId, async () => {
56
- const tableMode = getCore().channel.text.resolveMarkdownTableMode({
57
- cfg,
58
- channel: "matrix",
59
- accountId: opts.accountId,
60
- });
61
- const convertedMessage = getCore().channel.text.convertMarkdownTables(
62
- trimmedMessage,
63
- tableMode,
64
- );
65
- const textLimit = getCore().channel.text.resolveTextChunkLimit(cfg, "matrix");
66
- const chunkLimit = Math.min(textLimit, MATRIX_TEXT_LIMIT);
67
- const chunkMode = getCore().channel.text.resolveChunkMode(cfg, "matrix", opts.accountId);
68
- const chunks = getCore().channel.text.chunkMarkdownTextWithMode(
69
- convertedMessage,
70
- chunkLimit,
71
- chunkMode,
72
- );
73
- const threadId = normalizeThreadId(opts.threadId);
74
- const relation = threadId
75
- ? buildThreadRelation(threadId, opts.replyToId)
76
- : buildReplyRelation(opts.replyToId);
77
- const sendContent = async (content: MatrixOutboundContent) => {
78
- // @vector-im/matrix-bot-sdk uses sendMessage differently
79
- const eventId = await client.sendMessage(roomId, content);
80
- return eventId;
81
- };
82
-
83
- let lastMessageId = "";
84
- if (opts.mediaUrl) {
85
- const maxBytes = resolveMediaMaxBytes(opts.accountId, cfg);
86
- const media = await getCore().media.loadWebMedia(opts.mediaUrl, maxBytes);
87
- const uploaded = await uploadMediaMaybeEncrypted(client, roomId, media.buffer, {
88
- contentType: media.contentType,
89
- filename: media.fileName,
90
- });
91
- const durationMs = await resolveMediaDurationMs({
92
- buffer: media.buffer,
93
- contentType: media.contentType,
94
- fileName: media.fileName,
95
- kind: media.kind ?? "unknown",
96
- });
97
- const baseMsgType = resolveMatrixMsgType(media.contentType, media.fileName);
98
- const { useVoice } = resolveMatrixVoiceDecision({
99
- wantsVoice: opts.audioAsVoice === true,
100
- contentType: media.contentType,
101
- fileName: media.fileName,
102
- });
103
- const msgtype = useVoice ? MsgType.Audio : baseMsgType;
104
- const isImage = msgtype === MsgType.Image;
105
- const imageInfo = isImage
106
- ? await prepareImageInfo({ buffer: media.buffer, client })
107
- : undefined;
108
- const [firstChunk, ...rest] = chunks;
109
- const body = useVoice ? "Voice message" : (firstChunk ?? media.fileName ?? "(file)");
110
- const content = buildMediaContent({
111
- msgtype,
112
- body,
113
- url: uploaded.url,
114
- file: uploaded.file,
115
- filename: media.fileName,
116
- mimetype: media.contentType,
117
- size: media.buffer.byteLength,
118
- durationMs,
119
- relation,
120
- isVoice: useVoice,
121
- imageInfo,
122
- });
123
- const eventId = await sendContent(content);
124
- lastMessageId = eventId ?? lastMessageId;
125
- const textChunks = useVoice ? chunks : rest;
126
- const followupRelation = threadId ? relation : undefined;
127
- for (const chunk of textChunks) {
128
- const text = chunk.trim();
129
- if (!text) {
130
- continue;
131
- }
132
- const followup = buildTextContent(text, followupRelation);
133
- const followupEventId = await sendContent(followup);
134
- lastMessageId = followupEventId ?? lastMessageId;
135
- }
136
- } else {
137
- for (const chunk of chunks.length ? chunks : [""]) {
138
- const text = chunk.trim();
139
- if (!text) {
140
- continue;
141
- }
142
- const content = buildTextContent(text, relation);
143
- const eventId = await sendContent(content);
144
- lastMessageId = eventId ?? lastMessageId;
145
- }
146
- }
147
-
148
- return {
149
- messageId: lastMessageId || "unknown",
150
- roomId,
151
- };
152
- });
153
- } finally {
154
- if (stopOnDone) {
155
- client.stop();
156
- }
157
- }
158
- }
159
-
160
- export async function sendPollMatrix(
161
- to: string,
162
- poll: PollInput,
163
- opts: MatrixSendOpts = {},
164
- ): Promise<{ eventId: string; roomId: string }> {
165
- if (!poll.question?.trim()) {
166
- throw new Error("Matrix poll requires a question");
167
- }
168
- if (!poll.options?.length) {
169
- throw new Error("Matrix poll requires options");
170
- }
171
- const { client, stopOnDone } = await resolveMatrixClient({
172
- client: opts.client,
173
- timeoutMs: opts.timeoutMs,
174
- accountId: opts.accountId,
175
- cfg: opts.cfg,
176
- });
177
-
178
- try {
179
- const roomId = await resolveMatrixRoomId(client, to);
180
- const pollContent = buildPollStartContent(poll);
181
- const threadId = normalizeThreadId(opts.threadId);
182
- const pollPayload = threadId
183
- ? { ...pollContent, "m.relates_to": buildThreadRelation(threadId) }
184
- : pollContent;
185
- // @vector-im/matrix-bot-sdk sendEvent returns eventId string directly
186
- const eventId = await client.sendEvent(roomId, M_POLL_START, pollPayload);
187
-
188
- return {
189
- eventId: eventId ?? "unknown",
190
- roomId,
191
- };
192
- } finally {
193
- if (stopOnDone) {
194
- client.stop();
195
- }
196
- }
197
- }
198
-
199
- export async function sendTypingMatrix(
200
- roomId: string,
201
- typing: boolean,
202
- timeoutMs?: number,
203
- client?: MatrixClient,
204
- ): Promise<void> {
205
- const { client: resolved, stopOnDone } = await resolveMatrixClient({
206
- client,
207
- timeoutMs,
208
- });
209
- try {
210
- const resolvedTimeoutMs = typeof timeoutMs === "number" ? timeoutMs : 30_000;
211
- await resolved.setTyping(roomId, typing, resolvedTimeoutMs);
212
- } finally {
213
- if (stopOnDone) {
214
- resolved.stop();
215
- }
216
- }
217
- }
218
-
219
- export async function sendReadReceiptMatrix(
220
- roomId: string,
221
- eventId: string,
222
- client?: MatrixClient,
223
- ): Promise<void> {
224
- if (!eventId?.trim()) {
225
- return;
226
- }
227
- const { client: resolved, stopOnDone } = await resolveMatrixClient({
228
- client,
229
- });
230
- try {
231
- const resolvedRoom = await resolveMatrixRoomId(resolved, roomId);
232
- await resolved.sendReadReceipt(resolvedRoom, eventId.trim());
233
- } finally {
234
- if (stopOnDone) {
235
- resolved.stop();
236
- }
237
- }
238
- }
239
-
240
- export async function reactMatrixMessage(
241
- roomId: string,
242
- messageId: string,
243
- emoji: string,
244
- client?: MatrixClient,
245
- ): Promise<void> {
246
- if (!emoji.trim()) {
247
- throw new Error("Matrix reaction requires an emoji");
248
- }
249
- const { client: resolved, stopOnDone } = await resolveMatrixClient({
250
- client,
251
- });
252
- try {
253
- const resolvedRoom = await resolveMatrixRoomId(resolved, roomId);
254
- const reaction: ReactionEventContent = {
255
- "m.relates_to": {
256
- rel_type: RelationType.Annotation,
257
- event_id: messageId,
258
- key: emoji,
259
- },
260
- };
261
- await resolved.sendEvent(resolvedRoom, EventType.Reaction, reaction);
262
- } finally {
263
- if (stopOnDone) {
264
- resolved.stop();
265
- }
266
- }
267
- }