@openclaw/matrix 2026.1.29 → 2026.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/index.ts +0 -1
  3. package/openclaw.plugin.json +1 -3
  4. package/package.json +12 -12
  5. package/src/actions.ts +16 -6
  6. package/src/channel.directory.test.ts +13 -5
  7. package/src/channel.ts +61 -39
  8. package/src/directory-live.ts +21 -8
  9. package/src/group-mentions.ts +10 -5
  10. package/src/matrix/accounts.test.ts +0 -1
  11. package/src/matrix/accounts.ts +4 -2
  12. package/src/matrix/actions/client.ts +8 -4
  13. package/src/matrix/actions/messages.ts +17 -9
  14. package/src/matrix/actions/pins.ts +12 -6
  15. package/src/matrix/actions/reactions.ts +24 -12
  16. package/src/matrix/actions/room.ts +10 -13
  17. package/src/matrix/actions/summary.ts +4 -6
  18. package/src/matrix/client/config.ts +4 -9
  19. package/src/matrix/client/create-client.ts +12 -16
  20. package/src/matrix/client/logging.ts +17 -16
  21. package/src/matrix/client/shared.ts +6 -5
  22. package/src/matrix/client/storage.ts +12 -12
  23. package/src/matrix/client.test.ts +0 -1
  24. package/src/matrix/client.ts +1 -5
  25. package/src/matrix/credentials.ts +7 -5
  26. package/src/matrix/deps.ts +8 -5
  27. package/src/matrix/format.test.ts +0 -1
  28. package/src/matrix/monitor/allowlist.test.ts +45 -0
  29. package/src/matrix/monitor/allowlist.ts +62 -17
  30. package/src/matrix/monitor/auto-join.ts +7 -4
  31. package/src/matrix/monitor/direct.ts +11 -12
  32. package/src/matrix/monitor/events.ts +1 -3
  33. package/src/matrix/monitor/handler.ts +69 -53
  34. package/src/matrix/monitor/index.ts +118 -59
  35. package/src/matrix/monitor/location.ts +27 -10
  36. package/src/matrix/monitor/media.test.ts +1 -2
  37. package/src/matrix/monitor/media.ts +8 -8
  38. package/src/matrix/monitor/replies.ts +4 -3
  39. package/src/matrix/monitor/room-info.ts +5 -8
  40. package/src/matrix/monitor/rooms.test.ts +39 -0
  41. package/src/matrix/monitor/rooms.ts +7 -3
  42. package/src/matrix/monitor/threads.ts +6 -2
  43. package/src/matrix/poll-types.test.ts +0 -1
  44. package/src/matrix/poll-types.ts +16 -7
  45. package/src/matrix/send/client.ts +7 -4
  46. package/src/matrix/send/formatting.ts +14 -17
  47. package/src/matrix/send/media.ts +17 -8
  48. package/src/matrix/send/targets.test.ts +7 -11
  49. package/src/matrix/send/targets.ts +19 -27
  50. package/src/matrix/send.test.ts +1 -2
  51. package/src/matrix/send.ts +9 -4
  52. package/src/onboarding.ts +24 -14
  53. package/src/outbound.ts +1 -2
  54. package/src/resolve-targets.test.ts +48 -0
  55. package/src/resolve-targets.ts +55 -9
  56. package/src/tool-actions.ts +15 -11
  57. package/src/types.ts +5 -5
@@ -1,11 +1,8 @@
1
1
  import { format } from "node:util";
2
-
3
- import {
4
- mergeAllowlist,
5
- summarizeMapping,
6
- type RuntimeEnv,
7
- } from "openclaw/plugin-sdk";
2
+ import { mergeAllowlist, summarizeMapping, type RuntimeEnv } from "openclaw/plugin-sdk";
8
3
  import type { CoreConfig, ReplyToMode } from "../../types.js";
4
+ import { resolveMatrixTargets } from "../../resolve-targets.js";
5
+ import { getMatrixRuntime } from "../../runtime.js";
9
6
  import { setActiveMatrixClient } from "../active-client.js";
