Skip to content

Commit fecc292

Browse files
author
Your Name
committed
Add Teams member-info action gate
1 parent d5eabbd commit fecc292

6 files changed

Lines changed: 101 additions & 29 deletions

File tree

extensions/msteams/src/channel.actions.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,24 @@ describe("msteamsPlugin message actions", () => {
306306
});
307307
});
308308

309+
it("rejects member-info when disabled by the Teams action gate", async () => {
310+
await expectActionError(
311+
{
312+
action: "member-info",
313+
cfg: {
314+
channels: {
315+
msteams: {
316+
actions: { memberInfo: false },
317+
},
318+
},
319+
},
320+
params: { userId: "user-1" },
321+
},
322+
"member-info is disabled via channels.msteams.actions.memberInfo=false.",
323+
);
324+
expect(getMemberInfoMSTeamsMock).not.toHaveBeenCalled();
325+
});
326+
309327
it("routes channel-list through the Teams runtime", async () => {
310328
await expectSuccessfulAction({
311329
mockFn: listChannelsMSTeamsMock,

extensions/msteams/src/channel.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ describe("msteamsPlugin", () => {
3939
);
4040
});
4141

42+
it("hides member-info when disabled by the Teams action gate", () => {
43+
const actions = msteamsPlugin.actions?.describeMessageTool?.({
44+
cfg: {
45+
channels: {
46+
msteams: {
47+
appId: "app-id",
48+
appPassword: "secret",
49+
tenantId: "tenant-id",
50+
actions: { memberInfo: false },
51+
},
52+
},
53+
},
54+
})?.actions;
55+
56+
expect(actions).not.toContain("member-info");
57+
expect(actions).toEqual(expect.arrayContaining(["upload-file", "channel-list"]));
58+
});
59+
4260
it("reuses the shared Teams target-id matcher for explicit targets", () => {
4361
const looksLikeId = msteamsPlugin.messaging?.targetResolver?.looksLikeId;
4462

@@ -67,6 +85,15 @@ describe("msteams config schema", () => {
6785
}
6886
});
6987

88+
it("accepts actions.memberInfo", () => {
89+
const res = MSTeamsConfigSchema.safeParse({ actions: { memberInfo: false } });
90+
91+
expect(res.success).toBe(true);
92+
if (res.success) {
93+
expect(res.data.actions?.memberInfo).toBe(false);
94+
}
95+
});
96+
7097
it("accepts replyStyle at global/team/channel levels", () => {
7198
const res = MSTeamsConfigSchema.safeParse({
7299
replyStyle: "top-level",

extensions/msteams/src/channel.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
22
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
3+
import { createActionGate } from "openclaw/plugin-sdk/channel-actions";
34
import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
45
import type {
56
ChannelMessageActionAdapter,
@@ -163,6 +164,15 @@ function jsonActionResultWithDetails(
163164
}
164165

165166
const MSTEAMS_REACTION_TYPES = ["like", "heart", "laugh", "surprised", "sad", "angry"] as const;
167+
const MSTEAMS_MEMBER_INFO_DISABLED_ERROR =
168+
"member-info is disabled via channels.msteams.actions.memberInfo=false.";
169+
170+
function isMSTeamsMemberInfoActionEnabled(cfg: OpenClawConfig): boolean {
171+
const gate = createActionGate(
172+
cfg.channels?.msteams?.actions as Record<string, boolean | undefined> | undefined,
173+
);
174+
return gate("memberInfo");
175+
}
166176

167177
function actionError(message: string) {
168178
return {
@@ -369,28 +379,28 @@ function describeMSTeamsMessageTool({
369379
const enabled =
370380
cfg.channels?.msteams?.enabled !== false &&
371381
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams));
382+
const actions: ChannelMessageActionName[] = [
383+
"upload-file",
384+
"poll",
385+
"edit",
386+
"delete",
387+
"pin",
388+
"unpin",
389+
"list-pins",
390+
"read",
391+
"react",
392+
"reactions",
393+
"search",
394+
...(isMSTeamsMemberInfoActionEnabled(cfg) ? (["member-info"] as const) : []),
395+
"channel-list",
396+
"channel-info",
397+
"addParticipant",
398+
"removeParticipant",
399+
"renameGroup",
400+
];
401+
372402
return {
373-
actions: enabled
374-
? ([
375-
"upload-file",
376-
"poll",
377-
"edit",
378-
"delete",
379-
"pin",
380-
"unpin",
381-
"list-pins",
382-
"read",
383-
"react",
384-
"reactions",
385-
"search",
386-
"member-info",
387-
"channel-list",
388-
"channel-info",
389-
"addParticipant",
390-
"removeParticipant",
391-
"renameGroup",
392-
] satisfies ChannelMessageActionName[])
393-
: [],
403+
actions: enabled ? actions : [],
394404
capabilities: enabled ? ["presentation"] : [],
395405
schema: enabled
396406
? {
@@ -966,6 +976,9 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount, ProbeMSTeamsRe
966976
}
967977

968978
if (ctx.action === "member-info") {
979+
if (!isMSTeamsMemberInfoActionEnabled(ctx.cfg)) {
980+
return actionError(MSTEAMS_MEMBER_INFO_DISABLED_ERROR);
981+
}
969982
const userId = normalizeOptionalString(ctx.params.userId) ?? "";
970983
if (!userId) {
971984
return actionError("member-info requires a userId.");

0 commit comments

Comments
 (0)