@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.3.7
4
+
5
+ ### Changes
6
+
7
+ - Version alignment with core OpenClaw release numbers.
8
+
9
+ ## 2026.3.3
10
+
11
+ ### Changes
12
+
13
+ - Version alignment with core OpenClaw release numbers.
14
+
15
+ ## 2026.3.2
16
+
17
+ ### Changes
18
+
19
+ - Version alignment with core OpenClaw release numbers.
20
+
3
21
  ## 2026.3.1
4
22
 
5
23
  ### Changes
package/index.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/matrix";
2
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/matrix";
3
3
  import { matrixPlugin } from "./src/channel.js";
4
+ import { ensureMatrixCryptoRuntime } from "./src/matrix/deps.js";
4
5
  import { setMatrixRuntime } from "./src/runtime.js";
5
6
 
6
7
  const plugin = {
@@ -10,6 +11,10 @@ const plugin = {
10
11
  configSchema: emptyPluginConfigSchema(),
11
12
  register(api: OpenClawPluginApi) {
12
13
  setMatrixRuntime(api.runtime);
14
+ void ensureMatrixCryptoRuntime({ log: api.logger.info }).catch((err) => {
15
+ const message = err instanceof Error ? err.message : String(err);
16
+ api.logger.warn?.(`matrix: crypto runtime bootstrap failed: ${message}`);
17
+ });
13
18
  api.registerChannel({ plugin: matrixPlugin });
14
19
  },
15
20
  };
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@openclaw/matrix",
3
- "version": "2026.3.1",
3
+ "version": "2026.3.7",
4
4
  "description": "OpenClaw Matrix channel plugin",
5
5
  "type": "module",
6
6
  "dependencies": {
7
+ "@mariozechner/pi-agent-core": "0.55.3",
7
8
  "@matrix-org/matrix-sdk-crypto-nodejs": "^0.4.0",
8
9
  "@vector-im/matrix-bot-sdk": "0.8.0-element.3",
9
10
  "markdown-it": "14.1.1",
package/src/actions.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  type ChannelMessageActionContext,
7
7
  type ChannelMessageActionName,
8
8
  type ChannelToolSend,
9
- } from "openclaw/plugin-sdk";
9
+ } from "openclaw/plugin-sdk/matrix";
10
10
  import { resolveMatrixAccount } from "./matrix/accounts.js";
11
11
  import { handleMatrixAction } from "./tool-actions.js";
12
12
  import type { CoreConfig } from "./types.js";
@@ -1,4 +1,4 @@
1
- import type { PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/matrix";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { matrixPlugin } from "./channel.js";
4
4
  import { setMatrixRuntime } from "./runtime.js";
package/src/channel.ts CHANGED
@@ -1,16 +1,21 @@
1
+ import {
2
+ buildAccountScopedDmSecurityPolicy,
3
+ buildOpenGroupPolicyWarning,
4
+ collectAllowlistProviderGroupPolicyWarnings,
5
+ createScopedAccountConfigAccessors,
6
+ } from "openclaw/plugin-sdk/compat";
1
7
  import {
2
8
  applyAccountNameToChannelSection,
3
9
  buildChannelConfigSchema,
10
+ buildProbeChannelStatusSummary,
11
+ collectStatusIssuesFromLastError,
4
12
  DEFAULT_ACCOUNT_ID,
5
13
  deleteAccountFromConfigSection,
6
- formatPairingApproveHint,
7
14
  normalizeAccountId,
8
15
  PAIRING_APPROVED_MESSAGE,
9
- resolveAllowlistProviderRuntimeGroupPolicy,
10
- resolveDefaultGroupPolicy,
11
16
  setAccountEnabledInConfigSection,
12
17
  type ChannelPlugin,
13
- } from "openclaw/plugin-sdk";
18
+ } from "openclaw/plugin-sdk/matrix";
14
19
  import { matrixMessageActions } from "./actions.js";
15
20
  import { MatrixConfigSchema } from "./config-schema.js";
16
21
  import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./directory-live.js";
@@ -32,6 +37,7 @@ import { sendMessageMatrix } from "./matrix/send.js";
32
37
  import { matrixOnboardingAdapter } from "./onboarding.js";