10
7
  import {
11
8
  isBunRuntime,
@@ -13,13 +10,12 @@ import {
13
10
  resolveSharedMatrixClient,
14
11
  stopSharedClient,
15
12
  } from "../client.js";
13
+ import { normalizeMatrixUserId } from "./allowlist.js";
16
14
  import { registerMatrixAutoJoin } from "./auto-join.js";
17
15
  import { createDirectRoomTracker } from "./direct.js";
18
16
  import { registerMatrixMonitorEvents } from "./events.js";
19
17
  import { createMatrixRoomMessageHandler } from "./handler.js";
20
18
  import { createMatrixRoomInfoResolver } from "./room-info.js";
21
- import { resolveMatrixTargets } from "../../resolve-targets.js";
22
- import { getMatrixRuntime } from "../../runtime.js";
23
19
 
24
20
  export type MonitorMatrixOpts = {
25
21
  runtime?: RuntimeEnv;
@@ -38,7 +34,9 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
38
34
  }
39
35
  const core = getMatrixRuntime();
40
36
  let cfg = core.config.loadConfig() as CoreConfig;
41
- if (cfg.channels?.matrix?.enabled === false) return;
37
+ if (cfg.channels?.matrix?.enabled === false) {
38
+ return;
39
+ }
42
40
 
43
41
  const logger = core.logging.getChildLogger({ module: "matrix-auto-reply" });
44
42
  const formatRuntimeMessage = (...args: Parameters<RuntimeEnv["log"]>) => format(...args);
@@ -54,75 +52,111 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
54
52
  },
55
53
  };
56
54
  const logVerboseMessage = (message: string) => {
57
- if (!core.logging.shouldLogVerbose()) return;
55
+ if (!core.logging.shouldLogVerbose()) {
56
+ return;
57
+ }
58
58
  logger.debug(message);
59
59
  };
60
60
 
61
61
  const normalizeUserEntry = (raw: string) =>
62
- raw.replace(/^matrix:/i, "").replace(/^user:/i, "").trim();
62
+ raw
63
+ .replace(/^matrix:/i, "")
64
+ .replace(/^user:/i, "")
65
+ .trim();
63
66
  const normalizeRoomEntry = (raw: string) =>
64
- raw.replace(/^matrix:/i, "").replace(/^(room|channel):/i, "").trim();
67
+ raw
68
+ .replace(/^matrix:/i, "")
69
+ .replace(/^(room|channel):/i, "")
70
+ .trim();
65
71
  const isMatrixUserId = (value: string) => value.startsWith("@") && value.includes(":");
