@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
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { ensureMatrixCryptoRuntime } from "./deps.js";
3
+
4
+ const logStub = vi.fn();
5
+
6
+ describe("ensureMatrixCryptoRuntime", () => {
7
+ it("returns immediately when matrix SDK loads", async () => {
8
+ const runCommand = vi.fn();
9
+ const requireFn = vi.fn(() => ({}));
10
+
11
+ await ensureMatrixCryptoRuntime({
12
+ log: logStub,
13
+ requireFn,
14
+ runCommand,
15
+ resolveFn: () => "/tmp/download-lib.js",
16
+ nodeExecutable: "/usr/bin/node",
17
+ });
18
+
19
+ expect(requireFn).toHaveBeenCalledTimes(1);
20
+ expect(runCommand).not.toHaveBeenCalled();
21
+ });
22
+
23
+ it("bootstraps missing crypto runtime and retries matrix SDK load", async () => {
24
+ let bootstrapped = false;
25
+ const requireFn = vi.fn(() => {
26
+ if (!bootstrapped) {
27
+ throw new Error(
28
+ "Cannot find module '@matrix-org/matrix-sdk-crypto-nodejs-linux-x64-gnu' (required by matrix sdk)",
29
+ );
30
+ }
31
+ return {};
32
+ });
33
+ const runCommand = vi.fn(async () => {
34
+ bootstrapped = true;
35
+ return { code: 0, stdout: "", stderr: "" };
36
+ });
37
+
38
+ await ensureMatrixCryptoRuntime({
39
+ log: logStub,
40
+ requireFn,
41
+ runCommand,
42
+ resolveFn: () => "/tmp/download-lib.js",
43
+ nodeExecutable: "/usr/bin/node",
44
+ });
45
+
46
+ expect(runCommand).toHaveBeenCalledWith({
47
+ argv: ["/usr/bin/node", "/tmp/download-lib.js"],
48
+ cwd: "/tmp",
49
+ timeoutMs: 300_000,
50
+ env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
51
+ });
52
+ expect(requireFn).toHaveBeenCalledTimes(2);
53
+ });
54
+
55
+ it("rethrows non-crypto module errors without bootstrapping", async () => {
56
+ const runCommand = vi.fn();
57
+ const requireFn = vi.fn(() => {
58
+ throw new Error("Cannot find module '@vector-im/matrix-bot-sdk'");
59
+ });
60
+
61
+ await expect(
62
+ ensureMatrixCryptoRuntime({
63
+ log: logStub,
64
+ requireFn,
65
+ runCommand,
66
+ resolveFn: () => "/tmp/download-lib.js",
67
+ nodeExecutable: "/usr/bin/node",
68
+ }),
69
+ ).rejects.toThrow("Cannot find module '@vector-im/matrix-bot-sdk'");
70
+
71
+ expect(runCommand).not.toHaveBeenCalled();
72
+ expect(requireFn).toHaveBeenCalledTimes(1);
73
+ });
74
+ });
@@ -2,9 +2,30 @@ import fs from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { runPluginCommandWithTimeout, type RuntimeEnv } from "openclaw/plugin-sdk";
5
+ import { runPluginCommandWithTimeout, type RuntimeEnv } from "openclaw/plugin-sdk/matrix";
6
6
 
7
7
  const MATRIX_SDK_PACKAGE = "@vector-im/matrix-bot-sdk";
8
+ const MATRIX_CRYPTO_DOWNLOAD_HELPER = "@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js";
9
+
10
+ function formatCommandError(result: { stderr: string; stdout: string }): string {
11
+ const stderr = result.stderr.trim();
12
+ if (stderr) {
13
+ return stderr;
14
+ }
15
+ const stdout = result.stdout.trim();
16
+ if (stdout) {
17
+ return stdout;
18
+ }
19
+ return "unknown error";
20
+ }
21
+
22
+ function isMissingMatrixCryptoRuntimeError(err: unknown): boolean {
23
+ const message = err instanceof Error ? err.message : String(err ?? "");
24
+ return (
25
+ message.includes("Cannot find module") &&
26
+ message.includes("@matrix-org/matrix-sdk-crypto-nodejs-")
27
+ );
28
+ }
8
29
 
