Matrix: honor action gates per resolved account

This commit is contained in:
Gustavo Madeira Santana 2026-03-09 05:05:22 -04:00
parent 65b28384d1
commit b4b9625914
No known key found for this signature in database
4 changed files with 115 additions and 33 deletions

View File

@ -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"]);
});
});

View File

@ -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<ChannelMessageActionName>([
"permissions",
]);
function createMatrixExposedActions() {
return new Set<ChannelMessageActionName>(["poll", ...MATRIX_PLUGIN_HANDLED_ACTIONS]);
function createMatrixExposedActions(params: {
gate: ReturnType<typeof createActionGate>;
encryptionEnabled: boolean;
}) {
const actions = new Set<ChannelMessageActionName>(["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),

View File

@ -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.");
});
});

View File

@ -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<AgentToolResult<unknown>> {
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)) {