33
38
  import { matrixOutbound } from "./outbound.js";
34
39
  import { resolveMatrixTargets } from "./resolve-targets.js";
40
+ import { normalizeSecretInputString } from "./secret-input.js";
35
41
  import type { CoreConfig } from "./types.js";
36
42
 
37
43
  // Mutex for serializing account startup (workaround for concurrent dynamic import race condition)
@@ -93,6 +99,13 @@ function buildMatrixConfigUpdate(
93
99
  };
94
100
  }
95
101
 
102
+ const matrixConfigAccessors = createScopedAccountConfigAccessors({
103
+ resolveAccount: ({ cfg, accountId }) =>
104
+ resolveMatrixAccountConfig({ cfg: cfg as CoreConfig, accountId }),
105
+ resolveAllowFrom: (account) => account.dm?.allowFrom,
106
+ formatAllowFrom: (allowFrom) => normalizeMatrixAllowList(allowFrom),
107
+ });
108
+
96
109
  export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
97
110
  id: "matrix",
98
111
  meta,
@@ -148,41 +161,38 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
148
161
  configured: account.configured,
149
162
  baseUrl: account.homeserver,
150
163
  }),
151
- resolveAllowFrom: ({ cfg, accountId }) => {
152
- const matrixConfig = resolveMatrixAccountConfig({ cfg: cfg as CoreConfig, accountId });
153
- return (matrixConfig.dm?.allowFrom ?? []).map((entry: string | number) => String(entry));
154
- },
155
- formatAllowFrom: ({ allowFrom }) => normalizeMatrixAllowList(allowFrom),
164
+ ...matrixConfigAccessors,
156
165
  },
157
166
  security: {
158
- resolveDmPolicy: ({ account }) => {
159
- const accountId = account.accountId;
160
- const prefix =
161
- accountId && accountId !== "default"
162
- ? `channels.matrix.accounts.${accountId}.dm`
163
- : "channels.matrix.dm";
164
- return {
165
- policy: account.config.dm?.policy ?? "pairing",
167
+ resolveDmPolicy: ({ cfg, accountId, account }) => {
168
+ return buildAccountScopedDmSecurityPolicy({
169
+ cfg: cfg as CoreConfig,
170
+ channelKey: "matrix",
171
+ accountId,
172
+ fallbackAccountId: account.accountId ?? DEFAULT_ACCOUNT_ID,
173
+ policy: account.config.dm?.policy,
166
174
  allowFrom: account.config.dm?.allowFrom ?? [],
167
- policyPath: `${prefix}.policy`,
168
- allowFromPath: `${prefix}.allowFrom`,
169
- approveHint: formatPairingApproveHint("matrix"),
175
+ allowFromPathSuffix: "dm.",
170
176
  normalizeEntry: (raw) => normalizeMatrixUserId(raw),
171
- };
177
+ });
172
178
  },
173
179
  collectWarnings: ({ account, cfg }) => {
174
- const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg as CoreConfig);
175
- const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
180
+ return collectAllowlistProviderGroupPolicyWarnings({
181
+ cfg: cfg as CoreConfig,
176
182
  providerConfigPresent: (cfg as CoreConfig).channels?.matrix !== undefined,
177
- groupPolicy: account.config.groupPolicy,
178
- defaultGroupPolicy,
183
+ configuredGroupPolicy: account.config.groupPolicy,
184
+ collect: (groupPolicy) =>
185
+ groupPolicy === "open"
186
+ ? [
187
+ buildOpenGroupPolicyWarning({
188
+ surface: "Matrix rooms",
189
+ openBehavior: "allows any room to trigger (mention-gated)",
190
+ remediation:
191
+ 'Set channels.matrix.groupPolicy="allowlist" + channels.matrix.groups (and optionally channels.matrix.groupAllowFrom) to restrict rooms',
192
+ }),
193
+ ]
194
+ : [],
179
195
  });
180
- if (groupPolicy !== "open") {
181
- return [];
182
- }
183
- return [
184
- '- Matrix rooms: groupPolicy="open" allows any room to trigger (mention-gated). Set channels.matrix.groupPolicy="allowlist" + channels.matrix.groups (and optionally channels.matrix.groupAllowFrom) to restrict rooms.',
185
- ];
186
196
  },
187
197
  },
