@openclaw/matrix 2026.3.2 → 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 (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/index.ts +2 -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 +40 -46
  7. package/src/config-schema.ts +1 -1
  8. package/src/directory-live.ts +1 -1
  9. package/src/group-mentions.ts +1 -1
  10. package/src/matrix/accounts.ts +7 -43
  11. package/src/matrix/client/config.ts +1 -1
  12. package/src/matrix/deps.ts +1 -1
  13. package/src/matrix/monitor/access-policy.ts +6 -7
  14. package/src/matrix/monitor/allowlist.ts +6 -2
  15. package/src/matrix/monitor/auto-join.ts +1 -1
  16. package/src/matrix/monitor/events.test.ts +1 -1
  17. package/src/matrix/monitor/events.ts +1 -1
  18. package/src/matrix/monitor/handler.body-for-agent.test.ts +1 -1
  19. package/src/matrix/monitor/handler.ts +33 -36
  20. package/src/matrix/monitor/index.ts +6 -7
  21. package/src/matrix/monitor/location.ts +1 -1
  22. package/src/matrix/monitor/media.test.ts +1 -1
  23. package/src/matrix/monitor/replies.test.ts +1 -1
  24. package/src/matrix/monitor/replies.ts +1 -1
  25. package/src/matrix/monitor/rooms.ts +1 -1
  26. package/src/matrix/poll-types.ts +1 -1
  27. package/src/matrix/probe.ts +1 -1
  28. package/src/matrix/send/client.ts +8 -6
  29. package/src/matrix/send/types.ts +1 -0
  30. package/src/matrix/send.test.ts +86 -2
  31. package/src/matrix/send.ts +6 -4
  32. package/src/onboarding.ts +19 -16
  33. package/src/outbound.test.ts +159 -0
  34. package/src/outbound.ts +7 -4
  35. package/src/resolve-targets.test.ts +1 -1
  36. package/src/resolve-targets.ts +39 -40
  37. package/src/runtime.ts +1 -1
  38. package/src/secret-input.ts +8 -14
  39. package/src/tool-actions.ts +1 -1
  40. package/src/types.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
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
+
3
15
  ## 2026.3.2
4
16
 
5
17
  ### Changes
package/index.ts CHANGED
@@ -1,5 +1,5 @@
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
4
  import { ensureMatrixCryptoRuntime } from "./src/matrix/deps.js";
5
5
  import { setMatrixRuntime } from "./src/runtime.js";
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@openclaw/matrix",
3
- "version": "2026.3.2",
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,17 +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,
4
10
  buildProbeChannelStatusSummary,
11
+ collectStatusIssuesFromLastError,
5
12
  DEFAULT_ACCOUNT_ID,
6
13
  deleteAccountFromConfigSection,
7
- formatPairingApproveHint,
8
14
  normalizeAccountId,
9
15
  PAIRING_APPROVED_MESSAGE,
10
- resolveAllowlistProviderRuntimeGroupPolicy,
11
- resolveDefaultGroupPolicy,
12
16
  setAccountEnabledInConfigSection,
13
17
  type ChannelPlugin,
14
- } from "openclaw/plugin-sdk";
18
+ } from "openclaw/plugin-sdk/matrix";
15
19
  import { matrixMessageActions } from "./actions.js";
16
20
  import { MatrixConfigSchema } from "./config-schema.js";
17
21
  import { listMatrixDirectoryGroupsLive, listMatrixDirectoryPeersLive } from "./directory-live.js";
@@ -95,6 +99,13 @@ function buildMatrixConfigUpdate(
95
99
  };
96
100
  }
97
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
+
98
109
  export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
99
110
  id: "matrix",
100
111
  meta,
@@ -150,41 +161,38 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
150
161
  configured: account.configured,
151
162
  baseUrl: account.homeserver,
152
163
  }),
153
- resolveAllowFrom: ({ cfg, accountId }) => {
154
- const matrixConfig = resolveMatrixAccountConfig({ cfg: cfg as CoreConfig, accountId });
155
- return (matrixConfig.dm?.allowFrom ?? []).map((entry: string | number) => String(entry));
156
- },
157
- formatAllowFrom: ({ allowFrom }) => normalizeMatrixAllowList(allowFrom),
164
+ ...matrixConfigAccessors,
158
165
  },