66
-
67
- const allowlistOnly = cfg.channels?.matrix?.allowlistOnly === true;
68
- let allowFrom = cfg.channels?.matrix?.dm?.allowFrom ?? [];
69
- let roomsConfig = cfg.channels?.matrix?.groups ?? cfg.channels?.matrix?.rooms;
70
-
71
- if (allowFrom.length > 0) {
72
- const entries = allowFrom
72
+ const resolveUserAllowlist = async (
73
+ label: string,
74
+ list?: Array<string | number>,
75
+ ): Promise<string[]> => {
76
+ let allowList = list ?? [];
77
+ if (allowList.length === 0) {
78
+ return allowList;
79
+ }
80
+ const entries = allowList
73
81
  .map((entry) => normalizeUserEntry(String(entry)))
74
82
  .filter((entry) => entry && entry !== "*");
75
- if (entries.length > 0) {
76
- const mapping: string[] = [];
77
- const unresolved: string[] = [];
78
- const additions: string[] = [];
79
- const pending: string[] = [];
80
- for (const entry of entries) {
81
- if (isMatrixUserId(entry)) {
82
- additions.push(entry);
83
- continue;
84
- }
85
- pending.push(entry);
83
+ if (entries.length === 0) {
84
+ return allowList;
85
+ }
86
+ const mapping: string[] = [];
87
+ const unresolved: string[] = [];
88
+ const additions: string[] = [];
89
+ const pending: string[] = [];
90
+ for (const entry of entries) {
91
+ if (isMatrixUserId(entry)) {
92
+ additions.push(normalizeMatrixUserId(entry));
93
+ continue;
86
94
  }
87
- if (pending.length > 0) {
88
- const resolved = await resolveMatrixTargets({
89
- cfg,
90
- inputs: pending,
91
- kind: "user",
92
- runtime,
93
- });
94
- for (const entry of resolved) {
95
- if (entry.resolved && entry.id) {
96
- additions.push(entry.id);
97
- mapping.push(`${entry.input}→${entry.id}`);
98
- } else {
99
- unresolved.push(entry.input);
100
- }
95
+ pending.push(entry);
96
+ }
97
+ if (pending.length > 0) {
98
+ const resolved = await resolveMatrixTargets({
99
+ cfg,
100
+ inputs: pending,
101
+ kind: "user",
102
+ runtime,
103
+ });
104
+ for (const entry of resolved) {
105
+ if (entry.resolved && entry.id) {
106
+ const normalizedId = normalizeMatrixUserId(entry.id);
107
+ additions.push(normalizedId);
108
+ mapping.push(`${entry.input}→${normalizedId}`);
109
+ } else {
110
+ unresolved.push(entry.input);
101
111
  }
102
112
  }
103
- allowFrom = mergeAllowlist({ existing: allowFrom, additions });
104
- summarizeMapping("matrix users", mapping, unresolved, runtime);
105
113
  }
106
- }
114
+ allowList = mergeAllowlist({ existing: allowList, additions });
115
+ summarizeMapping(label, mapping, unresolved, runtime);
116
+ if (unresolved.length > 0) {
117
+ runtime.log?.(
118
+ `${label} entries must be full Matrix IDs (example: @user:server). Unresolved entries are ignored.`,
119
+ );
120
+ }
121
+ return allowList;
122
+ };
123
+
124
+ const allowlistOnly = cfg.channels?.matrix?.allowlistOnly === true;
125
+ let allowFrom = cfg.channels?.matrix?.dm?.allowFrom ?? [];
126
+ let groupAllowFrom = cfg.channels?.matrix?.groupAllowFrom ?? [];
127
+ let roomsConfig = cfg.channels?.matrix?.groups ?? cfg.channels?.matrix?.rooms;
128
+
129
+ allowFrom = await resolveUserAllowlist("matrix dm allowlist", allowFrom);
130
+ groupAllowFrom = await resolveUserAllowlist("matrix group allowlist", groupAllowFrom);
107
131
 
108
132
  if (roomsConfig && Object.keys(roomsConfig).length > 0) {
109
- const entries = Object.keys(roomsConfig).filter((key) => key !== "*");
110
133
  const mapping: string[] = [];
111
134
  const unresolved: string[] = [];
112
- const nextRooms = { ...roomsConfig };
113
- const pending: Array<{ input: string; query: string }> = [];
114
- for (const entry of entries) {
135
+ const nextRooms: Record<string, (typeof roomsConfig)[string]> = {};
136
+ if (roomsConfig["*"]) {
137
+ nextRooms["*"] = roomsConfig["*"];
138
+ }
139
+ const pending: Array<{ input: string; query: string; config: (typeof roomsConfig)[string] }> =
140
+ [];
141
+ for (const [entry, roomConfig] of Object.entries(roomsConfig)) {
142
+ if (entry === "*") {
143
+ continue;
144
+ }
115
145
  const trimmed = entry.trim();
116
- if (!trimmed) continue;
146
+ if (!trimmed) {
147
+ continue;
148
+ }
117
149
  const cleaned = normalizeRoomEntry(trimmed);
118
- if (cleaned.startsWith("!") && cleaned.includes(":")) {
150
+ if ((cleaned.startsWith("!") || cleaned.startsWith("#")) && cleaned.includes(":")) {
119
151
  if (!nextRooms[cleaned]) {
120
- nextRooms[cleaned] = roomsConfig[entry];
152
+ nextRooms[cleaned] = roomConfig;
153
+ }
154
+ if (cleaned !== entry) {
155
+ mapping.push(`${entry}→${cleaned}`);
121
156
  }
122
- mapping.push(`${entry}→${cleaned}`);
123
157
  continue;
124
158
  }
125
- pending.push({ input: entry, query: trimmed });
159
+ pending.push({ input: entry, query: trimmed, config: roomConfig });
126
160
  }
127
161
  if (pending.length > 0) {
128
162
  const resolved = await resolveMatrixTargets({
@@ -133,10 +167,12 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
133
167
  });
134
168
  resolved.forEach((entry, index) => {
135
169
  const source = pending[index];
136
- if (!source) return;
170
+ if (!source) {
171
+ return;
172
+ }
137
173
  if (entry.resolved && entry.id) {
138
174
  if (!nextRooms[entry.id]) {
139
- nextRooms[entry.id] = roomsConfig[source.input];
175
+ nextRooms[entry.id] = source.config;
140
176
  }
141
177
  mapping.push(`${source.input}→${entry.id}`);
142
178
  } else {
@@ -146,6 +182,25 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
146
182
  }
147
183
  roomsConfig = nextRooms;
148
184
  summarizeMapping("matrix rooms", mapping, unresolved, runtime);
185
+ if (unresolved.length > 0) {
186
+ runtime.log?.(
187
+ "matrix rooms must be room IDs or aliases (example: !room:server or #alias:server). Unresolved entries are ignored.",
188
+ );
189
+ }
190
+ }
191
+ if (roomsConfig && Object.keys(roomsConfig).length > 0) {
192
+ const nextRooms = { ...roomsConfig };
193
+ for (const [roomKey, roomConfig] of Object.entries(roomsConfig)) {
194
+ const users = roomConfig?.users ?? [];
195
+ if (users.length === 0) {
196
+ continue;
197
+ }
198
+ const resolvedUsers = await resolveUserAllowlist(`matrix room users (${roomKey})`, users);
199
+ if (resolvedUsers !== users) {
200
+ nextRooms[roomKey] = { ...roomConfig, users: resolvedUsers };
201
+ }
202
+ }
203
+ roomsConfig = nextRooms;
149
204
  }
150
205
 
151
206
  cfg = {
@@ -158,6 +213,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
158
213
  ...cfg.channels?.matrix?.dm,
159
214
  allowFrom,
160
215
  },
216
+ ...(groupAllowFrom.length > 0 ? { groupAllowFrom } : {}),
161
217
  ...(roomsConfig ? { groups: roomsConfig } : {}),
162
218
  },
163
219
  },
@@ -256,7 +312,10 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
256
312
  logger.info("matrix: device verification requested - please verify in another client");
257
313
  }
258
314
  } catch (err) {
259
- logger.debug({ error: String(err) }, "Device verification request failed (may already be verified)");
315
+ logger.debug(
316
+ { error: String(err) },
317
+ "Device verification request failed (may already be verified)",
318
+ );
260
319
  }
261
320
  }
262
321
 
@@ -1,5 +1,4 @@
1
1
  import type { LocationMessageEventContent } from "@vector-im/matrix-bot-sdk";
2
-
3
2
  import {
4
3
  formatLocationText,
5
4
  toLocationContext,
@@ -20,25 +19,37 @@ type GeoUriParams = {
20
19
 
21
20
  function parseGeoUri(value: string): GeoUriParams | null {
22
21
  const trimmed = value.trim();
23
- if (!trimmed) return null;
24
- if (!trimmed.toLowerCase().startsWith("geo:")) return null;
22
+ if (!trimmed) {
23
+ return null;
24
+ }
25
+ if (!trimmed.toLowerCase().startsWith("geo:")) {
26
+ return null;
27
+ }
25
28
  const payload = trimmed.slice(4);
26
29
  const [coordsPart, ...paramParts] = payload.split(";");
27
30
  const coords = coordsPart.split(",");
28
- if (coords.length < 2) return null;
31
+ if (coords.length < 2) {
32
+ return null;
33
+ }
29
34
  const latitude = Number.parseFloat(coords[0] ?? "");
30
35
  const longitude = Number.parseFloat(coords[1] ?? "");
31
- if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null;
36
+ if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
37
+ return null;
38
+ }
32
39
 
33
40
  const params = new Map<string, string>();
34
41
  for (const part of paramParts) {
35
42
  const segment = part.trim();
36
- if (!segment) continue;
43
+ if (!segment) {
44
+ continue;
45
+ }
37
46
  const eqIndex = segment.indexOf("=");
38
47
  const rawKey = eqIndex === -1 ? segment : segment.slice(0, eqIndex);
39
48
  const rawValue = eqIndex === -1 ? "" : segment.slice(eqIndex + 1);
40
49
  const key = rawKey.trim().toLowerCase();
41
- if (!key) continue;
50
+ if (!key) {
51
+ continue;
52
+ }
42
53
  const valuePart = rawValue.trim();
43
54
  params.set(key, valuePart ? decodeURIComponent(valuePart) : "");
44
55
  }
@@ -61,11 +72,17 @@ export function resolveMatrixLocation(params: {
61
72
  const isLocation =
62
73
  eventType === EventType.Location ||
63
74
  (eventType === EventType.RoomMessage && content.msgtype === EventType.Location);
64
- if (!isLocation) return null;
75
+ if (!isLocation) {
76
+ return null;
77
+ }
65
78
  const geoUri = typeof content.geo_uri === "string" ? content.geo_uri.trim() : "";
66
- if (!geoUri) return null;
79
+ if (!geoUri) {
80
+ return null;
81
+ }
67
82
  const parsed = parseGeoUri(geoUri);
68
- if (!parsed) return null;
83
+ if (!parsed) {
84
+ return null;
85
+ }
69
86
  const caption = typeof content.body === "string" ? content.body.trim() : "";
70
87
  const location: NormalizedLocation = {
71
88
  latitude: parsed.latitude,
@@ -1,6 +1,5 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
1
  import type { PluginRuntime } from "openclaw/plugin-sdk";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
4
3
  import { setMatrixRuntime } from "../../runtime.js";
5
4
  import { downloadMatrixMedia } from "./media.js";
6
5
 
@@ -1,5 +1,4 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
-
3
2
  import { getMatrixRuntime } from "../../runtime.js";
4
3
 
5
4
  // Type for encrypted file info
@@ -24,7 +23,9 @@ async function fetchMatrixMediaBuffer(params: {
24
23
  }): Promise<{ buffer: Buffer; headerType?: string } | null> {
25
24
  // @vector-im/matrix-bot-sdk provides mxcToHttp helper
26
25
  const url = params.client.mxcToHttp(params.mxcUrl);
27
- if (!url) return null;
26
+ if (!url) {
27
+ return null;
28
+ }
28
29
 
29
30
  // Use the client's download method which handles auth
30
31
  try {
@@ -34,7 +35,7 @@ async function fetchMatrixMediaBuffer(params: {
34
35
  }
35
36
  return { buffer: Buffer.from(buffer) };
36
37
  } catch (err) {
37
- throw new Error(`Matrix media download failed: ${String(err)}`);
38
+ throw new Error(`Matrix media download failed: ${String(err)}`, { cause: err });
38
39
  }
39
40
  }
40
41
 
@@ -74,10 +75,7 @@ export async function downloadMatrixMedia(params: {
74
75
  placeholder: string;
75
76
  } | null> {
76
77
  let fetched: { buffer: Buffer; headerType?: string } | null;
77
- if (
78
- typeof params.sizeBytes === "number" &&
79
- params.sizeBytes > params.maxBytes
80
- ) {
78
+ if (typeof params.sizeBytes === "number" && params.sizeBytes > params.maxBytes) {
81
79
  throw new Error("Matrix media exceeds configured size limit");
82
80
  }
83
81
 
@@ -97,7 +95,9 @@ export async function downloadMatrixMedia(params: {
97
95
  });
98
96
  }
99
97
 
100
- if (!fetched) return null;
98
+ if (!fetched) {
99
+ return null;
100
+ }
101
101
  const headerType = fetched.headerType ?? params.contentType ?? undefined;
102
102
  const saved = await getMatrixRuntime().channel.media.saveMediaBuffer(
103
103
  fetched.buffer,
@@ -1,8 +1,7 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
-
3
2
  import type { MarkdownTableMode, ReplyPayload, RuntimeEnv } from "openclaw/plugin-sdk";
4
- import { sendMessageMatrix } from "../send.js";
5
3
  import { getMatrixRuntime } from "../../runtime.js";
4
+ import { sendMessageMatrix } from "../send.js";
6
5
 
7
6
  export async function deliverMatrixReplies(params: {
8
7
  replies: ReplyPayload[];
@@ -62,7 +61,9 @@ export async function deliverMatrixReplies(params: {
62
61
  chunkMode,
63
62
  )) {
64
63
  const trimmed = chunk.trim();
65
- if (!trimmed) continue;
64
+ if (!trimmed) {
65
+ continue;
66
+ }
66
67
  await sendMessageMatrix(params.roomId, trimmed, {
67
68
  client: params.client,
68
69
  replyToId: shouldIncludeReply(replyToId) ? replyToId : undefined,
@@ -11,14 +11,14 @@ export function createMatrixRoomInfoResolver(client: MatrixClient) {
11
11
 
12
12
  const getRoomInfo = async (roomId: string): Promise<MatrixRoomInfo> => {
13
13
  const cached = roomInfoCache.get(roomId);
14
- if (cached) return cached;
14
+ if (cached) {
15
+ return cached;
16
+ }
15
17
  let name: string | undefined;
16
18
  let canonicalAlias: string | undefined;
17
19
  let altAliases: string[] = [];
18
20
  try {
19
- const nameState = await client
20
- .getRoomStateEvent(roomId, "m.room.name", "")
21
- .catch(() => null);
21
+ const nameState = await client.getRoomStateEvent(roomId, "m.room.name", "").catch(() => null);
22
22
  name = nameState?.name;
23
23
  } catch {
24
24
  // ignore
@@ -37,10 +37,7 @@ export function createMatrixRoomInfoResolver(client: MatrixClient) {
37
37
  return info;
38
38
  };
39
39
 
40
- const getMemberDisplayName = async (
41
- roomId: string,
42
- userId: string,
43
- ): Promise<string> => {
40
+ const getMemberDisplayName = async (roomId: string, userId: string): Promise<string> => {
44
41
  try {
45
42
  const memberState = await client
46
43
  .getRoomStateEvent(roomId, "m.room.member", userId)
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveMatrixRoomConfig } from "./rooms.js";
3
+
4
+ describe("resolveMatrixRoomConfig", () => {
5
+ it("matches room IDs and aliases, not names", () => {
6
+ const rooms = {
7
+ "!room:example.org": { allow: true },
8
+ "#alias:example.org": { allow: true },
9
+ "Project Room": { allow: true },
10
+ };
11
+
12
+ const byId = resolveMatrixRoomConfig({
13
+ rooms,
14
+ roomId: "!room:example.org",
15
+ aliases: [],
16
+ name: "Project Room",
17
+ });
18
+ expect(byId.allowed).toBe(true);
19
+ expect(byId.matchKey).toBe("!room:example.org");
20
+
21
+ const byAlias = resolveMatrixRoomConfig({
22
+ rooms,
23
+ roomId: "!other:example.org",
24
+ aliases: ["#alias:example.org"],
25
+ name: "Other Room",
26
+ });
27
+ expect(byAlias.allowed).toBe(true);
28
+ expect(byAlias.matchKey).toBe("#alias:example.org");
29
+
30
+ const byName = resolveMatrixRoomConfig({
31
+ rooms: { "Project Room": { allow: true } },
32
+ roomId: "!different:example.org",
33
+ aliases: [],
34
+ name: "Project Room",
35
+ });
36
+ expect(byName.allowed).toBe(false);
37
+ expect(byName.config).toBeUndefined();
38
+ });
39
+ });
@@ -1,5 +1,5 @@
1
- import type { MatrixRoomConfig } from "../../types.js";
2
1
  import { buildChannelKeyCandidates, resolveChannelEntryMatch } from "openclaw/plugin-sdk";
2
+ import type { MatrixRoomConfig } from "../../types.js";
3
3
 
4
4
  export type MatrixRoomConfigResolved = {
5
5
  allowed: boolean;
@@ -22,9 +22,13 @@ export function resolveMatrixRoomConfig(params: {
22
22
  params.roomId,
23
23
  `room:${params.roomId}`,
24
24
  ...params.aliases,
25
- params.name ?? "",
26
25
  );
27
- const { entry: matched, key: matchedKey, wildcardEntry, wildcardKey } = resolveChannelEntryMatch({
26
+ const {
27
+ entry: matched,
28
+ key: matchedKey,
29
+ wildcardEntry,
30
+ wildcardKey,
31
+ } = resolveChannelEntryMatch({
28
32
  entries: rooms,
29
33
  keys: candidates,
30
34
  wildcardKey: "*",
@@ -28,7 +28,9 @@ export function resolveMatrixThreadTarget(params: {
28
28
  isThreadRoot?: boolean;
29
29
  }): string | undefined {
30
30
  const { threadReplies, messageId, threadRootId } = params;
31
- if (threadReplies === "off") return undefined;
31
+ if (threadReplies === "off") {
32
+ return undefined;
33
+ }
32
34
  const isThreadRoot = params.isThreadRoot === true;
33
35
  const hasInboundThread = Boolean(threadRootId && threadRootId !== messageId && !isThreadRoot);
34
36
  if (threadReplies === "inbound") {
@@ -45,7 +47,9 @@ export function resolveMatrixThreadRootId(params: {
45
47
  content: RoomMessageEventContent;
46
48
  }): string | undefined {
47
49
  const relates = params.content["m.relates_to"];
48
- if (!relates || typeof relates !== "object") return undefined;
50
+ if (!relates || typeof relates !== "object") {
51
+ return undefined;
52
+ }
49
53
  if ("rel_type" in relates && relates.rel_type === RelationType.Thread) {
50
54
  if ("event_id" in relates && typeof relates.event_id === "string") {
51
55
  return relates.event_id;
@@ -1,5 +1,4 @@
1
1
  import { describe, expect, it } from "vitest";
2
-
3
2
  import { parsePollStartContent } from "./poll-types.js";
4
3
 
5
4
  describe("parsePollStartContent", () => {
@@ -77,18 +77,25 @@ export function isPollStartType(eventType: string): boolean {
77
77
  }
78
78
 
79
79
  export function getTextContent(text?: TextContent): string {
80
- if (!text) return "";
80
+ if (!text) {
81
+ return "";
82
+ }
81
83
  return text["m.text"] ?? text["org.matrix.msc1767.text"] ?? text.body ?? "";
82
84
  }
83
85
 
84
86
  export function parsePollStartContent(content: PollStartContent): PollSummary | null {
85
- const poll = (content as Record<string, PollStartSubtype | undefined>)[M_POLL_START]
86
- ?? (content as Record<string, PollStartSubtype | undefined>)[ORG_POLL_START]
87
- ?? (content as Record<string, PollStartSubtype | undefined>)["m.poll"];
88
- if (!poll) return null;
87
+ const poll =
88
+ (content as Record<string, PollStartSubtype | undefined>)[M_POLL_START] ??
89
+ (content as Record<string, PollStartSubtype | undefined>)[ORG_POLL_START] ??
90
+ (content as Record<string, PollStartSubtype | undefined>)["m.poll"];
91
+ if (!poll) {
92
+ return null;
93
+ }
89
94
 
90
95
  const question = getTextContent(poll.question);
91
- if (!question) return null;
96
+ if (!question) {
97
+ return null;
98
+ }
92
99
 
93
100
  const answers = poll.answers
94
101
  .map((answer) => getTextContent(answer))
@@ -124,7 +131,9 @@ function buildTextContent(body: string): TextContent {
124
131
  }
125
132
 
126
133
  function buildPollFallbackText(question: string, answers: string[]): string {
127
- if (answers.length === 0) return question;
134
+ if (answers.length === 0) {
135
+ return question;
136
+ }
128
137
  return `${question}\n${answers.map((answer, idx) => `${idx + 1}. ${answer}`).join("\n")}`;
129
138
  }
130
139
 
@@ -1,5 +1,5 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
-
2
+ import type { CoreConfig } from "../types.js";
3
3
  import { getMatrixRuntime } from "../../runtime.js";
4
4
  import { getActiveMatrixClient } from "../active-client.js";
5
5
  import {
@@ -8,7 +8,6 @@ import {
8
8
  resolveMatrixAuth,
9
9
  resolveSharedMatrixClient,
10
10
  } from "../client.js";
11
- import type { CoreConfig } from "../types.js";
12
11
 
13
12
  const getCore = () => getMatrixRuntime();
14
13
 
@@ -31,9 +30,13 @@ export async function resolveMatrixClient(opts: {
31
30
  timeoutMs?: number;
32
31
  }): Promise<{ client: MatrixClient; stopOnDone: boolean }> {
33
32
  ensureNodeRuntime();
34
- if (opts.client) return { client: opts.client, stopOnDone: false };
33
+ if (opts.client) {
34
+ return { client: opts.client, stopOnDone: false };
35
+ }
35
36
  const active = getActiveMatrixClient();
36
- if (active) return { client: active, stopOnDone: false };
37
+ if (active) {
38
+ return { client: active, stopOnDone: false };
39
+ }
37
40
  const shouldShareClient = Boolean(process.env.OPENCLAW_GATEWAY_PORT);
38
41
  if (shouldShareClient) {
39
42
  const client = await resolveSharedMatrixClient({