fix(mattermost): land #30891 route private channels as group (@BlueBirdBack)

Landed from contributor PR #30891 by @BlueBirdBack.

Co-authored-by: BlueBirdBack <BlueBirdBack@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-03-02 03:14:17 +00:00
parent 6bea38b21f
commit 355b4c62bc
3 changed files with 31 additions and 3 deletions

View File

@@ -112,6 +112,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Mattermost/Private channel policy routing: map Mattermost private channel type `P` to group chat type so `groupPolicy`/`groupAllowFrom` gates apply correctly instead of being treated as open public channels. Landed from contributor PR #30891 by @BlueBirdBack. Thanks @BlueBirdBack.
- Models/Custom provider keys: trim custom provider map keys during normalization so image-capable models remain discoverable when provider keys are configured with leading/trailing whitespace. Landed from contributor PR #31202 by @stakeswky. Thanks @stakeswky.
- Discord/Agent component interactions: accept Components v2 `cid` payloads alongside legacy `componentId`, and safely decode percent-encoded IDs without throwing on malformed `%` sequences. Landed from contributor PR #29013 by @Jacky1n7. Thanks @Jacky1n7.
- Matrix/Directory room IDs: preserve original room-ID casing for direct `!roomId` group lookups (without `:server`) so allowlist checks do not fail on case-sensitive IDs. Landed from contributor PR #31201 by @williamos-dev. Thanks @williamos-dev.

View File

@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";
import { mapMattermostChannelTypeToChatType } from "./monitor.js";
describe("mapMattermostChannelTypeToChatType", () => {
it("maps direct and group dm channel types", () => {
expect(mapMattermostChannelTypeToChatType("D")).toBe("direct");
expect(mapMattermostChannelTypeToChatType("g")).toBe("group");
});
it("maps private channels to group", () => {
expect(mapMattermostChannelTypeToChatType("P")).toBe("group");
expect(mapMattermostChannelTypeToChatType(" p ")).toBe("group");
});
it("keeps public channels and unknown values as channel", () => {
expect(mapMattermostChannelTypeToChatType("O")).toBe("channel");
expect(mapMattermostChannelTypeToChatType("x")).toBe("channel");
expect(mapMattermostChannelTypeToChatType(undefined)).toBe("channel");
});
});

View File

@@ -110,10 +110,11 @@ function isSystemPost(post: MattermostPost): boolean {
return Boolean(type);
}
function channelKind(channelType?: string | null): ChatType {
export function mapMattermostChannelTypeToChatType(channelType?: string | null): ChatType {
if (!channelType) {
return "channel";
}
// Mattermost channel types: D=direct, G=group DM, O=public channel, P=private channel.
const normalized = channelType.trim().toUpperCase();
if (normalized === "D") {
return "direct";
@@ -121,6 +122,12 @@ function channelKind(channelType?: string | null): ChatType {
if (normalized === "G") {
return "group";
}
if (normalized === "P") {
// Private channels are invitation-restricted spaces; route as "group" so
// groupPolicy / groupAllowFrom can gate access separately from open public
// channels (type "O"), and the From prefix becomes mattermost:group:<id>.
return "group";
}
return "channel";
}
@@ -352,7 +359,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
const channelInfo = await resolveChannelInfo(channelId);
const channelType = payload.data?.channel_type ?? channelInfo?.type ?? undefined;
const kind = channelKind(channelType);
const kind = mapMattermostChannelTypeToChatType(channelType);
const chatType = channelChatType(kind);
const senderName =
@@ -863,7 +870,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
logVerboseMessage(`mattermost: drop reaction (cannot resolve channel type for ${channelId})`);
return;
}
const kind = channelKind(channelInfo.type);
const kind = mapMattermostChannelTypeToChatType(channelInfo.type);
// Enforce DM/group policy and allowlist checks (same as normal messages)
const dmPolicy = account.config.dmPolicy ?? "pairing";