@openclaw/matrix 2026.3.1 → 2026.3.7

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 (48) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/index.ts +7 -2
  3. package/package.json +2 -1
  4. package/src/actions.ts +1 -1
  5. package/src/channel.directory.test.ts +1 -1
  6. package/src/channel.ts +46 -58
  7. package/src/config-schema.test.ts +26 -0
  8. package/src/config-schema.ts +3 -2
  9. package/src/directory-live.ts +1 -1
  10. package/src/group-mentions.ts +1 -1
  11. package/src/matrix/accounts.ts +9 -44
  12. package/src/matrix/client/config.ts +55 -29
  13. package/src/matrix/client/create-client.ts +7 -5
  14. package/src/matrix/client/logging.ts +17 -7
  15. package/src/matrix/client/shared.ts +3 -1
  16. package/src/matrix/client-bootstrap.ts +2 -1
  17. package/src/matrix/deps.test.ts +74 -0
  18. package/src/matrix/deps.ts +67 -1
  19. package/src/matrix/monitor/access-policy.ts +6 -7
  20. package/src/matrix/monitor/allowlist.ts +9 -16
  21. package/src/matrix/monitor/auto-join.ts +3 -2
  22. package/src/matrix/monitor/events.test.ts +1 -1
  23. package/src/matrix/monitor/events.ts +1 -1
  24. package/src/matrix/monitor/handler.body-for-agent.test.ts +1 -1
  25. package/src/matrix/monitor/handler.ts +33 -36
  26. package/src/matrix/monitor/index.ts +6 -7
  27. package/src/matrix/monitor/location.ts +1 -1
  28. package/src/matrix/monitor/media.test.ts +1 -1
  29. package/src/matrix/monitor/replies.test.ts +1 -1
  30. package/src/matrix/monitor/replies.ts +1 -1
  31. package/src/matrix/monitor/rooms.ts +1 -1
  32. package/src/matrix/poll-types.ts +1 -1
  33. package/src/matrix/probe.ts +1 -1
  34. package/src/matrix/sdk-runtime.ts +18 -0
  35. package/src/matrix/send/client.ts +8 -6
  36. package/src/matrix/send/types.ts +1 -0
  37. package/src/matrix/send-queue.ts +7 -23
  38. package/src/matrix/send.test.ts +90 -2
  39. package/src/matrix/send.ts +6 -4
  40. package/src/onboarding.ts +52 -36
  41. package/src/outbound.test.ts +159 -0
  42. package/src/outbound.ts +7 -4
  43. package/src/resolve-targets.test.ts +1 -1
  44. package/src/resolve-targets.ts +39 -40
  45. package/src/runtime.ts +1 -1
  46. package/src/secret-input.ts +13 -0
  47. package/src/tool-actions.ts +1 -1
  48. package/src/types.ts +2 -2
@@ -1,4 +1,4 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/matrix";
2
2
  import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { setMatrixRuntime } from "../runtime.js";
4
4
 
@@ -24,12 +24,17 @@ vi.mock("@vector-im/matrix-bot-sdk", () => ({
24
24
  RustSdkCryptoStorageProvider: vi.fn(),
25
25
  }));
26
26
 
27
+ vi.mock("./send-queue.js", () => ({
28
+ enqueueSend: async <T>(_roomId: string, fn: () => Promise<T>) => await fn(),
29
+ }));
30
+
27
31
  const loadWebMediaMock = vi.fn().mockResolvedValue({
28
32
  buffer: Buffer.from("media"),
29
33
  fileName: "photo.png",
30
34
  contentType: "image/png",
31
35
  kind: "image",
32
36
  });
37
+ const runtimeLoadConfigMock = vi.fn(() => ({}));
33
38
  const mediaKindFromMimeMock = vi.fn(() => "image");
34
39
  const isVoiceCompatibleAudioMock = vi.fn(() => false);
