@openclaw/matrix 2026.3.12 → 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 (205) 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 -98
  114. package/index.ts +0 -22
  115. package/src/actions.ts +0 -195
  116. package/src/channel.directory.test.ts +0 -154
  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 -100
  160. package/src/matrix/monitor/auto-join.ts +0 -72
  161. package/src/matrix/monitor/direct.test.ts +0 -400
  162. package/src/matrix/monitor/direct.ts +0 -152
  163. package/src/matrix/monitor/events.test.ts +0 -172
  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 -767
  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 -154
  194. package/src/matrix/send-queue.ts +0 -28
  195. package/src/matrix/send.test.ts +0 -326
  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 -67
  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/tool-actions.ts +0 -164
  205. package/src/types.ts +0 -118
@@ -1,86 +0,0 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk/matrix";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
- import { setMatrixRuntime } from "../../runtime.js";
4
- import { downloadMatrixMedia } from "./media.js";
5
-
6
- describe("downloadMatrixMedia", () => {
7
- const saveMediaBuffer = vi.fn().mockResolvedValue({
8
- path: "/tmp/media",
9
- contentType: "image/png",
10
- });
11
-
12
- const runtimeStub = {
13
- channel: {
14
- media: {
15
- saveMediaBuffer: (...args: unknown[]) => saveMediaBuffer(...args),
16
- },
17
- },
18
- } as unknown as PluginRuntime;
19
-
20
- beforeEach(() => {
21
- vi.clearAllMocks();
22
- setMatrixRuntime(runtimeStub);
23
- });
24
-
25
- function makeEncryptedMediaFixture() {
26
- const decryptMedia = vi.fn().mockResolvedValue(Buffer.from("decrypted"));
27
- const client = {
28
- crypto: { decryptMedia },
29
- mxcToHttp: vi.fn().mockReturnValue("https://example/mxc"),
30
- } as unknown as import("@vector-im/matrix-bot-sdk").MatrixClient;
31
- const file = {
32
- url: "mxc://example/file",
33
- key: {
34
- kty: "oct",
35
- key_ops: ["encrypt", "decrypt"],
36
- alg: "A256CTR",
37
- k: "secret",
38
- ext: true,
39
- },
40
- iv: "iv",
41
- hashes: { sha256: "hash" },
42
- v: "v2",
43
- };
44
- return { decryptMedia, client, file };
45
- }
46
-
47
- it("decrypts encrypted media when file payloads are present", async () => {
48
- const { decryptMedia, client, file } = makeEncryptedMediaFixture();
49
-
50
- const result = await downloadMatrixMedia({
51
- client,
52
- mxcUrl: "mxc://example/file",
53
- contentType: "image/png",
54
- maxBytes: 1024,
55
- file,
56
- });
57
-
58
- // decryptMedia should be called with just the file object (it handles download internally)
59
- expect(decryptMedia).toHaveBeenCalledWith(file);
60
- expect(saveMediaBuffer).toHaveBeenCalledWith(
61
- Buffer.from("decrypted"),
62
- "image/png",
63
- "inbound",
64
- 1024,
65
- );
66
- expect(result?.path).toBe("/tmp/media");
67
- });
68
-
69
- it("rejects encrypted media that exceeds maxBytes before decrypting", async () => {
70
- const { decryptMedia, client, file } = makeEncryptedMediaFixture();
71
-
72
- await expect(
73
- downloadMatrixMedia({
74
- client,
75
- mxcUrl: "mxc://example/file",
76
- contentType: "image/png",
77
- sizeBytes: 2048,
78
- maxBytes: 1024,
79
- file,
80
- }),
81
- ).rejects.toThrow("Matrix media exceeds configured size limit");
82
-
83
- expect(decryptMedia).not.toHaveBeenCalled();
84
- expect(saveMediaBuffer).not.toHaveBeenCalled();
85
- });
86
- });
@@ -1,118 +0,0 @@
1
- import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import { getMatrixRuntime } from "../../runtime.js";
3
-
4
- // Type for encrypted file info
5
- type EncryptedFile = {
6
- url: string;
7
- key: {
8
- kty: string;
9
- key_ops: string[];
10
- alg: string;
11
- k: string;
12
- ext: boolean;
13
- };
14
- iv: string;
15
- hashes: Record<string, string>;
16
- v: string;
17
- };
18
-
19
- async function fetchMatrixMediaBuffer(params: {
20
- client: MatrixClient;
21
- mxcUrl: string;
22
- maxBytes: number;
23
- }): Promise<{ buffer: Buffer; headerType?: string } | null> {
24
- // @vector-im/matrix-bot-sdk provides mxcToHttp helper
25
- const url = params.client.mxcToHttp(params.mxcUrl);
26
- if (!url) {
27
- return null;
28
- }
29
-
30
- // Use the client's download method which handles auth
31
- try {
32
- const result = await params.client.downloadContent(params.mxcUrl);
33
- const raw = result.data ?? result;
34
- const buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
35
-
36
- if (buffer.byteLength > params.maxBytes) {
37
- throw new Error("Matrix media exceeds configured size limit");
38
- }
39
- return { buffer, headerType: result.contentType };
40
- } catch (err) {
41
- throw new Error(`Matrix media download failed: ${String(err)}`, { cause: err });
42
- }
43
- }
44
-
45
- /**
46
- * Download and decrypt encrypted media from a Matrix room.
47
- * Uses @vector-im/matrix-bot-sdk's decryptMedia which handles both download and decryption.
48
- */
49
- async function fetchEncryptedMediaBuffer(params: {
50
- client: MatrixClient;
51
- file: EncryptedFile;
52
- maxBytes: number;
53
- }): Promise<{ buffer: Buffer } | null> {
54
- if (!params.client.crypto) {
55
- throw new Error("Cannot decrypt media: crypto not enabled");
56
- }
57
-
58
- // decryptMedia handles downloading and decrypting the encrypted content internally
59
- const decrypted = await params.client.crypto.decryptMedia(
60
- params.file as Parameters<typeof params.client.crypto.decryptMedia>[0],
61
- );
62
-
63
- if (decrypted.byteLength > params.maxBytes) {
64
- throw new Error("Matrix media exceeds configured size limit");
65
- }
66
-
67
- return { buffer: decrypted };
68
- }
69
-
70
- export async function downloadMatrixMedia(params: {
71
- client: MatrixClient;
72
- mxcUrl: string;
73
- contentType?: string;
74
- sizeBytes?: number;
75
- maxBytes: number;
76
- file?: EncryptedFile;
77
- }): Promise<{
78
- path: string;
79
- contentType?: string;
80
- placeholder: string;
81
- } | null> {
82
- let fetched: { buffer: Buffer; headerType?: string } | null;
83
- if (typeof params.sizeBytes === "number" && params.sizeBytes > params.maxBytes) {
84
- throw new Error("Matrix media exceeds configured size limit");
85
- }
86
-
87
- if (params.file) {
88
- // Encrypted media
89
- fetched = await fetchEncryptedMediaBuffer({
90
- client: params.client,
91
- file: params.file,
92
- maxBytes: params.maxBytes,
93
- });
94
- } else {
95
- // Unencrypted media
96
- fetched = await fetchMatrixMediaBuffer({
97
- client: params.client,
98
- mxcUrl: params.mxcUrl,
99
- maxBytes: params.maxBytes,
100
- });
101
- }
102
-
103
- if (!fetched) {
104
- return null;
105
- }
106
- const headerType = fetched.headerType ?? params.contentType ?? undefined;
107
- const saved = await getMatrixRuntime().channel.media.saveMediaBuffer(
108
- fetched.buffer,
109
- headerType,
110
- "inbound",
111
- params.maxBytes,
112
- );
113
- return {
114
- path: saved.path,
115
- contentType: saved.contentType,
116
- placeholder: "[matrix media]",
117
- };
118
- }
@@ -1,154 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- // Mock the runtime before importing resolveMentions
4
- vi.mock("../../runtime.js", () => ({
5
- getMatrixRuntime: () => ({
6
- channel: {
7
- mentions: {
8
- matchesMentionPatterns: (text: string, patterns: RegExp[]) =>
9
- patterns.some((p) => p.test(text)),
10
- },
11
- },
12
- }),
13
- }));
14
-
15
- import { resolveMentions } from "./mentions.js";
16
-
17
- describe("resolveMentions", () => {
18
- const userId = "@bot:matrix.org";
19
- const mentionRegexes = [/@bot/i];
20
-
21
- describe("m.mentions field", () => {
22
- it("detects mention via m.mentions.user_ids", () => {
23
- const result = resolveMentions({
24
- content: {
25
- msgtype: "m.text",
26
- body: "hello",
27
- "m.mentions": { user_ids: ["@bot:matrix.org"] },
28
- },
29
- userId,
30
- text: "hello",
31
- mentionRegexes,
32
- });
33
- expect(result.wasMentioned).toBe(true);
34
- expect(result.hasExplicitMention).toBe(true);
35
- });
36
-
37
- it("detects room mention via m.mentions.room", () => {
38
- const result = resolveMentions({
39
- content: {
40
- msgtype: "m.text",
41
- body: "hello everyone",
42
- "m.mentions": { room: true },
43
- },
44
- userId,
45
- text: "hello everyone",
46
- mentionRegexes,
47
- });
48
- expect(result.wasMentioned).toBe(true);
49
- });
50
- });
51
-
52
- describe("formatted_body matrix.to links", () => {
53
- it("detects mention in formatted_body with plain user ID", () => {
54
- const result = resolveMentions({
55
- content: {
56
- msgtype: "m.text",
57
- body: "Bot: hello",
58
- formatted_body: '<a href="https://matrix.to/#/@bot:matrix.org">Bot</a>: hello',
59
- },
60
- userId,
61
- text: "Bot: hello",
62
- mentionRegexes: [],
63
- });
64
- expect(result.wasMentioned).toBe(true);
65
- });
66
-
67
- it("detects mention in formatted_body with URL-encoded user ID", () => {
68
- const result = resolveMentions({
69
- content: {
70
- msgtype: "m.text",
71
- body: "Bot: hello",
72
- formatted_body: '<a href="https://matrix.to/#/%40bot%3Amatrix.org">Bot</a>: hello',
73
- },
74
- userId,
75
- text: "Bot: hello",
76
- mentionRegexes: [],
77
- });
78
- expect(result.wasMentioned).toBe(true);
79
- });
80
-
81
- it("detects mention with single quotes in href", () => {
82
- const result = resolveMentions({
83
- content: {
84
- msgtype: "m.text",
85
- body: "Bot: hello",
86
- formatted_body: "<a href='https://matrix.to/#/@bot:matrix.org'>Bot</a>: hello",
87
- },
88
- userId,
89
- text: "Bot: hello",
90
- mentionRegexes: [],
91
- });
92
- expect(result.wasMentioned).toBe(true);
93
- });
94
-
95
- it("does not detect mention for different user ID", () => {
96
- const result = resolveMentions({
97
- content: {
98
- msgtype: "m.text",
99
- body: "Other: hello",
100
- formatted_body: '<a href="https://matrix.to/#/@other:matrix.org">Other</a>: hello',
101
- },
102
- userId,
103
- text: "Other: hello",
104
- mentionRegexes: [],
105
- });
106
- expect(result.wasMentioned).toBe(false);
107
- });
108
-
109
- it("does not false-positive on partial user ID match", () => {
110
- const result = resolveMentions({
111
- content: {
112
- msgtype: "m.text",
113
- body: "Bot2: hello",
114
- formatted_body: '<a href="https://matrix.to/#/@bot2:matrix.org">Bot2</a>: hello',
115
- },
116
- userId: "@bot:matrix.org",
117
- text: "Bot2: hello",
118
- mentionRegexes: [],
119
- });
120
- expect(result.wasMentioned).toBe(false);
121
- });
122
- });
123
-
124
- describe("regex patterns", () => {
125
- it("detects mention via regex pattern in body text", () => {
126
- const result = resolveMentions({
127
- content: {
128
- msgtype: "m.text",
129
- body: "hey @bot can you help?",
130
- },
131
- userId,
132
- text: "hey @bot can you help?",
133
- mentionRegexes,
134
- });
135
- expect(result.wasMentioned).toBe(true);
136
- });
137
- });
138
-
139
- describe("no mention", () => {
140
- it("returns false when no mention is present", () => {
141
- const result = resolveMentions({
142
- content: {
143
- msgtype: "m.text",
144
- body: "hello world",
145
- },
146
- userId,
147
- text: "hello world",
148
- mentionRegexes,
149
- });
150
- expect(result.wasMentioned).toBe(false);
151
- expect(result.hasExplicitMention).toBe(false);
152
- });
153
- });
154
- });
@@ -1,62 +0,0 @@
1
- import { getMatrixRuntime } from "../../runtime.js";
2
-
3
- // Type for room message content with mentions
4
- type MessageContentWithMentions = {
5
- msgtype: string;
6
- body: string;
7
- formatted_body?: string;
8
- "m.mentions"?: {
9
- user_ids?: string[];
10
- room?: boolean;
11
- };
12
- };
13
-
14
- /**
15
- * Check if the formatted_body contains a matrix.to mention link for the given user ID.
16
- * Many Matrix clients (including Element) use HTML links in formatted_body instead of
17
- * or in addition to the m.mentions field.
18
- */
19
- function checkFormattedBodyMention(formattedBody: string | undefined, userId: string): boolean {
20
- if (!formattedBody || !userId) {
21
- return false;
22
- }
23
- // Escape special regex characters in the user ID (e.g., @user:matrix.org)
24
- const escapedUserId = userId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
- // Match matrix.to links with the user ID, handling both URL-encoded and plain formats
26
- // Example: href="https://matrix.to/#/@user:matrix.org" or href="https://matrix.to/#/%40user%3Amatrix.org"
27
- const plainPattern = new RegExp(`href=["']https://matrix\\.to/#/${escapedUserId}["']`, "i");
28
- if (plainPattern.test(formattedBody)) {
29
- return true;
30
- }
31
- // Also check URL-encoded version (@ -> %40, : -> %3A)
32
- const encodedUserId = encodeURIComponent(userId).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
- const encodedPattern = new RegExp(`href=["']https://matrix\\.to/#/${encodedUserId}["']`, "i");
34
- return encodedPattern.test(formattedBody);
35
- }
36
-
37
- export function resolveMentions(params: {
38
- content: MessageContentWithMentions;
39
- userId?: string | null;
40
- text?: string;
41
- mentionRegexes: RegExp[];
42
- }) {
43
- const mentions = params.content["m.mentions"];
44
- const mentionedUsers = Array.isArray(mentions?.user_ids)
45
- ? new Set(mentions.user_ids)
46
- : new Set<string>();
47
-
48
- // Check formatted_body for matrix.to mention links (legacy/alternative mention format)
49
- const mentionedInFormattedBody = params.userId
50
- ? checkFormattedBodyMention(params.content.formatted_body, params.userId)
51
- : false;
52
-
53
- const wasMentioned =
54
- Boolean(mentions?.room) ||
55
- (params.userId ? mentionedUsers.has(params.userId) : false) ||
56
- mentionedInFormattedBody ||
57
- getMatrixRuntime().channel.mentions.matchesMentionPatterns(
58
- params.text ?? "",
59
- params.mentionRegexes,
60
- );
61
- return { wasMentioned, hasExplicitMention: Boolean(mentions) };
62
- }
@@ -1,184 +0,0 @@
1
- import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/matrix";
3
- import { beforeEach, describe, expect, it, vi } from "vitest";
4
-
5
- const sendMessageMatrixMock = vi.hoisted(() => vi.fn().mockResolvedValue({ messageId: "mx-1" }));
6
-
7
- vi.mock("../send.js", () => ({
8
- sendMessageMatrix: (to: string, message: string, opts?: unknown) =>
9
- sendMessageMatrixMock(to, message, opts),
10
- }));
11
-
12
- import { setMatrixRuntime } from "../../runtime.js";
13
- import { deliverMatrixReplies } from "./replies.js";
14
-
15
- describe("deliverMatrixReplies", () => {
16
- const loadConfigMock = vi.fn(() => ({}));
17
- const resolveMarkdownTableModeMock = vi.fn(() => "code");
18
- const convertMarkdownTablesMock = vi.fn((text: string) => text);
19
- const resolveChunkModeMock = vi.fn(() => "length");
20
- const chunkMarkdownTextWithModeMock = vi.fn((text: string) => [text]);
21
-
22
- const runtimeStub = {
23
- config: {
24
- loadConfig: () => loadConfigMock(),
25
- },
26
- channel: {
27
- text: {
28
- resolveMarkdownTableMode: () => resolveMarkdownTableModeMock(),
29
- convertMarkdownTables: (text: string) => convertMarkdownTablesMock(text),
30
- resolveChunkMode: () => resolveChunkModeMock(),
31
- chunkMarkdownTextWithMode: (text: string) => chunkMarkdownTextWithModeMock(text),
32
- },
33
- },
34
- logging: {
35
- shouldLogVerbose: () => false,
36
- },
37
- } as unknown as PluginRuntime;
38
-
39
- const runtimeEnv: RuntimeEnv = {
40
- log: vi.fn(),
41
- error: vi.fn(),
42
- } as unknown as RuntimeEnv;
43
-
44
- beforeEach(() => {
45
- vi.clearAllMocks();
46
- setMatrixRuntime(runtimeStub);
47
- chunkMarkdownTextWithModeMock.mockImplementation((text: string) => [text]);
48
- });
49
-
50
- it("keeps replyToId on first reply only when replyToMode=first", async () => {
51
- chunkMarkdownTextWithModeMock.mockImplementation((text: string) => text.split("|"));
52
-
53
- await deliverMatrixReplies({
54
- replies: [
55
- { text: "first-a|first-b", replyToId: "reply-1" },
56
- { text: "second", replyToId: "reply-2" },
57
- ],
58
- roomId: "room:1",
59
- client: {} as MatrixClient,
60
- runtime: runtimeEnv,
61
- textLimit: 4000,
62
- replyToMode: "first",
63
- });
64
-
65
- expect(sendMessageMatrixMock).toHaveBeenCalledTimes(3);
66
- expect(sendMessageMatrixMock.mock.calls[0]?.[2]).toEqual(
67
- expect.objectContaining({ replyToId: "reply-1", threadId: undefined }),
68
- );
69
- expect(sendMessageMatrixMock.mock.calls[1]?.[2]).toEqual(
70
- expect.objectContaining({ replyToId: "reply-1", threadId: undefined }),
71
- );
72
- expect(sendMessageMatrixMock.mock.calls[2]?.[2]).toEqual(
73
- expect.objectContaining({ replyToId: undefined, threadId: undefined }),
74
- );
75
- });
76
-
77
- it("keeps replyToId on every reply when replyToMode=all", async () => {
78
- await deliverMatrixReplies({
79
- replies: [
80
- {
81
- text: "caption",
82
- mediaUrls: ["https://example.com/a.jpg", "https://example.com/b.jpg"],
83
- replyToId: "reply-media",
84
- audioAsVoice: true,
85
- },
86
- { text: "plain", replyToId: "reply-text" },
87
- ],
88
- roomId: "room:2",
89
- client: {} as MatrixClient,
90
- runtime: runtimeEnv,
91
- textLimit: 4000,
92
- replyToMode: "all",
93
- });
94
-
95
- expect(sendMessageMatrixMock).toHaveBeenCalledTimes(3);
96
- expect(sendMessageMatrixMock.mock.calls[0]).toEqual([
97
- "room:2",
98
- "caption",
99
- expect.objectContaining({ mediaUrl: "https://example.com/a.jpg", replyToId: "reply-media" }),
100
- ]);
101
- expect(sendMessageMatrixMock.mock.calls[1]).toEqual([
102
- "room:2",
103
- "",
104
- expect.objectContaining({ mediaUrl: "https://example.com/b.jpg", replyToId: "reply-media" }),
105
- ]);
106
- expect(sendMessageMatrixMock.mock.calls[2]?.[2]).toEqual(
107
- expect.objectContaining({ replyToId: "reply-text" }),
108
- );
109
- });
110
-
111
- it("skips reasoning-only replies with Reasoning prefix", async () => {
112
- await deliverMatrixReplies({
113
- replies: [
114
- { text: "Reasoning:\nThe user wants X because Y.", replyToId: "r1" },
115
- { text: "Here is the answer.", replyToId: "r2" },
116
- ],
117
- roomId: "room:reason",
118
- client: {} as MatrixClient,
119
- runtime: runtimeEnv,
120
- textLimit: 4000,
121
- replyToMode: "first",
122
- });
123
-
124
- expect(sendMessageMatrixMock).toHaveBeenCalledTimes(1);
125
- expect(sendMessageMatrixMock.mock.calls[0]?.[1]).toBe("Here is the answer.");
126
- });
127
-
128
- it("skips reasoning-only replies with thinking tags", async () => {
129
- await deliverMatrixReplies({
130
- replies: [
131
- { text: "<thinking>internal chain of thought</thinking>", replyToId: "r1" },
132
- { text: " <think>more reasoning</think> ", replyToId: "r2" },
133
- { text: "<antthinking>hidden</antthinking>", replyToId: "r3" },
134
- { text: "Visible reply", replyToId: "r4" },
135
- ],
136
- roomId: "room:tags",
137
- client: {} as MatrixClient,
138
- runtime: runtimeEnv,
139
- textLimit: 4000,
140
- replyToMode: "all",
141
- });
142
-
143
- expect(sendMessageMatrixMock).toHaveBeenCalledTimes(1);
144
- expect(sendMessageMatrixMock.mock.calls[0]?.[1]).toBe("Visible reply");
145
- });
146
-
147
- it("delivers all replies when none are reasoning-only", async () => {
148
- await deliverMatrixReplies({
149
- replies: [
150
- { text: "First answer", replyToId: "r1" },
151
- { text: "Second answer", replyToId: "r2" },
152
- ],
153
- roomId: "room:normal",
154
- client: {} as MatrixClient,
155
- runtime: runtimeEnv,
156
- textLimit: 4000,
157
- replyToMode: "all",
158
- });
159
-
160
- expect(sendMessageMatrixMock).toHaveBeenCalledTimes(2);
161
- });
162
-
163
- it("suppresses replyToId when threadId is set", async () => {
164
- chunkMarkdownTextWithModeMock.mockImplementation((text: string) => text.split("|"));
165
-
166
- await deliverMatrixReplies({
167
- replies: [{ text: "hello|thread", replyToId: "reply-thread" }],
168
- roomId: "room:3",
169
- client: {} as MatrixClient,
170
- runtime: runtimeEnv,
171
- textLimit: 4000,
172
- replyToMode: "all",
173
- threadId: "thread-77",
174
- });
175
-
176
- expect(sendMessageMatrixMock).toHaveBeenCalledTimes(2);
177
- expect(sendMessageMatrixMock.mock.calls[0]?.[2]).toEqual(
178
- expect.objectContaining({ replyToId: undefined, threadId: "thread-77" }),
179
- );
180
- expect(sendMessageMatrixMock.mock.calls[1]?.[2]).toEqual(
181
- expect.objectContaining({ replyToId: undefined, threadId: "thread-77" }),
182
- );
183
- });
184
- });