159
166
  security: {
160
- resolveDmPolicy: ({ account }) => {
161
- const accountId = account.accountId;
162
- const prefix =
163
- accountId && accountId !== "default"
164
- ? `channels.matrix.accounts.${accountId}.dm`
165
- : "channels.matrix.dm";
166
- return {
167
- 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,
168
174
  allowFrom: account.config.dm?.allowFrom ?? [],
169
- policyPath: `${prefix}.policy`,
170
- allowFromPath: `${prefix}.allowFrom`,
171
- approveHint: formatPairingApproveHint("matrix"),
175
+ allowFromPathSuffix: "dm.",
172
176
  normalizeEntry: (raw) => normalizeMatrixUserId(raw),
173
- };
177
+ });
174
178
  },
175
179
  collectWarnings: ({ account, cfg }) => {
176
- const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg as CoreConfig);
177
- const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
180
+ return collectAllowlistProviderGroupPolicyWarnings({
181
+ cfg: cfg as CoreConfig,
178
182
  providerConfigPresent: (cfg as CoreConfig).channels?.matrix !== undefined,
179
- groupPolicy: account.config.groupPolicy,
180
- 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
+ : [],
181
195
  });
182
- if (groupPolicy !== "open") {
183
- return [];
184
- }
185
- return [
186
- '- 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.',
187
- ];
188
196
  },
189
197
  },
190
198
  groups: {
@@ -380,21 +388,7 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
380
388
  lastStopAt: null,
381
389
  lastError: null,
382
390
  },
383
- collectStatusIssues: (accounts) =>
384
- accounts.flatMap((account) => {
385
- const lastError = typeof account.lastError === "string" ? account.lastError.trim() : "";
386
- if (!lastError) {
387
- return [];
388
- }
389
- return [
390
- {
391
- channel: "matrix",
392
- accountId: account.accountId,
393
- kind: "runtime",
394
- message: `Channel error: ${lastError}`,
395
- },
396
- ];
397
- }),
391
+ collectStatusIssues: (accounts) => collectStatusIssuesFromLastError("matrix", accounts),
398
392
  buildChannelSummary: ({ snapshot }) =>
399
393
  buildProbeChannelStatusSummary(snapshot, { baseUrl: snapshot.baseUrl ?? null }),
