diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index c96094a296f..0a7214f9410 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -1074,6 +1074,103 @@ describe("handleFeishuMessage command authorization", () => { expect(mockDispatchReplyFromConfig).not.toHaveBeenCalled(); }); + it("dispatches group image message when groupPolicy is open (requireMention defaults to false)", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + groupPolicy: "open", + // requireMention is NOT set — should default to false for open policy + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { + sender_id: { open_id: "ou-sender" }, + }, + message: { + message_id: "msg-group-image-open", + chat_id: "oc-group-open", + chat_type: "group", + message_type: "image", + content: JSON.stringify({ image_key: "img_v3_test" }), + }, + }; + + await dispatchMessage({ cfg, event }); + + expect(mockDispatchReplyFromConfig).toHaveBeenCalledTimes(1); + }); + + it("drops group image message when groupPolicy is open but requireMention is explicitly true", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + groupPolicy: "open", + requireMention: true, // explicit override — user opts into mention-required even for open + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { + sender_id: { open_id: "ou-sender" }, + }, + message: { + message_id: "msg-group-image-open-explicit-mention", + chat_id: "oc-group-open", + chat_type: "group", + message_type: "image", + content: JSON.stringify({ image_key: "img_v3_test" }), + }, + }; + + await dispatchMessage({ cfg, event }); + + expect(mockFinalizeInboundContext).not.toHaveBeenCalled(); + expect(mockDispatchReplyFromConfig).not.toHaveBeenCalled(); + }); + + it("drops group image message when groupPolicy is allowlist and requireMention is not set (defaults to true)", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + groupPolicy: "allowlist", + // requireMention not set — for non-open policy defaults to true + groups: { + "oc-allowlist-group": { + allow: true, + }, + }, + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { + sender_id: { open_id: "ou-sender" }, + }, + message: { + message_id: "msg-group-image-allowlist", + chat_id: "oc-allowlist-group", + chat_type: "group", + message_type: "image", + content: JSON.stringify({ image_key: "img_v3_test" }), + }, + }; + + await dispatchMessage({ cfg, event }); + + expect(mockFinalizeInboundContext).not.toHaveBeenCalled(); + expect(mockDispatchReplyFromConfig).not.toHaveBeenCalled(); + }); + it("drops message when groupConfig.enabled is false", async () => { const cfg: ClawdbotConfig = { channels: { diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 9c73ee4b6e5..32bd6269e56 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -416,6 +416,7 @@ export async function handleFeishuMessage(params: { isDirectMessage: false, globalConfig: feishuCfg, groupConfig, + groupPolicy, })); if (requireMention && !ctx.mentionedBot) { diff --git a/extensions/feishu/src/config-schema.ts b/extensions/feishu/src/config-schema.ts index db1714f173f..f388f3b6cc7 100644 --- a/extensions/feishu/src/config-schema.ts +++ b/extensions/feishu/src/config-schema.ts @@ -221,7 +221,7 @@ export const FeishuConfigSchema = z dmPolicy: DmPolicySchema.optional().default("pairing"), reactionNotifications: ReactionNotificationModeSchema.optional().default("own"), groupPolicy: GroupPolicySchema.optional().default("allowlist"), - requireMention: z.boolean().optional().default(true), + requireMention: z.boolean().optional(), groupSessionScope: GroupSessionScopeSchema, topicSessionMode: TopicSessionModeSchema, // Dynamic agent creation for DM users diff --git a/extensions/feishu/src/policy.ts b/extensions/feishu/src/policy.ts index faee6675127..14df2916ca1 100644 --- a/extensions/feishu/src/policy.ts +++ b/extensions/feishu/src/policy.ts @@ -107,13 +107,25 @@ export function resolveFeishuReplyPolicy(params: { isDirectMessage: boolean; globalConfig?: FeishuConfig; groupConfig?: FeishuGroupConfig; + /** + * Effective group policy resolved for this chat. When "open", requireMention + * defaults to false so that non-text messages (e.g. images) that cannot carry + * @-mentions are still delivered to the agent. + */ + groupPolicy?: string; }): { requireMention: boolean } { if (params.isDirectMessage) { return { requireMention: false }; } + // When groupPolicy is "open" and requireMention is not explicitly configured, + // default to false: an open group should respond to all messages including + // images and files that cannot carry @-mentions. + const requireMentionDefault = params.groupPolicy === "open" ? false : true; const requireMention = - params.groupConfig?.requireMention ?? params.globalConfig?.requireMention ?? true; + params.groupConfig?.requireMention ?? + params.globalConfig?.requireMention ?? + requireMentionDefault; return { requireMention }; }