From 80daf16550a2f9fe402e2cc880e480cd114bb2df Mon Sep 17 00:00:00 2001 From: Strider Date: Mon, 9 Mar 2026 11:32:29 +0800 Subject: [PATCH 1/5] feat(feishu): add requireMentionInThread to allow thread replies without @mention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When requireMention is true for a group, thread replies still require an explicit @mention — even though thread context makes the intent unambiguous. This adds a requireMentionInThread config option at both the global and per-group level, letting users opt out of the mention requirement for thread replies while keeping it for top-level group messages. Closes #40475 Co-Authored-By: Claude Opus 4.6 --- extensions/feishu/src/bot.ts | 1 + extensions/feishu/src/config-schema.ts | 2 + extensions/feishu/src/policy.test.ts | 76 +++++++++++++++++++++++++- extensions/feishu/src/policy.ts | 9 +++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 63b898a23fb..a3b125ceecd 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -414,6 +414,7 @@ export async function handleFeishuMessage(params: { ({ requireMention } = resolveFeishuReplyPolicy({ isDirectMessage: false, + isThreadReply: groupSession?.threadReply, globalConfig: feishuCfg, groupConfig, })); diff --git a/extensions/feishu/src/config-schema.ts b/extensions/feishu/src/config-schema.ts index db1714f173f..38b7255626d 100644 --- a/extensions/feishu/src/config-schema.ts +++ b/extensions/feishu/src/config-schema.ts @@ -141,6 +141,7 @@ const ReplyInThreadSchema = z.enum(["disabled", "enabled"]).optional(); export const FeishuGroupSchema = z .object({ requireMention: z.boolean().optional(), + requireMentionInThread: z.boolean().optional(), tools: ToolPolicySchema, skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), @@ -164,6 +165,7 @@ const FeishuSharedConfigShape = { groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupSenderAllowFrom: z.array(z.union([z.string(), z.number()])).optional(), requireMention: z.boolean().optional(), + requireMentionInThread: z.boolean().optional(), groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(), historyLimit: z.number().int().min(0).optional(), dmHistoryLimit: z.number().int().min(0).optional(), diff --git a/extensions/feishu/src/policy.test.ts b/extensions/feishu/src/policy.test.ts index c53532df3ff..ec5f4430ebe 100644 --- a/extensions/feishu/src/policy.test.ts +++ b/extensions/feishu/src/policy.test.ts @@ -3,8 +3,9 @@ import { isFeishuGroupAllowed, resolveFeishuAllowlistMatch, resolveFeishuGroupConfig, + resolveFeishuReplyPolicy, } from "./policy.js"; -import type { FeishuConfig } from "./types.js"; +import type { FeishuConfig, FeishuGroupConfig } from "./types.js"; describe("feishu policy", () => { describe("resolveFeishuGroupConfig", () => { @@ -100,6 +101,79 @@ describe("feishu policy", () => { }); }); + describe("resolveFeishuReplyPolicy", () => { + it("does not require mention for DMs", () => { + expect(resolveFeishuReplyPolicy({ isDirectMessage: true })).toEqual({ + requireMention: false, + }); + }); + + it("requires mention in group by default", () => { + expect(resolveFeishuReplyPolicy({ isDirectMessage: false })).toEqual({ + requireMention: true, + }); + }); + + it("respects group-level requireMention override", () => { + expect( + resolveFeishuReplyPolicy({ + isDirectMessage: false, + groupConfig: { requireMention: false } as FeishuGroupConfig, + }), + ).toEqual({ requireMention: false }); + }); + + it("still requires mention for thread replies when no thread override is set", () => { + expect( + resolveFeishuReplyPolicy({ + isDirectMessage: false, + isThreadReply: true, + }), + ).toEqual({ requireMention: true }); + }); + + it("skips mention for thread replies when requireMentionInThread is false (global)", () => { + expect( + resolveFeishuReplyPolicy({ + isDirectMessage: false, + isThreadReply: true, + globalConfig: { requireMentionInThread: false } as FeishuConfig, + }), + ).toEqual({ requireMention: false }); + }); + + it("skips mention for thread replies when requireMentionInThread is false (group)", () => { + expect( + resolveFeishuReplyPolicy({ + isDirectMessage: false, + isThreadReply: true, + groupConfig: { requireMentionInThread: false } as FeishuGroupConfig, + }), + ).toEqual({ requireMention: false }); + }); + + it("group-level requireMentionInThread overrides global", () => { + expect( + resolveFeishuReplyPolicy({ + isDirectMessage: false, + isThreadReply: true, + globalConfig: { requireMentionInThread: false } as FeishuConfig, + groupConfig: { requireMentionInThread: true } as FeishuGroupConfig, + }), + ).toEqual({ requireMention: true }); + }); + + it("does not apply thread override for non-thread messages", () => { + expect( + resolveFeishuReplyPolicy({ + isDirectMessage: false, + isThreadReply: false, + globalConfig: { requireMentionInThread: false } as FeishuConfig, + }), + ).toEqual({ requireMention: true }); + }); + }); + describe("isFeishuGroupAllowed", () => { it("matches group IDs with chat: prefix", () => { expect( diff --git a/extensions/feishu/src/policy.ts b/extensions/feishu/src/policy.ts index faee6675127..7a08dfa1877 100644 --- a/extensions/feishu/src/policy.ts +++ b/extensions/feishu/src/policy.ts @@ -105,6 +105,7 @@ export function isFeishuGroupAllowed(params: { export function resolveFeishuReplyPolicy(params: { isDirectMessage: boolean; + isThreadReply?: boolean; globalConfig?: FeishuConfig; groupConfig?: FeishuGroupConfig; }): { requireMention: boolean } { @@ -115,5 +116,13 @@ export function resolveFeishuReplyPolicy(params: { const requireMention = params.groupConfig?.requireMention ?? params.globalConfig?.requireMention ?? true; + if (requireMention && params.isThreadReply) { + const threadOverride = + params.groupConfig?.requireMentionInThread ?? params.globalConfig?.requireMentionInThread; + if (threadOverride !== undefined) { + return { requireMention: threadOverride }; + } + } + return { requireMention }; } From 85a1b9d7101b5bfe79779f67ba9d5837ce50f1ae Mon Sep 17 00:00:00 2001 From: Strider Date: Mon, 9 Mar 2026 11:40:38 +0800 Subject: [PATCH 2/5] fix: let requireMentionInThread both relax and tighten the base setting Remove the `requireMention &&` guard so requireMentionInThread works bidirectionally: a group with requireMention: false can now set requireMentionInThread: true to re-enable the mention requirement for thread replies specifically. Add test for the tightening edge case. Co-Authored-By: Claude Opus 4.6 --- extensions/feishu/src/policy.test.ts | 11 +++++++++++ extensions/feishu/src/policy.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/extensions/feishu/src/policy.test.ts b/extensions/feishu/src/policy.test.ts index ec5f4430ebe..01530e12cba 100644 --- a/extensions/feishu/src/policy.test.ts +++ b/extensions/feishu/src/policy.test.ts @@ -163,6 +163,17 @@ describe("feishu policy", () => { ).toEqual({ requireMention: true }); }); + it("requireMentionInThread tightens when base requireMention is false", () => { + expect( + resolveFeishuReplyPolicy({ + isDirectMessage: false, + isThreadReply: true, + globalConfig: { requireMention: false } as FeishuConfig, + groupConfig: { requireMentionInThread: true } as FeishuGroupConfig, + }), + ).toEqual({ requireMention: true }); + }); + it("does not apply thread override for non-thread messages", () => { expect( resolveFeishuReplyPolicy({ diff --git a/extensions/feishu/src/policy.ts b/extensions/feishu/src/policy.ts index 7a08dfa1877..1db6e6618c6 100644 --- a/extensions/feishu/src/policy.ts +++ b/extensions/feishu/src/policy.ts @@ -116,7 +116,7 @@ export function resolveFeishuReplyPolicy(params: { const requireMention = params.groupConfig?.requireMention ?? params.globalConfig?.requireMention ?? true; - if (requireMention && params.isThreadReply) { + if (params.isThreadReply) { const threadOverride = params.groupConfig?.requireMentionInThread ?? params.globalConfig?.requireMentionInThread; if (threadOverride !== undefined) { From 1d0c37e82023ac860e15d5ad0e5d780c579b6c37 Mon Sep 17 00:00:00 2001 From: Strider Date: Thu, 12 Mar 2026 07:05:28 +0800 Subject: [PATCH 3/5] =?UTF-8?q?ci:=20retrigger=20=E2=80=94=20flaky=20windo?= =?UTF-8?q?ws=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 From 33da852ff1cae3adec505f0b7ea50af795415a72 Mon Sep 17 00:00:00 2001 From: Strider Date: Fri, 20 Mar 2026 07:53:31 +0800 Subject: [PATCH 4/5] fix: restrict requireMentionInThread to topic-thread groups only Plain quote-replies in normal groups also set threadReply=true (via root_id), which would incorrectly bypass the mention gate. Now only apply the thread override when the group is in topic-thread mode (group_topic or group_topic_sender). Co-Authored-By: Claude Opus 4.6 --- extensions/feishu/src/bot.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index a3b125ceecd..5eb85cba742 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -414,7 +414,10 @@ export async function handleFeishuMessage(params: { ({ requireMention } = resolveFeishuReplyPolicy({ isDirectMessage: false, - isThreadReply: groupSession?.threadReply, + isThreadReply: + groupSession?.threadReply && + (groupSession?.groupSessionScope === "group_topic" || + groupSession?.groupSessionScope === "group_topic_sender"), globalConfig: feishuCfg, groupConfig, })); From cc4afb26869e81429f9fc43ebcddb58cefb2f3b4 Mon Sep 17 00:00:00 2001 From: Strider Date: Fri, 20 Mar 2026 21:02:49 +0800 Subject: [PATCH 5/5] fix: also honor requireMentionInThread for replyInThread-enabled groups Groups with replyInThread: "enabled" create ad-hoc threads even with default groupSessionScope: "group". Include these in the isThreadReply check so requireMentionInThread works for them too. Co-Authored-By: Claude Opus 4.6 --- extensions/feishu/src/bot.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 5eb85cba742..0cfda65ea47 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -417,7 +417,8 @@ export async function handleFeishuMessage(params: { isThreadReply: groupSession?.threadReply && (groupSession?.groupSessionScope === "group_topic" || - groupSession?.groupSessionScope === "group_topic_sender"), + groupSession?.groupSessionScope === "group_topic_sender" || + groupSession?.replyInThread), globalConfig: feishuCfg, groupConfig, }));