188
198
  groups: {
@@ -325,7 +335,7 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
325
335
  return "Matrix requires --homeserver";
326
336
  }
327
337
  const accessToken = input.accessToken?.trim();
328
- const password = input.password?.trim();
338
+ const password = normalizeSecretInputString(input.password);
329
339
  const userId = input.userId?.trim();
330
340
  if (!accessToken && !password) {
331
341
  return "Matrix requires --access-token or --password";
@@ -363,7 +373,7 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
363
373
  homeserver: input.homeserver?.trim(),
364
374
  userId: input.userId?.trim(),
365
375
  accessToken: input.accessToken?.trim(),
366
- password: input.password?.trim(),
376
+ password: normalizeSecretInputString(input.password),
367
377
  deviceName: input.deviceName?.trim(),
368
378
  initialSyncLimit: input.initialSyncLimit,
369
379
  });
@@ -378,31 +388,9 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
378
388
  lastStopAt: null,
379
389
  lastError: null,
380
390
  },
381
- collectStatusIssues: (accounts) =>
382
- accounts.flatMap((account) => {
383
- const lastError = typeof account.lastError === "string" ? account.lastError.trim() : "";
384
- if (!lastError) {
385
- return [];
386
- }
387
- return [
388
- {
389
- channel: "matrix",
390
- accountId: account.accountId,
391
- kind: "runtime",
392
- message: `Channel error: ${lastError}`,
393
- },
394
- ];
395
- }),
396
- buildChannelSummary: ({ snapshot }) => ({
397
- configured: snapshot.configured ?? false,
398
- baseUrl: snapshot.baseUrl ?? null,
399
- running: snapshot.running ?? false,
400
- lastStartAt: snapshot.lastStartAt ?? null,
401
- lastStopAt: snapshot.lastStopAt ?? null,
402
- lastError: snapshot.lastError ?? null,
403
- probe: snapshot.probe,
404
- lastProbeAt: snapshot.lastProbeAt ?? null,
405
- }),
391
+ collectStatusIssues: (accounts) => collectStatusIssuesFromLastError("matrix", accounts),
392
+ buildChannelSummary: ({ snapshot }) =>
393
+ buildProbeChannelStatusSummary(snapshot, { baseUrl: snapshot.baseUrl ?? null }),
406
394
  probeAccount: async ({ account, timeoutMs, cfg }) => {
407
395
  try {
408
396
  const auth = await resolveMatrixAuth({
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { MatrixConfigSchema } from "./config-schema.js";
3
+
4
+ describe("MatrixConfigSchema SecretInput", () => {
5
+ it("accepts SecretRef password at top-level", () => {
6
+ const result = MatrixConfigSchema.safeParse({
7
+ homeserver: "https://matrix.example.org",
8
+ userId: "@bot:example.org",
9
+ password: { source: "env", provider: "default", id: "MATRIX_PASSWORD" },
10
+ });
11
+ expect(result.success).toBe(true);
12
+ });
13
+
14
+ it("accepts SecretRef password on account", () => {
15
+ const result = MatrixConfigSchema.safeParse({
16
+ accounts: {
17
+ work: {
18
+ homeserver: "https://matrix.example.org",
19
+ userId: "@bot:example.org",
20
+ password: { source: "env", provider: "default", id: "MATRIX_WORK_PASSWORD" },
21
+ },
22
+ },
23
+ });
24
+ expect(result.success).toBe(true);
25
+ });
26
+ });
@@ -1,5 +1,6 @@
1
- import { MarkdownConfigSchema, ToolPolicySchema } from "openclaw/plugin-sdk";
1
+ import { MarkdownConfigSchema, ToolPolicySchema } from "openclaw/plugin-sdk/matrix";
2
2
  import { z } from "zod";
3
+ import { buildSecretInputSchema } from "./secret-input.js";
3
4
 
4
5
  const allowFromEntry = z.union([z.string(), z.number()]);
5
6
 
@@ -43,7 +44,7 @@ export const MatrixConfigSchema = z.object({
43
44
  homeserver: z.string().optional(),
44
45
  userId: z.string().optional(),
45
46
  accessToken: z.string().optional(),
46
- password: z.string().optional(),
47
+ password: buildSecretInputSchema().optional(),
47
48
  deviceName: z.string().optional(),
48
49
  initialSyncLimit: z.number().optional(),
49
50
  encryption: z.boolean().optional(),
@@ -1,4 +1,4 @@
1
- import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk";
1
+ import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk/matrix";
2
2
  import { resolveMatrixAuth } from "./matrix/client.js";
3
3
 
4
4
  type MatrixUserResult = {
@@ -1,4 +1,4 @@
1
- import type { ChannelGroupContext, GroupToolPolicyConfig } from "openclaw/plugin-sdk";
1
+ import type { ChannelGroupContext, GroupToolPolicyConfig } from "openclaw/plugin-sdk/matrix";
2
2
  import { resolveMatrixAccountConfig } from "./matrix/accounts.js";
3
3
  import { resolveMatrixRoomConfig } from "./matrix/monitor/rooms.js";
4
4
  import type { CoreConfig } from "./types.js";
@@ -1,8 +1,6 @@
1
- import {
2
- DEFAULT_ACCOUNT_ID,
3
- normalizeAccountId,
4
- normalizeOptionalAccountId,
5
- } from "openclaw/plugin-sdk/account-id";
1
+ import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
+ import { createAccountListHelpers } from "openclaw/plugin-sdk/matrix";
3
+ import { hasConfiguredSecretInput } from "../secret-input.js";
6
4
  import type { CoreConfig, MatrixConfig } from "../types.js";
7
5
  import { resolveMatrixConfigForAccount } from "./client.js";
8
6
  import { credentialsMatchConfig, loadMatrixCredentials } from "./credentials.js";
@@ -34,44 +32,11 @@ export type ResolvedMatrixAccount = {
34
32
  config: MatrixConfig;
35
33
  };
36
34
 
37
- function listConfiguredAccountIds(cfg: CoreConfig): string[] {
38
- const accounts = cfg.channels?.matrix?.accounts;
39
- if (!accounts || typeof accounts !== "object") {
40
- return [];
41
- }
42
- // Normalize and de-duplicate keys so listing and resolution use the same semantics
43
- return [
44
- ...new Set(
45
- Object.keys(accounts)
46
- .filter(Boolean)
47
- .map((id) => normalizeAccountId(id)),
48
- ),
49
- ];
50
- }
51
-
52
- export function listMatrixAccountIds(cfg: CoreConfig): string[] {
53
- const ids = listConfiguredAccountIds(cfg);
54
- if (ids.length === 0) {
55
- // Fall back to default if no accounts configured (legacy top-level config)
56
- return [DEFAULT_ACCOUNT_ID];
57
- }
58
- return ids.toSorted((a, b) => a.localeCompare(b));
59
- }
60
-
61
- export function resolveDefaultMatrixAccountId(cfg: CoreConfig): string {
62
- const preferred = normalizeOptionalAccountId(cfg.channels?.matrix?.defaultAccount);
63
- if (
64
- preferred &&
65
- listMatrixAccountIds(cfg).some((accountId) => normalizeAccountId(accountId) === preferred)
66
- ) {
67
- return preferred;
68
- }
69
- const ids = listMatrixAccountIds(cfg);
70
- if (ids.includes(DEFAULT_ACCOUNT_ID)) {
71
- return DEFAULT_ACCOUNT_ID;
72
- }
73
- return ids[0] ?? DEFAULT_ACCOUNT_ID;
74
- }
35
+ const {
36
+ listAccountIds: listMatrixAccountIds,
37
+ resolveDefaultAccountId: resolveDefaultMatrixAccountId,
38
+ } = createAccountListHelpers("matrix", { normalizeAccountId });
39
+ export { listMatrixAccountIds, resolveDefaultMatrixAccountId };
75
40
 
76
41
  function resolveAccountConfig(cfg: CoreConfig, accountId: string): MatrixConfig | undefined {
77
42
  const accounts = cfg.channels?.matrix?.accounts;
@@ -106,7 +71,7 @@ export function resolveMatrixAccount(params: {
106
71
  const hasUserId = Boolean(resolved.userId);
107
72
  const hasAccessToken = Boolean(resolved.accessToken);
108
73
  const hasPassword = Boolean(resolved.password);
109
- const hasPasswordAuth = hasUserId && hasPassword;
74
+ const hasPasswordAuth = hasUserId && (hasPassword || hasConfiguredSecretInput(base.password));
110
75
  const stored = loadMatrixCredentials(process.env, accountId);
111
76
  const hasStored =
112
77
  stored && resolved.homeserver
@@ -1,12 +1,17 @@
1
- import { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
1
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
+ import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/matrix";
3
3
  import { getMatrixRuntime } from "../../runtime.js";
4
+ import {
5
+ normalizeResolvedSecretInputString,
6
+ normalizeSecretInputString,
7
+ } from "../../secret-input.js";
4
8
  import type { CoreConfig } from "../../types.js";
9
+ import { loadMatrixSdk } from "../sdk-runtime.js";
5
10
  import { ensureMatrixSdkLoggingConfigured } from "./logging.js";
6
11
  import type { MatrixAuth, MatrixResolvedConfig } from "./types.js";
7
12
 
8
- function clean(value?: string): string {
9
- return value?.trim() ?? "";
13
+ function clean(value: unknown, path: string): string {
14
+ return normalizeResolvedSecretInputString({ value, path }) ?? "";
10
15
  }
11
16
 
12
17
  /** Shallow-merge known nested config sub-objects so partial overrides inherit base values. */
@@ -52,11 +57,23 @@ export function resolveMatrixConfigForAccount(
52
57
  // nested object inheritance (dm, actions, groups) so partial overrides work.
53
58
  const matrix = accountConfig ? deepMergeConfig(matrixBase, accountConfig) : matrixBase;
54
59
 
55
- const homeserver = clean(matrix.homeserver) || clean(env.MATRIX_HOMESERVER);
56
- const userId = clean(matrix.userId) || clean(env.MATRIX_USER_ID);
57
- const accessToken = clean(matrix.accessToken) || clean(env.MATRIX_ACCESS_TOKEN) || undefined;
58
- const password = clean(matrix.password) || clean(env.MATRIX_PASSWORD) || undefined;
59
- const deviceName = clean(matrix.deviceName) || clean(env.MATRIX_DEVICE_NAME) || undefined;
60
+ const homeserver =
61
+ clean(matrix.homeserver, "channels.matrix.homeserver") ||
62
+ clean(env.MATRIX_HOMESERVER, "MATRIX_HOMESERVER");
63
+ const userId =
64
+ clean(matrix.userId, "channels.matrix.userId") || clean(env.MATRIX_USER_ID, "MATRIX_USER_ID");
65
+ const accessToken =
66
+ clean(matrix.accessToken, "channels.matrix.accessToken") ||
67
+ clean(env.MATRIX_ACCESS_TOKEN, "MATRIX_ACCESS_TOKEN") ||
68
+ undefined;
69
+ const password =
70
+ clean(matrix.password, "channels.matrix.password") ||
71
+ clean(env.MATRIX_PASSWORD, "MATRIX_PASSWORD") ||
72
+ undefined;
73
+ const deviceName =
74
+ clean(matrix.deviceName, "channels.matrix.deviceName") ||
75
+ clean(env.MATRIX_DEVICE_NAME, "MATRIX_DEVICE_NAME") ||
76
+ undefined;
60
77
  const initialSyncLimit =
61
78
  typeof matrix.initialSyncLimit === "number"
62
79
  ? Math.max(0, Math.floor(matrix.initialSyncLimit))
@@ -119,6 +136,7 @@ export async function resolveMatrixAuth(params?: {
119
136
  if (!userId) {
120
137
  // Fetch userId from access token via whoami
121
138
  ensureMatrixSdkLoggingConfigured();
139
+ const { MatrixClient } = loadMatrixSdk();
122
140
  const tempClient = new MatrixClient(resolved.homeserver, resolved.accessToken);
123
141
  const whoami = await tempClient.getUserId();
124
142
  userId = whoami;
@@ -167,28 +185,36 @@ export async function resolveMatrixAuth(params?: {
167
185
  );
168
186
  }
169
187
 
170
- // Login with password using HTTP API
171
- const loginResponse = await fetch(`${resolved.homeserver}/_matrix/client/v3/login`, {
172
- method: "POST",
173
- headers: { "Content-Type": "application/json" },
174
- body: JSON.stringify({
175
- type: "m.login.password",
176
- identifier: { type: "m.id.user", user: resolved.userId },
177
- password: resolved.password,
178
- initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway",
179
- }),
188
+ // Login with password using HTTP API.
189
+ const { response: loginResponse, release: releaseLoginResponse } = await fetchWithSsrFGuard({
190
+ url: `${resolved.homeserver}/_matrix/client/v3/login`,
191
+ init: {
192
+ method: "POST",
193
+ headers: { "Content-Type": "application/json" },
194
+ body: JSON.stringify({
195
+ type: "m.login.password",
196
+ identifier: { type: "m.id.user", user: resolved.userId },
197
+ password: resolved.password,
198
+ initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway",
199
+ }),
200
+ },
201
+ auditContext: "matrix.login",
180
202
  });
181
-
182
- if (!loginResponse.ok) {
183
- const errorText = await loginResponse.text();
184
- throw new Error(`Matrix login failed: ${errorText}`);
185
- }
186
-
187
- const login = (await loginResponse.json()) as {
188
- access_token?: string;
189
- user_id?: string;
190
- device_id?: string;
191
- };
203
+ const login = await (async () => {
204
+ try {
205
+ if (!loginResponse.ok) {
206
+ const errorText = await loginResponse.text();
207
+ throw new Error(`Matrix login failed: ${errorText}`);
208
+ }
209
+ return (await loginResponse.json()) as {
210
+ access_token?: string;
211
+ user_id?: string;
212
+ device_id?: string;
213
+ };
214
+ } finally {
215
+ await releaseLoginResponse();
216
+ }
217
+ })();
192
218
 
193
219
  const accessToken = login.access_token?.trim();
194
220
  if (!accessToken) {
@@ -1,11 +1,10 @@
1
1
  import fs from "node:fs";
2
- import type { IStorageProvider, ICryptoStorageProvider } from "@vector-im/matrix-bot-sdk";
3
- import {
4
- LogService,
2
+ import type {
3
+ IStorageProvider,
4
+ ICryptoStorageProvider,
5
5
  MatrixClient,
6
- SimpleFsStorageProvider,
7
- RustSdkCryptoStorageProvider,
8
6
  } from "@vector-im/matrix-bot-sdk";
7
+ import { loadMatrixSdk } from "../sdk-runtime.js";
9
8
  import { ensureMatrixSdkLoggingConfigured } from "./logging.js";
10
9
  import {
11
10
  maybeMigrateLegacyStorage,
@@ -14,6 +13,7 @@ import {
14
13
  } from "./storage.js";
15
14
 
16
15
  function sanitizeUserIdList(input: unknown, label: string): string[] {
16
+ const LogService = loadMatrixSdk().LogService;
17
17
  if (input == null) {
18
18
  return [];
19
19
  }
@@ -44,6 +44,8 @@ export async function createMatrixClient(params: {
44
44
  localTimeoutMs?: number;
45
45
  accountId?: string | null;
46
46
  }): Promise<MatrixClient> {
47
+ const { MatrixClient, SimpleFsStorageProvider, RustSdkCryptoStorageProvider, LogService } =
48
+ loadMatrixSdk();
47
49
  ensureMatrixSdkLoggingConfigured();
48
50
  const env = process.env;
49
51
 
@@ -1,7 +1,15 @@
1
- import { ConsoleLogger, LogService } from "@vector-im/matrix-bot-sdk";
1
+ import { loadMatrixSdk } from "../sdk-runtime.js";
2
2
 
3
3
  let matrixSdkLoggingConfigured = false;
4
- const matrixSdkBaseLogger = new ConsoleLogger();
4
+ let matrixSdkBaseLogger:
5
+ | {
6
+ trace: (module: string, ...messageOrObject: unknown[]) => void;
7
+ debug: (module: string, ...messageOrObject: unknown[]) => void;
8
+ info: (module: string, ...messageOrObject: unknown[]) => void;
9
+ warn: (module: string, ...messageOrObject: unknown[]) => void;
10
+ error: (module: string, ...messageOrObject: unknown[]) => void;
11
+ }
12
+ | undefined;
5
13
 
6
14
  function shouldSuppressMatrixHttpNotFound(module: string, messageOrObject: unknown[]): boolean {
7
15
  if (module !== "MatrixHttpClient") {
@@ -19,18 +27,20 @@ export function ensureMatrixSdkLoggingConfigured(): void {
19
27
  if (matrixSdkLoggingConfigured) {
20
28
  return;
21
29
  }
30
+ const { ConsoleLogger, LogService } = loadMatrixSdk();
31
+ matrixSdkBaseLogger = new ConsoleLogger();
22
32
  matrixSdkLoggingConfigured = true;
23
33
 
24
34
  LogService.setLogger({
25
- trace: (module, ...messageOrObject) => matrixSdkBaseLogger.trace(module, ...messageOrObject),
26
- debug: (module, ...messageOrObject) => matrixSdkBaseLogger.debug(module, ...messageOrObject),
27
- info: (module, ...messageOrObject) => matrixSdkBaseLogger.info(module, ...messageOrObject),
28
- warn: (module, ...messageOrObject) => matrixSdkBaseLogger.warn(module, ...messageOrObject),
35
+ trace: (module, ...messageOrObject) => matrixSdkBaseLogger?.trace(module, ...messageOrObject),
36
+ debug: (module, ...messageOrObject) => matrixSdkBaseLogger?.debug(module, ...messageOrObject),
37
+ info: (module, ...messageOrObject) => matrixSdkBaseLogger?.info(module, ...messageOrObject),
38
+ warn: (module, ...messageOrObject) => matrixSdkBaseLogger?.warn(module, ...messageOrObject),
29
39
  error: (module, ...messageOrObject) => {
30
40
  if (shouldSuppressMatrixHttpNotFound(module, messageOrObject)) {
31
41
  return;
32
42
  }
33
- matrixSdkBaseLogger.error(module, ...messageOrObject);
43
+ matrixSdkBaseLogger?.error(module, ...messageOrObject);
34
44
  },
35
45
  });
36
46
  }
@@ -1,7 +1,7 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import { LogService } from "@vector-im/matrix-bot-sdk";
3
2
  import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
4
3
  import type { CoreConfig } from "../../types.js";
4
+ import { getMatrixLogService } from "../sdk-runtime.js";
5
5
  import { resolveMatrixAuth } from "./config.js";
6
6
  import { createMatrixClient } from "./create-client.js";
7
7
  import { startMatrixClientWithGrace } from "./startup.js";
@@ -81,6 +81,7 @@ async function ensureSharedClientStarted(params: {
81
81
  params.state.cryptoReady = true;
82
82
  }
83
83
  } catch (err) {
84
+ const LogService = getMatrixLogService();
84
85
  LogService.warn("MatrixClientLite", "Failed to prepare crypto:", err);
85
86
  }
86
87
  }
@@ -89,6 +90,7 @@ async function ensureSharedClientStarted(params: {
89
90
  client,
90
91
  onError: (err: unknown) => {
91
92
  params.state.started = false;
93
+ const LogService = getMatrixLogService();
92
94
  LogService.error("MatrixClientLite", "client.start() error:", err);
93
95
  },
94
96
  });
@@ -1,6 +1,6 @@
1
- import { LogService } from "@vector-im/matrix-bot-sdk";
2
1
  import { createMatrixClient } from "./client/create-client.js";
3
2
  import { startMatrixClientWithGrace } from "./client/startup.js";
3
+ import { getMatrixLogService } from "./sdk-runtime.js";
4
4
 
5
5
  type MatrixClientBootstrapAuth = {
6
6
  homeserver: string;
@@ -39,6 +39,7 @@ export async function createPreparedMatrixClient(opts: {
39
39
  await startMatrixClientWithGrace({
40
40
  client,
41
41
  onError: (err: unknown) => {
42
+ const LogService = getMatrixLogService();
42
43
  LogService.error("MatrixClientBootstrap", "client.start() error:", err);
43
44
  },
44
45
  });