9
30
  export function isMatrixSdkAvailable(): boolean {
10
31
  try {
@@ -21,6 +42,51 @@ function resolvePluginRoot(): string {
21
42
  return path.resolve(currentDir, "..", "..");
22
43
  }
23
44
 
45
+ export async function ensureMatrixCryptoRuntime(
46
+ params: {
47
+ log?: (message: string) => void;
48
+ requireFn?: (id: string) => unknown;
49
+ resolveFn?: (id: string) => string;
50
+ runCommand?: typeof runPluginCommandWithTimeout;
51
+ nodeExecutable?: string;
52
+ } = {},
53
+ ): Promise<void> {
54
+ const req = createRequire(import.meta.url);
55
+ const requireFn = params.requireFn ?? ((id: string) => req(id));
56
+ const resolveFn = params.resolveFn ?? ((id: string) => req.resolve(id));
57
+ const runCommand = params.runCommand ?? runPluginCommandWithTimeout;
58
+ const nodeExecutable = params.nodeExecutable ?? process.execPath;
59
+
60
+ try {
61
+ requireFn(MATRIX_SDK_PACKAGE);
62
+ return;
63
+ } catch (err) {
64
+ if (!isMissingMatrixCryptoRuntimeError(err)) {
65
+ throw err;
66
+ }
67
+ }
68
+
69
+ const scriptPath = resolveFn(MATRIX_CRYPTO_DOWNLOAD_HELPER);
70
+ params.log?.("matrix: crypto runtime missing; downloading platform library…");
71
+ const result = await runCommand({
72
+ argv: [nodeExecutable, scriptPath],
73
+ cwd: path.dirname(scriptPath),
74
+ timeoutMs: 300_000,
75
+ env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
76
+ });
77
+ if (result.code !== 0) {
78
+ throw new Error(`Matrix crypto runtime bootstrap failed: ${formatCommandError(result)}`);
79
+ }
80
+
81
+ try {
82
+ requireFn(MATRIX_SDK_PACKAGE);
83
+ } catch (err) {
84
+ throw new Error(
85
+ `Matrix crypto runtime remains unavailable after bootstrap: ${err instanceof Error ? err.message : String(err)}`,
86
+ );
87
+ }
88
+ }
89
+
24
90
  export async function ensureMatrixSdkInstalled(params: {
25
91
  runtime: RuntimeEnv;
26
92
  confirm?: (message: string) => Promise<boolean>;
@@ -3,7 +3,8 @@ import {
3
3
  issuePairingChallenge,
4
4
  readStoreAllowFromForDmPolicy,
5
5
  resolveDmGroupAccessWithLists,
6
- } from "openclaw/plugin-sdk";
6
+ resolveSenderScopedGroupPolicy,
7
+ } from "openclaw/plugin-sdk/matrix";
7
8
  import {
8
9
  normalizeMatrixAllowList,
9
10
  resolveMatrixAllowListMatch,
@@ -32,12 +33,10 @@ export async function resolveMatrixAccessState(params: {
32
33
  })
33
34
  : [];
34
35
  const normalizedGroupAllowFrom = normalizeMatrixAllowList(params.groupAllowFrom);
35
- const senderGroupPolicy =
36
- params.groupPolicy === "disabled"
37
- ? "disabled"
38
- : normalizedGroupAllowFrom.length > 0
39
- ? "allowlist"
40
- : "open";
36
+ const senderGroupPolicy = resolveSenderScopedGroupPolicy({
37
+ groupPolicy: params.groupPolicy,
38
+ groupAllowFrom: normalizedGroupAllowFrom,
39
+ });
41
40
  const access = resolveDmGroupAccessWithLists({
42
41
  isGroup: !params.isDirectMessage,
43
42
  dmPolicy: params.dmPolicy,
@@ -1,7 +1,11 @@
1
- import type { AllowlistMatch } from "openclaw/plugin-sdk";
1
+ import {
2
+ normalizeStringEntries,
3
+ resolveAllowlistMatchByCandidates,
4
+ type AllowlistMatch,
5
+ } from "openclaw/plugin-sdk/matrix";
2
6
 
3
7
  function normalizeAllowList(list?: Array<string | number>) {
4
- return (list ?? []).map((entry) => String(entry).trim()).filter(Boolean);
8
+ return normalizeStringEntries(list);
5
9
  }
6
10
 
7
11
  function normalizeMatrixUser(raw?: string | null): string {
@@ -65,6 +69,7 @@ export function normalizeMatrixAllowList(list?: Array<string | number>) {
65
69
  export type MatrixAllowListMatch = AllowlistMatch<
66
70
  "wildcard" | "id" | "prefixed-id" | "prefixed-user"
67
71
  >;
72
+ type MatrixAllowListSource = Exclude<MatrixAllowListMatch["matchSource"], undefined>;
68
73
 
69
74
  export function resolveMatrixAllowListMatch(params: {
70
75
  allowList: string[];
@@ -78,24 +83,12 @@ export function resolveMatrixAllowListMatch(params: {
78
83
  return { allowed: true, matchKey: "*", matchSource: "wildcard" };
79
84
  }
80
85
  const userId = normalizeMatrixUser(params.userId);
81
- const candidates: Array<{ value?: string; source: MatrixAllowListMatch["matchSource"] }> = [
86
+ const candidates: Array<{ value?: string; source: MatrixAllowListSource }> = [
82
87
  { value: userId, source: "id" },
83
88
  { value: userId ? `matrix:${userId}` : "", source: "prefixed-id" },
84
89
  { value: userId ? `user:${userId}` : "", source: "prefixed-user" },
85
90
  ];
86
- for (const candidate of candidates) {
87
- if (!candidate.value) {
88
- continue;
89
- }
90
- if (allowList.includes(candidate.value)) {
91
- return {
92
- allowed: true,
93
- matchKey: candidate.value,
94
- matchSource: candidate.source,
95
- };
96
- }
97
- }
98
- return { allowed: false };
91
+ return resolveAllowlistMatchByCandidates({ allowList, candidates });
99
92
  }
100
93
 
101
94
  export function resolveMatrixAllowListMatches(params: { allowList: string[]; userId?: string }) {
@@ -1,8 +1,8 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import { AutojoinRoomsMixin } from "@vector-im/matrix-bot-sdk";
3
- import type { RuntimeEnv } from "openclaw/plugin-sdk";
2
+ import type { RuntimeEnv } from "openclaw/plugin-sdk/matrix";
4
3
  import { getMatrixRuntime } from "../../runtime.js";
5
4
  import type { CoreConfig } from "../../types.js";
5
+ import { loadMatrixSdk } from "../sdk-runtime.js";
6
6
 
7
7
  export function registerMatrixAutoJoin(params: {
8
8
  client: MatrixClient;
@@ -26,6 +26,7 @@ export function registerMatrixAutoJoin(params: {
26
26
 
27
27
  if (autoJoin === "always") {
28
28
  // Use the built-in autojoin mixin for "always" mode
29
+ const { AutojoinRoomsMixin } = loadMatrixSdk();
29
30
  AutojoinRoomsMixin.setupOnClient(client);
30
31
  logVerbose("matrix: auto-join enabled for all invites");
31
32
  return;
@@ -1,5 +1,5 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk";
2
+ import type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk/matrix";
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import type { MatrixAuth } from "../client.js";
5
5
  import { registerMatrixMonitorEvents } from "./events.js";
@@ -1,5 +1,5 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk";
2
+ import type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk/matrix";
3
3
  import type { MatrixAuth } from "../client.js";
4
4
  import { sendReadReceiptMatrix } from "../send.js";
5
5
  import type { MatrixRawEvent } from "./types.js";
@@ -1,5 +1,5 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { PluginRuntime, RuntimeEnv, RuntimeLogger } from "openclaw/plugin-sdk";
2
+ import type { PluginRuntime, RuntimeEnv, RuntimeLogger } from "openclaw/plugin-sdk/matrix";
3
3
  import { describe, expect, it, vi } from "vitest";
4
4
  import { createMatrixRoomMessageHandler } from "./handler.js";
5
5
  import { EventType, type MatrixRawEvent } from "./types.js";
@@ -4,14 +4,17 @@ import {
4
4
  createScopedPairingAccess,
5
5
  createReplyPrefixOptions,
6
6
  createTypingCallbacks,
7
+ dispatchReplyFromConfigWithSettledDispatcher,
8
+ evaluateGroupRouteAccessForPolicy,
7
9
  formatAllowlistMatchMeta,
8
10
  logInboundDrop,
9
11
  logTypingFailure,
12
+ resolveInboundSessionEnvelopeContext,
10
13
  resolveControlCommandGate,
11
14
  type PluginRuntime,
12
15
  type RuntimeEnv,
13
16
  type RuntimeLogger,
14
- } from "openclaw/plugin-sdk";
17
+ } from "openclaw/plugin-sdk/matrix";
15
18
  import type { CoreConfig, MatrixRoomConfig, ReplyToMode } from "../../types.js";
16
19
  import { fetchEventSummary } from "../actions/summary.js";
17
20
  import {
@@ -192,10 +195,6 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
192
195
  });
193
196
  const isRoom = !isDirectMessage;
194
197
 
195
- if (isRoom && groupPolicy === "disabled") {
196
- return;
197
- }
198
-
199
198
  const roomConfigInfo = isRoom
200
199
  ? resolveMatrixRoomConfig({
201
200
  rooms: roomsConfig,
@@ -211,17 +210,21 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
211
210
  }`
212
211
  : "matchKey=none matchSource=none";
213
212
 
214
- if (isRoom && roomConfig && !roomConfigInfo?.allowed) {
215
- logVerboseMessage(`matrix: room disabled room=${roomId} (${roomMatchMeta})`);
216
- return;
217
- }
218
- if (isRoom && groupPolicy === "allowlist") {
219
- if (!roomConfigInfo?.allowlistConfigured) {
220
- logVerboseMessage(`matrix: drop room message (no allowlist, ${roomMatchMeta})`);
221
- return;
222
- }
223
- if (!roomConfig) {
224
- logVerboseMessage(`matrix: drop room message (not in allowlist, ${roomMatchMeta})`);
213
+ if (isRoom) {
214
+ const routeAccess = evaluateGroupRouteAccessForPolicy({
215
+ groupPolicy,
216
+ routeAllowlistConfigured: Boolean(roomConfigInfo?.allowlistConfigured),
217
+ routeMatched: Boolean(roomConfig),
218
+ routeEnabled: roomConfigInfo?.allowed ?? true,
219
+ });
220
+ if (!routeAccess.allowed) {
221
+ if (routeAccess.reason === "route_disabled") {
222
+ logVerboseMessage(`matrix: room disabled room=${roomId} (${roomMatchMeta})`);
223
+ } else if (routeAccess.reason === "empty_allowlist") {
224
+ logVerboseMessage(`matrix: drop room message (no allowlist, ${roomMatchMeta})`);
225
+ } else if (routeAccess.reason === "route_not_allowlisted") {
226
+ logVerboseMessage(`matrix: drop room message (not in allowlist, ${roomMatchMeta})`);
227
+ }
225
228
  return;
226
229
  }
227
230
  }
@@ -484,14 +487,12 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
484
487
  const textWithId = threadRootId
485
488
  ? `${bodyText}\n[matrix event id: ${messageId} room: ${roomId} thread: ${threadRootId}]`
486
489
  : `${bodyText}\n[matrix event id: ${messageId} room: ${roomId}]`;
487
- const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
488
- agentId: route.agentId,
489
- });
490
- const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
491
- const previousTimestamp = core.channel.session.readSessionUpdatedAt({
492
- storePath,
493
- sessionKey: route.sessionKey,
494
- });
490
+ const { storePath, envelopeOptions, previousTimestamp } =
491
+ resolveInboundSessionEnvelopeContext({
492
+ cfg,
493
+ agentId: route.agentId,
494
+ sessionKey: route.sessionKey,
495
+ });
495
496
  const body = core.channel.reply.formatInboundEnvelope({
496
497
  channel: "Matrix",
497
498
  from: envelopeFrom,
@@ -655,22 +656,18 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
655
656
  },
656
657
  });
657
658
 
658
- const { queuedFinal, counts } = await core.channel.reply.withReplyDispatcher({
659
+ const { queuedFinal, counts } = await dispatchReplyFromConfigWithSettledDispatcher({
660
+ cfg,
661
+ ctxPayload,
659
662
  dispatcher,
660
663
  onSettled: () => {
661
664
  markDispatchIdle();
662
665
  },
663
- run: () =>
664
- core.channel.reply.dispatchReplyFromConfig({
665
- ctx: ctxPayload,
666
- cfg,
667
- dispatcher,
668
- replyOptions: {
669
- ...replyOptions,
670
- skillFilter: roomConfig?.skills,
671
- onModelSelected,
672
- },
673
- }),
666
+ replyOptions: {
667
+ ...replyOptions,
668
+ skillFilter: roomConfig?.skills,
669
+ onModelSelected,
670
+ },
674
671
  });
675
672
  if (!queuedFinal) {
676
673
  return;
@@ -1,13 +1,13 @@
1
1
  import {
2
- createLoggerBackedRuntime,
3
2
  GROUP_POLICY_BLOCKED_LABEL,
4
3
  mergeAllowlist,
4
+ resolveRuntimeEnv,
5
5
  resolveAllowlistProviderRuntimeGroupPolicy,
6
6
  resolveDefaultGroupPolicy,
7
7
  summarizeMapping,
8
8
  warnMissingProviderGroupPolicyFallbackOnce,
9
9
  type RuntimeEnv,
10
- } from "openclaw/plugin-sdk";
10
+ } from "openclaw/plugin-sdk/matrix";
11
11
  import { resolveMatrixTargets } from "../../resolve-targets.js";
12
12
  import { getMatrixRuntime } from "../../runtime.js";
13
13
  import type { CoreConfig, MatrixConfig, MatrixRoomConfig, ReplyToMode } from "../../types.js";
@@ -241,11 +241,10 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
241
241
  }
242
242
 
243
243
  const logger = core.logging.getChildLogger({ module: "matrix-auto-reply" });
244
- const runtime: RuntimeEnv =
245
- opts.runtime ??
246
- createLoggerBackedRuntime({
247
- logger,
248
- });
244
+ const runtime: RuntimeEnv = resolveRuntimeEnv({
245
+ runtime: opts.runtime,
246
+ logger,
247
+ });
249
248
  const logVerboseMessage = (message: string) => {
250
249
  if (!core.logging.shouldLogVerbose()) {
251
250
  return;
@@ -3,7 +3,7 @@ import {
3
3
  formatLocationText,
4
4
  toLocationContext,
5
5
  type NormalizedLocation,
6
- } from "openclaw/plugin-sdk";
6
+ } from "openclaw/plugin-sdk/matrix";
7
7
  import { EventType } from "./types.js";
8
8
 
9
9
  export type MatrixLocationPayload = {
@@ -1,4 +1,4 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/matrix";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { setMatrixRuntime } from "../../runtime.js";
4
4
  import { downloadMatrixMedia } from "./media.js";
@@ -1,5 +1,5 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk";
2
+ import type { PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/matrix";
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
4
 
5
5
  const sendMessageMatrixMock = vi.hoisted(() => vi.fn().mockResolvedValue({ messageId: "mx-1" }));
@@ -1,5 +1,5 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { MarkdownTableMode, ReplyPayload, RuntimeEnv } from "openclaw/plugin-sdk";
2
+ import type { MarkdownTableMode, ReplyPayload, RuntimeEnv } from "openclaw/plugin-sdk/matrix";
3
3
  import { getMatrixRuntime } from "../../runtime.js";
4
4
  import { sendMessageMatrix } from "../send.js";
5
5
 
@@ -1,4 +1,4 @@
1
- import { buildChannelKeyCandidates, resolveChannelEntryMatch } from "openclaw/plugin-sdk";
1
+ import { buildChannelKeyCandidates, resolveChannelEntryMatch } from "openclaw/plugin-sdk/matrix";
2
2
  import type { MatrixRoomConfig } from "../../types.js";
3
3
 
4
4
  export type MatrixRoomConfigResolved = {
@@ -7,7 +7,7 @@
7
7
  * - m.poll.end - Closes a poll
8
8
  */
9
9
 
10
- import type { PollInput } from "openclaw/plugin-sdk";
10
+ import type { PollInput } from "openclaw/plugin-sdk/matrix";
11
11
 
12
12
  export const M_POLL_START = "m.poll.start" as const;
13
13
  export const M_POLL_RESPONSE = "m.poll.response" as const;
@@ -1,4 +1,4 @@
1
- import type { BaseProbeResult } from "openclaw/plugin-sdk";
1
+ import type { BaseProbeResult } from "openclaw/plugin-sdk/matrix";
2
2
  import { createMatrixClient, isBunRuntime } from "./client.js";
3
3
 
4
4
  export type MatrixProbe = BaseProbeResult & {
@@ -0,0 +1,18 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ type MatrixSdkRuntime = typeof import("@vector-im/matrix-bot-sdk");
4
+
5
+ let cachedMatrixSdkRuntime: MatrixSdkRuntime | null = null;
6
+
7
+ export function loadMatrixSdk(): MatrixSdkRuntime {
8
+ if (cachedMatrixSdkRuntime) {
9
+ return cachedMatrixSdkRuntime;
10
+ }
11
+ const req = createRequire(import.meta.url);
12
+ cachedMatrixSdkRuntime = req("@vector-im/matrix-bot-sdk") as MatrixSdkRuntime;
13
+ return cachedMatrixSdkRuntime;
14
+ }
15
+
16
+ export function getMatrixLogService() {
17
+ return loadMatrixSdk().LogService;
18
+ }
@@ -32,19 +32,19 @@ function findAccountConfig(
32
32
  return undefined;
33
33
  }
34
34
 
35
- export function resolveMediaMaxBytes(accountId?: string): number | undefined {
36
- const cfg = getCore().config.loadConfig() as CoreConfig;
35
+ export function resolveMediaMaxBytes(accountId?: string, cfg?: CoreConfig): number | undefined {
36
+ const resolvedCfg = cfg ?? (getCore().config.loadConfig() as CoreConfig);
37
37
  // Check account-specific config first (case-insensitive key matching)
38
38
  const accountConfig = findAccountConfig(
39
- cfg.channels?.matrix?.accounts as Record<string, unknown> | undefined,
39
+ resolvedCfg.channels?.matrix?.accounts as Record<string, unknown> | undefined,
40
40
  accountId ?? "",
41
41
  );
42
42
  if (typeof accountConfig?.mediaMaxMb === "number") {
43
43
  return (accountConfig.mediaMaxMb as number) * 1024 * 1024;
44
44
  }
45
45
  // Fall back to top-level config
46
- if (typeof cfg.channels?.matrix?.mediaMaxMb === "number") {
47
- return cfg.channels.matrix.mediaMaxMb * 1024 * 1024;
46
+ if (typeof resolvedCfg.channels?.matrix?.mediaMaxMb === "number") {
47
+ return resolvedCfg.channels.matrix.mediaMaxMb * 1024 * 1024;
48
48
  }
49
49
  return undefined;
50
50
  }
@@ -53,6 +53,7 @@ export async function resolveMatrixClient(opts: {
53
53
  client?: MatrixClient;
54
54
  timeoutMs?: number;
55
55
  accountId?: string;
56
+ cfg?: CoreConfig;
56
57
  }): Promise<{ client: MatrixClient; stopOnDone: boolean }> {
57
58
  ensureNodeRuntime();
58
59
  if (opts.client) {
@@ -84,10 +85,11 @@ export async function resolveMatrixClient(opts: {
84
85
  const client = await resolveSharedMatrixClient({
85
86
  timeoutMs: opts.timeoutMs,
86
87
  accountId,
88
+ cfg: opts.cfg,
87
89
  });
88
90
  return { client, stopOnDone: false };
89
91
  }
90
- const auth = await resolveMatrixAuth({ accountId });
92
+ const auth = await resolveMatrixAuth({ accountId, cfg: opts.cfg });
91
93
  const client = await createPreparedMatrixClient({
92
94
  auth,
93
95
  timeoutMs: opts.timeoutMs,
@@ -85,6 +85,7 @@ export type MatrixSendResult = {
85
85
  };
86
86
 
87
87
  export type MatrixSendOpts = {
88
+ cfg?: import("../../types.js").CoreConfig;
88
89
  client?: import("@vector-im/matrix-bot-sdk").MatrixClient;
89
90
  mediaUrl?: string;
90
91
  accountId?: string;
@@ -1,3 +1,5 @@
1
+ import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
2
+
1
3
  export const DEFAULT_SEND_GAP_MS = 150;
2
4
 
3
5
  type MatrixSendQueueOptions = {
@@ -6,37 +8,19 @@ type MatrixSendQueueOptions = {
6
8
  };
7
9
 
8
10
  // Serialize sends per room to preserve Matrix delivery order.
9
- const roomQueues = new Map<string, Promise<void>>();
11
+ const roomQueues = new KeyedAsyncQueue();
10
12
 
11
- export async function enqueueSend<T>(
13
+ export function enqueueSend<T>(
12
14
  roomId: string,
13
15
  fn: () => Promise<T>,
14
16
  options?: MatrixSendQueueOptions,
15
17
  ): Promise<T> {
16
18
  const gapMs = options?.gapMs ?? DEFAULT_SEND_GAP_MS;
17
19
  const delayFn = options?.delayFn ?? delay;
18
- const previous = roomQueues.get(roomId) ?? Promise.resolve();
19
-
20
- const next = previous
21
- .catch(() => {})
22
- .then(async () => {
23
- await delayFn(gapMs);
24
- return await fn();
25
- });
26
-
27
- const queueMarker = next.then(
28
- () => {},
29
- () => {},
30
- );
31
- roomQueues.set(roomId, queueMarker);
32
-
33
- queueMarker.finally(() => {
34
- if (roomQueues.get(roomId) === queueMarker) {
35
- roomQueues.delete(roomId);
36
- }
20
+ return roomQueues.enqueue(roomId, async () => {
21
+ await delayFn(gapMs);
22
+ return await fn();
37
23
  });
38
-
39
- return await next;
40
24
  }
41
25
 
42
26
  function delay(ms: number): Promise<void> {