@openclaw/matrix 2026.3.13 → 2026.5.10-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.
- package/dist/account-config-D2W-V1eQ.js +96 -0
- package/dist/account-selection-BWwIruri.js +158 -0
- package/dist/accounts-Bm90Rzvp.js +130 -0
- package/dist/active-client-uhlxdhEy.js +20 -0
- package/dist/allowlist-sTzpCn5d.js +68 -0
- package/dist/api.js +12 -0
- package/dist/approval-handler.runtime-DWTQfd4m.js +370 -0
- package/dist/approval-ids-DoC2z7tR.js +7 -0
- package/dist/approval-reaction-auth-DbcA1gGd.js +27 -0
- package/dist/approval-reactions-o2_tuH8D.js +162 -0
- package/dist/async-lock-uQfhfQIY.js +19 -0
- package/dist/auth-presence.js +26 -0
- package/dist/backup-health-Cabu_WQC.js +60 -0
- package/dist/channel-H_6lMgwf.js +1116 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-BnO9f0pR.js +246 -0
- package/dist/cli-CYZ9yVcB.js +1340 -0
- package/dist/cli-metadata-DPIHnoa6.js +22 -0
- package/dist/cli-metadata.js +2 -0
- package/dist/client-DkcXnm0X.js +25 -0
- package/dist/client-_hckQNGW.js +31 -0
- package/dist/client-bootstrap-Rb8oHvhH.js +114 -0
- package/dist/config--5-S2Akv.js +452 -0
- package/dist/config-paths-nsVaysCu.js +19 -0
- package/dist/config-schema-nPLpEgHl.js +200 -0
- package/dist/config-secret-input.runtime-DiKFehsE.js +2 -0
- package/dist/config-update-wZX-HLMn.js +143 -0
- package/dist/contract-api.js +9 -0
- package/dist/create-client-DCnqDaqd.js +64 -0
- package/dist/credentials-DV6fWXhC.js +56 -0
- package/dist/credentials-read-cmHgousK.js +112 -0
- package/dist/credentials-write.runtime-zniTq-Gr.js +17 -0
- package/dist/crypto-node.runtime-pihzdpY7.js +12 -0
- package/dist/crypto-runtime-ZI0zAtn3.js +1214 -0
- package/dist/deps-C6WqKY7m.js +235 -0
- package/dist/device-health-UVYpbA_W.js +16 -0
- package/dist/direct-management-DMMMgtTB.js +249 -0
- package/dist/direct-room-XkutHjES.js +76 -0
- package/dist/directory-live-DmOtMhyr.js +150 -0
- package/dist/doctor-C4__7c-U.js +153 -0
- package/dist/doctor-contract-D4-64QuJ.js +246 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/draft-stream-BE2QevQQ.js +144 -0
- package/dist/encryption-guidance-BPi3A_m3.js +15 -0
- package/dist/env-auth-BJqGI8M6.js +63 -0
- package/dist/env-vars-C7uQCTKn.js +63 -0
- package/dist/errors-CTcpEDq-.js +17 -0
- package/dist/exec-approval-resolver-Bza9Dhlm.js +15 -0
- package/dist/exec-approvals-Crnh543m.js +196 -0
- package/dist/helper-api.js +4 -0
- package/dist/http-client-C7AeVJay.js +319 -0
- package/dist/index.js +46 -0
- package/dist/legacy-crypto-inspector-poDWldgy.js +41 -0
- package/dist/legacy-crypto-restore-Biw-w2ng.js +85 -0
- package/dist/logger-CnZRVrux.js +78 -0
- package/dist/logging-DZHSPP5N.js +99 -0
- package/dist/matrix-migration.runtime-WY6ffcrf.js +525 -0
- package/dist/media-text-DU6nWZuj.js +146 -0
- package/dist/messages-BpihMh82.js +140 -0
- package/dist/migration-snapshot-backup-DaCHTp8C.js +69 -0
- package/dist/migration-snapshot.runtime-CKHE3xF9.js +2 -0
- package/dist/monitor-BaRCKyLd.js +4175 -0
- package/dist/plugin-entry.handlers.runtime.js +51 -0
- package/dist/probe.runtime-BvAzYAIe.js +3 -0
- package/dist/profile-BlHu0wDX.js +111 -0
- package/dist/profile-update-DjeBNgIV.js +69 -0
- package/dist/reaction-common-ejrL19w-.js +71 -0
- package/dist/reaction-events-CiARZfjk.js +121 -0
- package/dist/record-shared-CHWJCTWf.js +2 -0
- package/dist/recovery-key-store-BTJ6jz5v.js +294 -0
- package/dist/resolve-targets-YtJnw1Tb.js +140 -0
- package/dist/resolver.runtime-D9piiGEl.js +5 -0
- package/dist/rolldown-runtime-DUslC3ob.js +14 -0
- package/dist/route-D6rg-iXN.js +161 -0
- package/dist/runtime-C6X4h_SJ.js +6 -0
- package/dist/runtime-Dog86njy.js +8 -0
- package/dist/runtime-api-DTKcXOhp.js +24 -0
- package/dist/runtime-api.js +25 -0
- package/dist/runtime-heavy-api.js +3 -0
- package/dist/runtime-setter-api.js +2 -0
- package/dist/sdk-B2vZA27-.js +1416 -0
- package/dist/secret-contract-DcrJWCQI.js +120 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/send-Bo0DU1ca.js +1200 -0
- package/dist/session-store-metadata-DQXjgNLt.js +77 -0
- package/dist/setup-bootstrap-ImenBsMt.js +62 -0
- package/dist/setup-core-CfZy05oW.js +116 -0
- package/dist/setup-dm-policy-2-r1FrQh.js +194 -0
- package/dist/setup-entry.js +19 -0
- package/dist/setup-plugin-api.js +44 -0
- package/dist/setup-surface-CqT_o61M.js +540 -0
- package/dist/shared-CpMoYKm1.js +195 -0
- package/dist/startup-abort-56edvmbM.js +32 -0
- package/dist/startup-verification-Demyp0bP.js +132 -0
- package/dist/storage-paths-BJLdnCjV.js +52 -0
- package/dist/storage-tC3ujLiW.js +281 -0
- package/dist/subagent-hooks-DQbyqq9V.js +149 -0
- package/dist/subagent-hooks-api.js +23 -0
- package/dist/sync-state-C_beeevA.js +12 -0
- package/dist/target-ids-80nQ2gql.js +77 -0
- package/dist/test-api.js +4 -0
- package/dist/thread-binding-api-Cq_E-E1K.js +17 -0
- package/dist/thread-binding-api.js +2 -0
- package/dist/thread-bindings-B9mesxXk.js +352 -0
- package/dist/thread-bindings-runtime.js +2 -0
- package/dist/thread-bindings-shared-DK-d-oYX.js +97 -0
- package/dist/timeout-abort-signal-CtaIaP1v.js +2 -0
- package/dist/tool-actions.runtime-ThYhfHtZ.js +532 -0
- package/dist/url-validation-DiK9j7jz.js +36 -0
- package/dist/verification-CZ2rDeHL.js +345 -0
- package/openclaw.plugin.json +796 -1
- package/package.json +82 -16
- package/CHANGELOG.md +0 -104
- package/index.ts +0 -22
- package/src/actions.ts +0 -195
- package/src/channel.directory.test.ts +0 -135
- package/src/channel.ts +0 -461
- package/src/config-schema.test.ts +0 -26
- package/src/config-schema.ts +0 -62
- package/src/directory-live.test.ts +0 -85
- package/src/directory-live.ts +0 -209
- package/src/group-mentions.ts +0 -52
- package/src/matrix/accounts.test.ts +0 -131
- package/src/matrix/accounts.ts +0 -114
- package/src/matrix/actions/client.ts +0 -47
- package/src/matrix/actions/limits.test.ts +0 -15
- package/src/matrix/actions/limits.ts +0 -6
- package/src/matrix/actions/messages.ts +0 -126
- package/src/matrix/actions/pins.test.ts +0 -74
- package/src/matrix/actions/pins.ts +0 -84
- package/src/matrix/actions/reactions.test.ts +0 -109
- package/src/matrix/actions/reactions.ts +0 -102
- package/src/matrix/actions/room.ts +0 -85
- package/src/matrix/actions/summary.ts +0 -75
- package/src/matrix/actions/types.ts +0 -85
- package/src/matrix/actions.ts +0 -15
- package/src/matrix/active-client.ts +0 -32
- package/src/matrix/client/config.ts +0 -245
- package/src/matrix/client/create-client.ts +0 -125
- package/src/matrix/client/logging.ts +0 -46
- package/src/matrix/client/runtime.ts +0 -4
- package/src/matrix/client/shared.test.ts +0 -85
- package/src/matrix/client/shared.ts +0 -210
- package/src/matrix/client/startup.test.ts +0 -49
- package/src/matrix/client/startup.ts +0 -29
- package/src/matrix/client/storage.ts +0 -131
- package/src/matrix/client/types.ts +0 -34
- package/src/matrix/client-bootstrap.ts +0 -47
- package/src/matrix/client.test.ts +0 -56
- package/src/matrix/client.ts +0 -14
- package/src/matrix/credentials.ts +0 -125
- package/src/matrix/deps.test.ts +0 -74
- package/src/matrix/deps.ts +0 -126
- package/src/matrix/format.test.ts +0 -33
- package/src/matrix/format.ts +0 -22
- package/src/matrix/index.ts +0 -11
- package/src/matrix/monitor/access-policy.ts +0 -126
- package/src/matrix/monitor/allowlist.test.ts +0 -45
- package/src/matrix/monitor/allowlist.ts +0 -94
- package/src/matrix/monitor/auto-join.ts +0 -72
- package/src/matrix/monitor/direct.test.ts +0 -396
- package/src/matrix/monitor/direct.ts +0 -152
- package/src/matrix/monitor/events.test.ts +0 -186
- package/src/matrix/monitor/events.ts +0 -168
- package/src/matrix/monitor/handler.body-for-agent.test.ts +0 -196
- package/src/matrix/monitor/handler.ts +0 -768
- package/src/matrix/monitor/inbound-body.test.ts +0 -73
- package/src/matrix/monitor/inbound-body.ts +0 -28
- package/src/matrix/monitor/index.test.ts +0 -18
- package/src/matrix/monitor/index.ts +0 -414
- package/src/matrix/monitor/location.ts +0 -100
- package/src/matrix/monitor/media.test.ts +0 -86
- package/src/matrix/monitor/media.ts +0 -118
- package/src/matrix/monitor/mentions.test.ts +0 -154
- package/src/matrix/monitor/mentions.ts +0 -62
- package/src/matrix/monitor/replies.test.ts +0 -184
- package/src/matrix/monitor/replies.ts +0 -124
- package/src/matrix/monitor/room-info.ts +0 -55
- package/src/matrix/monitor/rooms.test.ts +0 -124
- package/src/matrix/monitor/rooms.ts +0 -47
- package/src/matrix/monitor/threads.ts +0 -68
- package/src/matrix/monitor/types.ts +0 -39
- package/src/matrix/poll-types.test.ts +0 -21
- package/src/matrix/poll-types.ts +0 -167
- package/src/matrix/probe.ts +0 -69
- package/src/matrix/sdk-runtime.ts +0 -18
- package/src/matrix/send/client.ts +0 -99
- package/src/matrix/send/formatting.ts +0 -93
- package/src/matrix/send/media.ts +0 -230
- package/src/matrix/send/targets.test.ts +0 -98
- package/src/matrix/send/targets.ts +0 -150
- package/src/matrix/send/types.ts +0 -110
- package/src/matrix/send-queue.test.ts +0 -145
- package/src/matrix/send-queue.ts +0 -28
- package/src/matrix/send.test.ts +0 -319
- package/src/matrix/send.ts +0 -267
- package/src/onboarding.ts +0 -462
- package/src/outbound.test.ts +0 -159
- package/src/outbound.ts +0 -58
- package/src/resolve-targets.test.ts +0 -68
- package/src/resolve-targets.ts +0 -125
- package/src/runtime.ts +0 -6
- package/src/secret-input.ts +0 -13
- package/src/test-mocks.ts +0 -53
- package/src/tool-actions.ts +0 -164
- 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
|
-
});
|