35
40
  const getImageMetadataMock = vi.fn().mockResolvedValue(null);
@@ -37,7 +42,7 @@ const resizeToJpegMock = vi.fn();
37
42
 
38
43
  const runtimeStub = {
39
44
  config: {
40
- loadConfig: () => ({}),
45
+ loadConfig: runtimeLoadConfigMock,
41
46
  },
42
47
  media: {
43
48
  loadWebMedia: loadWebMediaMock as unknown as PluginRuntime["media"]["loadWebMedia"],
@@ -61,6 +66,7 @@ const runtimeStub = {
61
66
  } as unknown as PluginRuntime;
62
67
 
63
68
  let sendMessageMatrix: typeof import("./send.js").sendMessageMatrix;
69
+ let resolveMediaMaxBytes: typeof import("./send/client.js").resolveMediaMaxBytes;
64
70
 
65
71
  const makeClient = () => {
66
72
  const sendMessage = vi.fn().mockResolvedValue("evt1");
@@ -76,11 +82,14 @@ const makeClient = () => {
76
82
  beforeAll(async () => {
77
83
  setMatrixRuntime(runtimeStub);
78
84
  ({ sendMessageMatrix } = await import("./send.js"));
85
+ ({ resolveMediaMaxBytes } = await import("./send/client.js"));
79
86
  });
80
87
 
81
88
  describe("sendMessageMatrix media", () => {
82
89
  beforeEach(() => {
83
90
  vi.clearAllMocks();
91
+ runtimeLoadConfigMock.mockReset();
92
+ runtimeLoadConfigMock.mockReturnValue({});
84
93
  mediaKindFromMimeMock.mockReturnValue("image");
85
94
  isVoiceCompatibleAudioMock.mockReturnValue(false);
86
95
  setMatrixRuntime(runtimeStub);
@@ -210,6 +219,8 @@ describe("sendMessageMatrix media", () => {
210
219
  describe("sendMessageMatrix threads", () => {
211
220
  beforeEach(() => {
212
221
  vi.clearAllMocks();
222
+ runtimeLoadConfigMock.mockReset();
223
+ runtimeLoadConfigMock.mockReturnValue({});
213
224
  setMatrixRuntime(runtimeStub);
214
225
  });
215
226
 
@@ -236,3 +247,80 @@ describe("sendMessageMatrix threads", () => {
236
247
  });
237
248
  });
238
249
  });
250
+
251
+ describe("sendMessageMatrix cfg threading", () => {
252
+ beforeEach(() => {
253
+ vi.clearAllMocks();
254
+ runtimeLoadConfigMock.mockReset();
255
+ runtimeLoadConfigMock.mockReturnValue({
256
+ channels: {
257
+ matrix: {
258
+ mediaMaxMb: 7,
259
+ },
260
+ },
261
+ });
262
+ setMatrixRuntime(runtimeStub);
263
+ });
264
+
265
+ it("does not call runtime loadConfig when cfg is provided", async () => {
266
+ const { client } = makeClient();
267
+ const providedCfg = {
268
+ channels: {
269
+ matrix: {
270
+ mediaMaxMb: 4,
271
+ },
272
+ },
273
+ };
274
+
275
+ await sendMessageMatrix("room:!room:example", "hello cfg", {
276
+ client,
277
+ cfg: providedCfg as any,
278
+ });
279
+
280
+ expect(runtimeLoadConfigMock).not.toHaveBeenCalled();
281
+ });
282
+
283
+ it("falls back to runtime loadConfig when cfg is omitted", async () => {
284
+ const { client } = makeClient();
285
+
286
+ await sendMessageMatrix("room:!room:example", "hello runtime", { client });
287
+
288
+ expect(runtimeLoadConfigMock).toHaveBeenCalledTimes(1);
289
+ });
290
+ });
291
+
292
+ describe("resolveMediaMaxBytes cfg threading", () => {
293
+ beforeEach(() => {
294
+ runtimeLoadConfigMock.mockReset();
295
+ runtimeLoadConfigMock.mockReturnValue({
296
+ channels: {
297
+ matrix: {
298
+ mediaMaxMb: 9,
299
+ },
300
+ },
301
+ });
302
+ setMatrixRuntime(runtimeStub);
303
+ });
304
+
305
+ it("uses provided cfg and skips runtime loadConfig", () => {
306
+ const providedCfg = {
307
+ channels: {
308
+ matrix: {
309
+ mediaMaxMb: 3,
310
+ },
311
+ },
312
+ };
313
+
314
+ const maxBytes = resolveMediaMaxBytes(undefined, providedCfg as any);
315
+
316
+ expect(maxBytes).toBe(3 * 1024 * 1024);
317
+ expect(runtimeLoadConfigMock).not.toHaveBeenCalled();
318
+ });
319
+
320
+ it("falls back to runtime loadConfig when cfg is omitted", () => {
321
+ const maxBytes = resolveMediaMaxBytes();
322
+
323
+ expect(maxBytes).toBe(9 * 1024 * 1024);
324
+ expect(runtimeLoadConfigMock).toHaveBeenCalledTimes(1);
325
+ });
326
+ });
@@ -1,5 +1,5 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { PollInput } from "openclaw/plugin-sdk";
2
+ import type { PollInput } from "openclaw/plugin-sdk/matrix";
3
3
  import { getMatrixRuntime } from "../runtime.js";
4
4
  import { buildPollStartContent, M_POLL_START } from "./poll-types.js";
5
5
  import { enqueueSend } from "./send-queue.js";
@@ -47,11 +47,12 @@ export async function sendMessageMatrix(
47
47
  client: opts.client,
48
48
  timeoutMs: opts.timeoutMs,
49
49
  accountId: opts.accountId,
50
+ cfg: opts.cfg,
50
51
  });
52
+ const cfg = opts.cfg ?? getCore().config.loadConfig();
51
53
  try {
52
54
  const roomId = await resolveMatrixRoomId(client, to);
53
55
  return await enqueueSend(roomId, async () => {
54
- const cfg = getCore().config.loadConfig();
55
56
  const tableMode = getCore().channel.text.resolveMarkdownTableMode({
56
57
  cfg,
57
58
  channel: "matrix",
@@ -81,7 +82,7 @@ export async function sendMessageMatrix(
81
82
 
82
83
  let lastMessageId = "";
83
84
  if (opts.mediaUrl) {
84
- const maxBytes = resolveMediaMaxBytes(opts.accountId);
85
+ const maxBytes = resolveMediaMaxBytes(opts.accountId, cfg);
85
86
  const media = await getCore().media.loadWebMedia(opts.mediaUrl, maxBytes);
86
87
  const uploaded = await uploadMediaMaybeEncrypted(client, roomId, media.buffer, {
87
88
  contentType: media.contentType,
@@ -91,7 +92,7 @@ export async function sendMessageMatrix(
91
92
  buffer: media.buffer,
92
93
  contentType: media.contentType,
93
94
  fileName: media.fileName,
94
- kind: media.kind,
95
+ kind: media.kind ?? "unknown",
95
96
  });
96
97
  const baseMsgType = resolveMatrixMsgType(media.contentType, media.fileName);
97
98
  const { useVoice } = resolveMatrixVoiceDecision({
@@ -171,6 +172,7 @@ export async function sendPollMatrix(
171
172
  client: opts.client,
172
173
  timeoutMs: opts.timeoutMs,
173
174
  accountId: opts.accountId,
175
+ cfg: opts.cfg,
174
176
  });
175
177
 
176
178
  try {
package/src/onboarding.ts CHANGED
@@ -1,13 +1,19 @@
1
- import type { DmPolicy } from "openclaw/plugin-sdk";
1
+ import type { DmPolicy } from "openclaw/plugin-sdk/matrix";
2
2
  import {
3
3
  addWildcardAllowFrom,
4
+ buildSingleChannelSecretPromptState,
5
+ formatResolvedUnresolvedNote,
4
6
  formatDocsLink,
7
+ hasConfiguredSecretInput,
5
8
  mergeAllowFromEntries,
9
+ promptSingleChannelSecretInput,
6
10
  promptChannelAccessConfig,
11
+ setTopLevelChannelGroupPolicy,
12
+ type SecretInput,
7
13
  type ChannelOnboardingAdapter,
8
14
  type ChannelOnboardingDmPolicy,
9
15
  type WizardPrompter,
10
- } from "openclaw/plugin-sdk";
16
+ } from "openclaw/plugin-sdk/matrix";
11
17
  import { listMatrixDirectoryGroupsLive } from "./directory-live.js";
12
18
  import { resolveMatrixAccount } from "./matrix/accounts.js";
13
19
  import { ensureMatrixSdkInstalled, isMatrixSdkAvailable } from "./matrix/deps.js";
@@ -139,17 +145,12 @@ async function promptMatrixAllowFrom(params: {
139
145
  }
140
146
 
141
147
  function setMatrixGroupPolicy(cfg: CoreConfig, groupPolicy: "open" | "allowlist" | "disabled") {
142
- return {
143
- ...cfg,
144
- channels: {
145
- ...cfg.channels,
146
- matrix: {
147
- ...cfg.channels?.matrix,
148
- enabled: true,
149
- groupPolicy,
150
- },
151
- },
152
- };
148
+ return setTopLevelChannelGroupPolicy({
149
+ cfg,
150
+ channel: "matrix",
151
+ groupPolicy,
152
+ enabled: true,
153
+ }) as CoreConfig;
153
154
  }
154
155
 
155
156
  function setMatrixGroupRooms(cfg: CoreConfig, roomKeys: string[]) {
@@ -265,22 +266,24 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
265
266
  ).trim();
266
267
 
267
268
  let accessToken = existing.accessToken ?? "";
268
- let password = existing.password ?? "";
269
+ let password: SecretInput | undefined = existing.password;
269
270
  let userId = existing.userId ?? "";
271
+ const existingPasswordConfigured = hasConfiguredSecretInput(existing.password);
272
+ const passwordConfigured = () => hasConfiguredSecretInput(password);
270
273
 
271
- if (accessToken || password) {
274
+ if (accessToken || passwordConfigured()) {
272
275
  const keep = await prompter.confirm({
273
276
  message: "Matrix credentials already configured. Keep them?",
274
277
  initialValue: true,
275
278
  });
276
279
  if (!keep) {
277
280
  accessToken = "";
278
- password = "";
281
+ password = undefined;
279
282
  userId = "";
280
283
  }
281
284
  }
282
285
 
283
- if (!accessToken && !password) {
286
+ if (!accessToken && !passwordConfigured()) {
284
287
  // Ask auth method FIRST before asking for user ID
285
288
  const authMode = await prompter.select({
286
289
  message: "Matrix auth method",
@@ -321,12 +324,31 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
321
324
  },
322
325
  }),
323
326
  ).trim();
324
- password = String(
325
- await prompter.text({
326
- message: "Matrix password",
327
- validate: (value) => (value?.trim() ? undefined : "Required"),
328
- }),
329
- ).trim();
327
+ const passwordPromptState = buildSingleChannelSecretPromptState({
328
+ accountConfigured: Boolean(existingPasswordConfigured),
329
+ hasConfigToken: existingPasswordConfigured,
330
+ allowEnv: true,
331
+ envValue: envPassword,
332
+ });
333
+ const passwordResult = await promptSingleChannelSecretInput({
334
+ cfg: next,
335
+ prompter,
336
+ providerHint: "matrix",
337
+ credentialLabel: "password",
338
+ accountConfigured: passwordPromptState.accountConfigured,
339
+ canUseEnv: passwordPromptState.canUseEnv,
340
+ hasConfigToken: passwordPromptState.hasConfigToken,
341
+ envPrompt: "MATRIX_PASSWORD detected. Use env var?",
342
+ keepPrompt: "Matrix password already configured. Keep it?",
343
+ inputPrompt: "Matrix password",
344
+ preferredEnvVar: "MATRIX_PASSWORD",
345
+ });
346
+ if (passwordResult.action === "set") {
347
+ password = passwordResult.value;
348
+ }
349
+ if (passwordResult.action === "use-env") {
350
+ password = undefined;
351
+ }
330
352
  }
331
353
  }
332
354
 
@@ -353,7 +375,7 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
353
375
  homeserver,
354
376
  userId: userId || undefined,
355
377
  accessToken: accessToken || undefined,
356
- password: password || undefined,
378
+ password: password,
357
379
  deviceName: deviceName || undefined,
358
380
  encryption: enableEncryption || undefined,
359
381
  },
@@ -408,18 +430,12 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
408
430
  }
409
431
  }
410
432
  roomKeys = [...resolvedIds, ...unresolved.map((entry) => entry.trim()).filter(Boolean)];
411
- if (resolvedIds.length > 0 || unresolved.length > 0) {
412
- await prompter.note(
413
- [
414
- resolvedIds.length > 0 ? `Resolved: ${resolvedIds.join(", ")}` : undefined,
415
- unresolved.length > 0
416
- ? `Unresolved (kept as typed): ${unresolved.join(", ")}`
417
- : undefined,
418
- ]
419
- .filter(Boolean)
420
- .join("\n"),
421
- "Matrix rooms",
422
- );
433
+ const resolution = formatResolvedUnresolvedNote({
434
+ resolved: resolvedIds,
435
+ unresolved,
436
+ });
437
+ if (resolution) {
438
+ await prompter.note(resolution, "Matrix rooms");
423
439
  }
424
440
  } catch (err) {
425
441
  await prompter.note(
@@ -0,0 +1,159 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/matrix";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ const mocks = vi.hoisted(() => ({
5
+ sendMessageMatrix: vi.fn(),
6
+ sendPollMatrix: vi.fn(),
7
+ }));
8
+
9
+ vi.mock("./matrix/send.js", () => ({
10
+ sendMessageMatrix: mocks.sendMessageMatrix,
11
+ sendPollMatrix: mocks.sendPollMatrix,
12
+ }));
13
+
14
+ vi.mock("./runtime.js", () => ({
15
+ getMatrixRuntime: () => ({
16
+ channel: {
17
+ text: {
18
+ chunkMarkdownText: (text: string) => [text],
19
+ },
20
+ },
21
+ }),
22
+ }));
23
+
24
+ import { matrixOutbound } from "./outbound.js";
25
+
26
+ describe("matrixOutbound cfg threading", () => {
27
+ beforeEach(() => {
28
+ mocks.sendMessageMatrix.mockReset();
29
+ mocks.sendPollMatrix.mockReset();
30
+ mocks.sendMessageMatrix.mockResolvedValue({ messageId: "evt-1", roomId: "!room:example" });
31
+ mocks.sendPollMatrix.mockResolvedValue({ eventId: "$poll", roomId: "!room:example" });
32
+ });
33
+
34
+ it("passes resolved cfg to sendMessageMatrix for text sends", async () => {
35
+ const cfg = {
36
+ channels: {
37
+ matrix: {
38
+ accessToken: "resolved-token",
39
+ },
40
+ },
41
+ } as OpenClawConfig;
42
+
43
+ await matrixOutbound.sendText!({
44
+ cfg,
45
+ to: "room:!room:example",
46
+ text: "hello",
47
+ accountId: "default",
48
+ threadId: "$thread",
49
+ replyToId: "$reply",
50
+ });
51
+
52
+ expect(mocks.sendMessageMatrix).toHaveBeenCalledWith(
53
+ "room:!room:example",
54
+ "hello",
55
+ expect.objectContaining({
56
+ cfg,
57
+ accountId: "default",
58
+ threadId: "$thread",
59
+ replyToId: "$reply",
60
+ }),
61
+ );
62
+ });
63
+
64
+ it("passes resolved cfg to sendMessageMatrix for media sends", async () => {
65
+ const cfg = {
66
+ channels: {
67
+ matrix: {
68
+ accessToken: "resolved-token",
69
+ },
70
+ },
71
+ } as OpenClawConfig;
72
+
73
+ await matrixOutbound.sendMedia!({
74
+ cfg,
75
+ to: "room:!room:example",
76
+ text: "caption",
77
+ mediaUrl: "file:///tmp/cat.png",
78
+ accountId: "default",
79
+ });
80
+
81
+ expect(mocks.sendMessageMatrix).toHaveBeenCalledWith(
82
+ "room:!room:example",
83
+ "caption",
84
+ expect.objectContaining({
85
+ cfg,
86
+ mediaUrl: "file:///tmp/cat.png",
87
+ }),
88
+ );
89
+ });
90
+
91
+ it("passes resolved cfg through injected deps.sendMatrix", async () => {
92
+ const cfg = {
93
+ channels: {
94
+ matrix: {
95
+ accessToken: "resolved-token",
96
+ },
97
+ },
98
+ } as OpenClawConfig;
99
+ const sendMatrix = vi.fn(async () => ({
100
+ messageId: "evt-injected",
101
+ roomId: "!room:example",
102
+ }));
103
+
104
+ await matrixOutbound.sendText!({
105
+ cfg,
106
+ to: "room:!room:example",
107
+ text: "hello via deps",
108
+ deps: { sendMatrix },
109
+ accountId: "default",
110
+ threadId: "$thread",
111
+ replyToId: "$reply",
112
+ });
113
+
114
+ expect(sendMatrix).toHaveBeenCalledWith(
115
+ "room:!room:example",
116
+ "hello via deps",
117
+ expect.objectContaining({
118
+ cfg,
119
+ accountId: "default",
120
+ threadId: "$thread",
121
+ replyToId: "$reply",
122
+ }),
123
+ );
124
+ });
125
+
126
+ it("passes resolved cfg to sendPollMatrix", async () => {
127
+ const cfg = {
128
+ channels: {
129
+ matrix: {
130
+ accessToken: "resolved-token",
131
+ },
132
+ },
133
+ } as OpenClawConfig;
134
+
135
+ await matrixOutbound.sendPoll!({
136
+ cfg,
137
+ to: "room:!room:example",
138
+ poll: {
139
+ question: "Snack?",
140
+ options: ["Pizza", "Sushi"],
141
+ },
142
+ accountId: "default",
143
+ threadId: "$thread",
144
+ });
145
+
146
+ expect(mocks.sendPollMatrix).toHaveBeenCalledWith(
147
+ "room:!room:example",
148
+ expect.objectContaining({
149
+ question: "Snack?",
150
+ options: ["Pizza", "Sushi"],
151
+ }),
152
+ expect.objectContaining({
153
+ cfg,
154
+ accountId: "default",
155
+ threadId: "$thread",
156
+ }),
157
+ );
158
+ });
159
+ });
package/src/outbound.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
1
+ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/matrix";
2
2
  import { sendMessageMatrix, sendPollMatrix } from "./matrix/send.js";
3
3
  import { getMatrixRuntime } from "./runtime.js";
4
4
 
@@ -7,11 +7,12 @@ export const matrixOutbound: ChannelOutboundAdapter = {
7
7
  chunker: (text, limit) => getMatrixRuntime().channel.text.chunkMarkdownText(text, limit),
8
8
  chunkerMode: "markdown",
9
9
  textChunkLimit: 4000,
10
- sendText: async ({ to, text, deps, replyToId, threadId, accountId }) => {
10
+ sendText: async ({ cfg, to, text, deps, replyToId, threadId, accountId }) => {
11
11
  const send = deps?.sendMatrix ?? sendMessageMatrix;
12
12
  const resolvedThreadId =
13
13
  threadId !== undefined && threadId !== null ? String(threadId) : undefined;
14
14
  const result = await send(to, text, {
15
+ cfg,
15
16
  replyToId: replyToId ?? undefined,
16
17
  threadId: resolvedThreadId,
17
18
  accountId: accountId ?? undefined,
@@ -22,11 +23,12 @@ export const matrixOutbound: ChannelOutboundAdapter = {
22
23
  roomId: result.roomId,
23
24
  };
24
25
  },
25
- sendMedia: async ({ to, text, mediaUrl, deps, replyToId, threadId, accountId }) => {
26
+ sendMedia: async ({ cfg, to, text, mediaUrl, deps, replyToId, threadId, accountId }) => {
26
27
  const send = deps?.sendMatrix ?? sendMessageMatrix;
27
28
  const resolvedThreadId =
28
29
  threadId !== undefined && threadId !== null ? String(threadId) : undefined;
29
30
  const result = await send(to, text, {
31
+ cfg,
30
32
  mediaUrl,
31
33
  replyToId: replyToId ?? undefined,
32
34
  threadId: resolvedThreadId,
@@ -38,10 +40,11 @@ export const matrixOutbound: ChannelOutboundAdapter = {
38
40
  roomId: result.roomId,
39
41
  };
40
42
  },
41
- sendPoll: async ({ to, poll, threadId, accountId }) => {
43
+ sendPoll: async ({ cfg, to, poll, threadId, accountId }) => {
42
44
  const resolvedThreadId =
43
45
  threadId !== undefined && threadId !== null ? String(threadId) : undefined;
44
46
  const result = await sendPollMatrix(to, poll, {
47
+ cfg,
45
48
  threadId: resolvedThreadId,
46
49
  accountId: accountId ?? undefined,
47
50
  });
@@ -1,4 +1,4 @@
1
- import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk";
1
+ import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk/matrix";
2
2
  import { describe, expect, it, vi, beforeEach } from "vitest";
3
3
  import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./directory-live.js";
4
4
  import { resolveMatrixTargets } from "./resolve-targets.js";
@@ -1,9 +1,10 @@
1
+ import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/compat";
1
2
  import type {
2
3
  ChannelDirectoryEntry,
3
4
  ChannelResolveKind,
4
5
  ChannelResolveResult,
5
6
  RuntimeEnv,
6
- } from "openclaw/plugin-sdk";
7
+ } from "openclaw/plugin-sdk/matrix";
7
8
  import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./directory-live.js";
8
9
 
9
10
  function findExactDirectoryMatches(
@@ -71,56 +72,54 @@ export async function resolveMatrixTargets(params: {
71
72
  kind: ChannelResolveKind;
72
73
  runtime?: RuntimeEnv;
73
74
  }): Promise<ChannelResolveResult[]> {
74
- const results: ChannelResolveResult[] = [];
75
- for (const input of params.inputs) {
76
- const trimmed = input.trim();
77
- if (!trimmed) {
78
- results.push({ input, resolved: false, note: "empty input" });
79
- continue;
80
- }
81
- if (params.kind === "user") {
82
- if (trimmed.startsWith("@") && trimmed.includes(":")) {
83
- results.push({ input, resolved: true, id: trimmed });
84
- continue;
75
+ return await mapAllowlistResolutionInputs({
76
+ inputs: params.inputs,
77
+ mapInput: async (input): Promise<ChannelResolveResult> => {
78
+ const trimmed = input.trim();
79
+ if (!trimmed) {
80
+ return { input, resolved: false, note: "empty input" };
81
+ }
82
+ if (params.kind === "user") {
83
+ if (trimmed.startsWith("@") && trimmed.includes(":")) {
84
+ return { input, resolved: true, id: trimmed };
85
+ }
86
+ try {
87
+ const matches = await listMatrixDirectoryPeersLive({
88
+ cfg: params.cfg,
89
+ query: trimmed,
90
+ limit: 5,
91
+ });
92
+ const best = pickBestUserMatch(matches, trimmed);
93
+ return {
94
+ input,
95
+ resolved: Boolean(best?.id),
96
+ id: best?.id,
97
+ name: best?.name,
98
+ note: best ? undefined : describeUserMatchFailure(matches, trimmed),
99
+ };
100
+ } catch (err) {
101
+ params.runtime?.error?.(`matrix resolve failed: ${String(err)}`);
102
+ return { input, resolved: false, note: "lookup failed" };
103
+ }
85
104
  }
86
105
  try {
87
- const matches = await listMatrixDirectoryPeersLive({
106
+ const matches = await listMatrixDirectoryGroupsLive({
88
107
  cfg: params.cfg,
89
108
  query: trimmed,
90
109
  limit: 5,
91
110
  });
92
- const best = pickBestUserMatch(matches, trimmed);
93
- results.push({
111
+ const best = pickBestGroupMatch(matches, trimmed);
112
+ return {
94
113
  input,
95
114
  resolved: Boolean(best?.id),
96
115
  id: best?.id,
97
116
  name: best?.name,
98
- note: best ? undefined : describeUserMatchFailure(matches, trimmed),
99
- });
117
+ note: matches.length > 1 ? "multiple matches; chose first" : undefined,
118
+ };
100
119
  } catch (err) {
101
120
  params.runtime?.error?.(`matrix resolve failed: ${String(err)}`);
102
- results.push({ input, resolved: false, note: "lookup failed" });
121
+ return { input, resolved: false, note: "lookup failed" };
103
122
  }
104
- continue;
105
- }
106
- try {
107
- const matches = await listMatrixDirectoryGroupsLive({
108
- cfg: params.cfg,
109
- query: trimmed,
110
- limit: 5,
111
- });
112
- const best = pickBestGroupMatch(matches, trimmed);
113
- results.push({
114
- input,
115
- resolved: Boolean(best?.id),
116
- id: best?.id,
117
- name: best?.name,
118
- note: matches.length > 1 ? "multiple matches; chose first" : undefined,
119
- });
120
- } catch (err) {
121
- params.runtime?.error?.(`matrix resolve failed: ${String(err)}`);
122
- results.push({ input, resolved: false, note: "lookup failed" });
123
- }
124
- }
125
- return results;
123
+ },
124
+ });
126
125
  }
package/src/runtime.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/matrix";
2
2
 
3
3
  let runtime: PluginRuntime | null = null;
4
4
 
@@ -0,0 +1,13 @@
1
+ import {
2
+ buildSecretInputSchema,
3
+ hasConfiguredSecretInput,
4
+ normalizeResolvedSecretInputString,
5
+ normalizeSecretInputString,
6
+ } from "openclaw/plugin-sdk/matrix";
7
+
8
+ export {
9
+ buildSecretInputSchema,
10
+ hasConfiguredSecretInput,
11
+ normalizeResolvedSecretInputString,
12
+ normalizeSecretInputString,
13
+ };
@@ -5,7 +5,7 @@ import {
5
5
  readNumberParam,
6
6
  readReactionParams,
7
7
  readStringParam,
8
- } from "openclaw/plugin-sdk";
8
+ } from "openclaw/plugin-sdk/matrix";
9
9
  import {
10
10
  deleteMatrixMessage,
11
11
  editMatrixMessage,