400
394
  probeAccount: async ({ account, timeoutMs, cfg }) => {
@@ -1,4 +1,4 @@
1
- import { MarkdownConfigSchema, ToolPolicySchema } from "openclaw/plugin-sdk";
1
+ import { MarkdownConfigSchema, ToolPolicySchema } from "openclaw/plugin-sdk/matrix";
2
2
  import { z } from "zod";
3
3
  import { buildSecretInputSchema } from "./secret-input.js";
4
4
 
@@ -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,5 @@
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";
6
3
  import { hasConfiguredSecretInput } from "../secret-input.js";
7
4
  import type { CoreConfig, MatrixConfig } from "../types.js";
8
5
  import { resolveMatrixConfigForAccount } from "./client.js";
@@ -35,44 +32,11 @@ export type ResolvedMatrixAccount = {
35
32
  config: MatrixConfig;
36
33
  };
37
34
 
38
- function listConfiguredAccountIds(cfg: CoreConfig): string[] {
39
- const accounts = cfg.channels?.matrix?.accounts;
40
- if (!accounts || typeof accounts !== "object") {
41
- return [];
42
- }
43
- // Normalize and de-duplicate keys so listing and resolution use the same semantics
44
- return [
45
- ...new Set(
46
- Object.keys(accounts)
47
- .filter(Boolean)
48
- .map((id) => normalizeAccountId(id)),
49
- ),
50
- ];
51
- }
52
-
53
- export function listMatrixAccountIds(cfg: CoreConfig): string[] {
54
- const ids = listConfiguredAccountIds(cfg);
55
- if (ids.length === 0) {
56
- // Fall back to default if no accounts configured (legacy top-level config)
57
- return [DEFAULT_ACCOUNT_ID];
58
- }
59
- return ids.toSorted((a, b) => a.localeCompare(b));
60
- }
61
-
62
- export function resolveDefaultMatrixAccountId(cfg: CoreConfig): string {
63
- const preferred = normalizeOptionalAccountId(cfg.channels?.matrix?.defaultAccount);
64
- if (
65
- preferred &&
66
- listMatrixAccountIds(cfg).some((accountId) => normalizeAccountId(accountId) === preferred)
67
- ) {
68
- return preferred;
69
- }
70
- const ids = listMatrixAccountIds(cfg);
71
- if (ids.includes(DEFAULT_ACCOUNT_ID)) {
72
- return DEFAULT_ACCOUNT_ID;
73
- }
74
- return ids[0] ?? DEFAULT_ACCOUNT_ID;
75
- }
35
+ const {
36
+ listAccountIds: listMatrixAccountIds,
37
+ resolveDefaultAccountId: resolveDefaultMatrixAccountId,
38
+ } = createAccountListHelpers("matrix", { normalizeAccountId });
39
+ export { listMatrixAccountIds, resolveDefaultMatrixAccountId };
76
40
 
77
41
  function resolveAccountConfig(cfg: CoreConfig, accountId: string): MatrixConfig | undefined {
78
42
  const accounts = cfg.channels?.matrix?.accounts;
@@ -1,5 +1,5 @@
1
- import { fetchWithSsrFGuard } from "openclaw/plugin-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
4
  import {
5
5
  normalizeResolvedSecretInputString,
@@ -2,7 +2,7 @@ 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
8
  const MATRIX_CRYPTO_DOWNLOAD_HELPER = "@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js";
@@ -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 { resolveAllowlistMatchByCandidates, 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 {
@@ -1,5 +1,5 @@
1
1
  import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import type { RuntimeEnv } from "openclaw/plugin-sdk";
2
+ import type { RuntimeEnv } from "openclaw/plugin-sdk/matrix";
3
3
  import { getMatrixRuntime } from "../../runtime.js";
4
4
  import type { CoreConfig } from "../../types.js";
5
5
  import { loadMatrixSdk } from "../sdk-runtime.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 { 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 & {
@@ -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,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
 
@@ -34,6 +34,7 @@ const loadWebMediaMock = vi.fn().mockResolvedValue({
34
34
  contentType: "image/png",
35
35
  kind: "image",
36
36
  });
37
+ const runtimeLoadConfigMock = vi.fn(() => ({}));
37
38
  const mediaKindFromMimeMock = vi.fn(() => "image");
38
39
  const isVoiceCompatibleAudioMock = vi.fn(() => false);
39
40
  const getImageMetadataMock = vi.fn().mockResolvedValue(null);
@@ -41,7 +42,7 @@ const resizeToJpegMock = vi.fn();
41
42
 
42
43
  const runtimeStub = {
43
44
  config: {
44
- loadConfig: () => ({}),
45
+ loadConfig: runtimeLoadConfigMock,
45
46
  },
46
47
  media: {
47
48
  loadWebMedia: loadWebMediaMock as unknown as PluginRuntime["media"]["loadWebMedia"],
@@ -65,6 +66,7 @@ const runtimeStub = {
65
66
  } as unknown as PluginRuntime;
66
67
 
67
68
  let sendMessageMatrix: typeof import("./send.js").sendMessageMatrix;
69
+ let resolveMediaMaxBytes: typeof import("./send/client.js").resolveMediaMaxBytes;
68
70
 
69
71
  const makeClient = () => {
70
72
  const sendMessage = vi.fn().mockResolvedValue("evt1");
@@ -80,11 +82,14 @@ const makeClient = () => {
80
82
  beforeAll(async () => {
81
83
  setMatrixRuntime(runtimeStub);
82
84
  ({ sendMessageMatrix } = await import("./send.js"));
85
+ ({ resolveMediaMaxBytes } = await import("./send/client.js"));
83
86
  });
84
87
 
85
88
  describe("sendMessageMatrix media", () => {
86
89
  beforeEach(() => {
87
90
  vi.clearAllMocks();
91
+ runtimeLoadConfigMock.mockReset();
92
+ runtimeLoadConfigMock.mockReturnValue({});
88
93
  mediaKindFromMimeMock.mockReturnValue("image");
89
94
  isVoiceCompatibleAudioMock.mockReturnValue(false);
90
95
  setMatrixRuntime(runtimeStub);
@@ -214,6 +219,8 @@ describe("sendMessageMatrix media", () => {
214
219
  describe("sendMessageMatrix threads", () => {
215
220
  beforeEach(() => {
216
221
  vi.clearAllMocks();
222
+ runtimeLoadConfigMock.mockReset();
223
+ runtimeLoadConfigMock.mockReturnValue({});
217
224
  setMatrixRuntime(runtimeStub);
218
225
  });
219
226
 
@@ -240,3 +247,80 @@ describe("sendMessageMatrix threads", () => {
240
247
  });
241
248
  });
242
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,17 +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,
4
5
  formatResolvedUnresolvedNote,
5
6
  formatDocsLink,
6
7
  hasConfiguredSecretInput,
7
8
  mergeAllowFromEntries,
8
9
  promptSingleChannelSecretInput,
9
10
  promptChannelAccessConfig,
11
+ setTopLevelChannelGroupPolicy,
10
12
  type SecretInput,
11
13
  type ChannelOnboardingAdapter,
12
14
  type ChannelOnboardingDmPolicy,
13
15
  type WizardPrompter,
14
- } from "openclaw/plugin-sdk";
16
+ } from "openclaw/plugin-sdk/matrix";
15
17
  import { listMatrixDirectoryGroupsLive } from "./directory-live.js";
16
18
  import { resolveMatrixAccount } from "./matrix/accounts.js";
17
19
  import { ensureMatrixSdkInstalled, isMatrixSdkAvailable } from "./matrix/deps.js";
@@ -143,17 +145,12 @@ async function promptMatrixAllowFrom(params: {
143
145
  }
144
146
 
145
147
  function setMatrixGroupPolicy(cfg: CoreConfig, groupPolicy: "open" | "allowlist" | "disabled") {
146
- return {
147
- ...cfg,
148
- channels: {
149
- ...cfg.channels,
150
- matrix: {
151
- ...cfg.channels?.matrix,
152
- enabled: true,
153
- groupPolicy,
154
- },
155
- },
156
- };
148
+ return setTopLevelChannelGroupPolicy({
149
+ cfg,
150
+ channel: "matrix",
151
+ groupPolicy,
152
+ enabled: true,
153
+ }) as CoreConfig;
157
154
  }
158
155
 
159
156
  function setMatrixGroupRooms(cfg: CoreConfig, roomKeys: string[]) {
@@ -327,14 +324,20 @@ export const matrixOnboardingAdapter: ChannelOnboardingAdapter = {
327
324
  },
328
325
  }),
329
326
  ).trim();
327
+ const passwordPromptState = buildSingleChannelSecretPromptState({
328
+ accountConfigured: Boolean(existingPasswordConfigured),
329
+ hasConfigToken: existingPasswordConfigured,
330
+ allowEnv: true,
331
+ envValue: envPassword,
332
+ });
330
333
  const passwordResult = await promptSingleChannelSecretInput({
331
334
  cfg: next,
332
335
  prompter,
333
336
  providerHint: "matrix",
334
337
  credentialLabel: "password",
335
- accountConfigured: Boolean(existingPasswordConfigured),
336
- canUseEnv: Boolean(envPassword?.trim()) && !existingPasswordConfigured,
337
- hasConfigToken: existingPasswordConfigured,
338
+ accountConfigured: passwordPromptState.accountConfigured,
339
+ canUseEnv: passwordPromptState.canUseEnv,
340
+ hasConfigToken: passwordPromptState.hasConfigToken,
338
341
  envPrompt: "MATRIX_PASSWORD detected. Use env var?",
339
342
  keepPrompt: "Matrix password already configured. Keep it?",
340
343
  inputPrompt: "Matrix password",
@@ -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
 
@@ -1,19 +1,13 @@
1
1
  import {
2
+ buildSecretInputSchema,
2
3
  hasConfiguredSecretInput,
3
4
  normalizeResolvedSecretInputString,
4
5
  normalizeSecretInputString,
5
- } from "openclaw/plugin-sdk";
6
- import { z } from "zod";
6
+ } from "openclaw/plugin-sdk/matrix";
7
7
 
8
- export { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString };
9
-
10
- export function buildSecretInputSchema() {
11
- return z.union([
12
- z.string(),
13
- z.object({
14
- source: z.enum(["env", "file", "exec"]),
15
- provider: z.string().min(1),
16
- id: z.string().min(1),
17
- }),
18
- ]);
19
- }
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,
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DmPolicy, GroupPolicy, SecretInput } from "openclaw/plugin-sdk";
1
+ import type { DmPolicy, GroupPolicy, SecretInput } from "openclaw/plugin-sdk/matrix";
2
2
  export type { DmPolicy, GroupPolicy };
3
3
 
4
4
  export type ReplyToMode = "off" | "first" | "all";