diff --git a/extensions/matrix/src/actions.test.ts b/extensions/matrix/src/actions.test.ts index 7c7e5ef466d..60fe63c1dcf 100644 --- a/extensions/matrix/src/actions.test.ts +++ b/extensions/matrix/src/actions.test.ts @@ -78,4 +78,44 @@ describe("matrixMessageActions", () => { expect(actions).toContain("set-profile"); expect(supportsAction!({ action: "set-profile" } as never)).toBe(true); }); + + it("hides gated actions when the default Matrix account disables them", () => { + const actions = matrixMessageActions.listActions!({ + cfg: { + channels: { + matrix: { + defaultAccount: "assistant", + actions: { + messages: true, + reactions: true, + pins: true, + profile: true, + memberInfo: true, + channelInfo: true, + verification: true, + }, + accounts: { + assistant: { + homeserver: "https://matrix.example.org", + userId: "@bot:example.org", + accessToken: "token", + encryption: true, + actions: { + messages: false, + reactions: false, + pins: false, + profile: false, + memberInfo: false, + channelInfo: false, + verification: false, + }, + }, + }, + }, + }, + } as CoreConfig, + } as never); + + expect(actions).toEqual(["poll", "poll-vote"]); + }); }); diff --git a/extensions/matrix/src/actions.ts b/extensions/matrix/src/actions.ts index cb666a32c91..2954a1bae69 100644 --- a/extensions/matrix/src/actions.ts +++ b/extensions/matrix/src/actions.ts @@ -7,7 +7,7 @@ import { type ChannelMessageActionName, type ChannelToolSend, } from "openclaw/plugin-sdk/matrix"; -import { resolveMatrixAccount } from "./matrix/accounts.js"; +import { resolveDefaultMatrixAccountId, resolveMatrixAccount } from "./matrix/accounts.js"; import { handleMatrixAction } from "./tool-actions.js"; import type { CoreConfig } from "./types.js"; @@ -28,44 +28,56 @@ const MATRIX_PLUGIN_HANDLED_ACTIONS = new Set([ "permissions", ]); -function createMatrixExposedActions() { - return new Set(["poll", ...MATRIX_PLUGIN_HANDLED_ACTIONS]); +function createMatrixExposedActions(params: { + gate: ReturnType; + encryptionEnabled: boolean; +}) { + const actions = new Set(["poll", "poll-vote"]); + if (params.gate("messages")) { + actions.add("send"); + actions.add("read"); + actions.add("edit"); + actions.add("delete"); + } + if (params.gate("reactions")) { + actions.add("react"); + actions.add("reactions"); + } + if (params.gate("pins")) { + actions.add("pin"); + actions.add("unpin"); + actions.add("list-pins"); + } + if (params.gate("profile")) { + actions.add("set-profile"); + } + if (params.gate("memberInfo")) { + actions.add("member-info"); + } + if (params.gate("channelInfo")) { + actions.add("channel-info"); + } + if (params.encryptionEnabled && params.gate("verification")) { + actions.add("permissions"); + } + return actions; } export const matrixMessageActions: ChannelMessageActionAdapter = { listActions: ({ cfg }) => { - const account = resolveMatrixAccount({ cfg: cfg as CoreConfig }); + const resolvedCfg = cfg as CoreConfig; + const account = resolveMatrixAccount({ + cfg: resolvedCfg, + accountId: resolveDefaultMatrixAccountId(resolvedCfg), + }); if (!account.enabled || !account.configured) { return []; } - const gate = createActionGate((cfg as CoreConfig).channels?.["matrix"]?.actions); - const actions = createMatrixExposedActions(); - if (gate("reactions")) { - actions.add("react"); - actions.add("reactions"); - } - if (gate("messages")) { - actions.add("read"); - actions.add("edit"); - actions.add("delete"); - } - if (gate("pins")) { - actions.add("pin"); - actions.add("unpin"); - actions.add("list-pins"); - } - if (gate("profile")) { - actions.add("set-profile"); - } - if (gate("memberInfo")) { - actions.add("member-info"); - } - if (gate("channelInfo")) { - actions.add("channel-info"); - } - if (account.config.encryption === true && gate("verification")) { - actions.add("permissions"); - } + const gate = createActionGate(account.config.actions); + const actions = createMatrixExposedActions({ + gate, + encryptionEnabled: account.config.encryption === true, + }); return Array.from(actions); }, supportsAction: ({ action }) => MATRIX_PLUGIN_HANDLED_ACTIONS.has(action), diff --git a/extensions/matrix/src/tool-actions.test.ts b/extensions/matrix/src/tool-actions.test.ts index 10052c5ed79..476fed61cd7 100644 --- a/extensions/matrix/src/tool-actions.test.ts +++ b/extensions/matrix/src/tool-actions.test.ts @@ -281,4 +281,33 @@ describe("handleMatrixAction pollVote", () => { avatarPath: "/tmp/avatar.jpg", }); }); + + it("respects account-scoped action overrides when gating direct tool actions", async () => { + await expect( + handleMatrixAction( + { + action: "sendMessage", + accountId: "ops", + to: "room:!room:example", + content: "hello", + }, + { + channels: { + matrix: { + actions: { + messages: true, + }, + accounts: { + ops: { + actions: { + messages: false, + }, + }, + }, + }, + }, + } as CoreConfig, + ), + ).rejects.toThrow("Matrix messages are disabled."); + }); }); diff --git a/extensions/matrix/src/tool-actions.ts b/extensions/matrix/src/tool-actions.ts index 22478f75236..3e82f4febb8 100644 --- a/extensions/matrix/src/tool-actions.ts +++ b/extensions/matrix/src/tool-actions.ts @@ -7,6 +7,7 @@ import { readStringArrayParam, readStringParam, } from "openclaw/plugin-sdk/matrix"; +import { resolveMatrixAccountConfig } from "./matrix/accounts.js"; import { bootstrapMatrixVerification, acceptMatrixVerification, @@ -131,7 +132,7 @@ export async function handleMatrixAction( ): Promise> { const action = readStringParam(params, "action", { required: true }); const accountId = readStringParam(params, "accountId") ?? undefined; - const isActionEnabled = createActionGate(cfg.channels?.["matrix"]?.actions); + const isActionEnabled = createActionGate(resolveMatrixAccountConfig({ cfg, accountId }).actions); const clientOpts = accountId ? { accountId } : {}; if (reactionActions.has(action)) {