@@ -424,6 +424,7 @@ async function dispatchMessage(params: {
|
424 | 424 | currentCfg?: ClawdbotConfig; |
425 | 425 | event: FeishuMessageEvent; |
426 | 426 | channelRuntime?: PluginRuntime["channel"]; |
| 427 | +botOpenId?: string; |
427 | 428 | }) { |
428 | 429 | const runtime = createRuntimeEnv(); |
429 | 430 | const feishuConfig = params.cfg.channels?.feishu; |
@@ -444,6 +445,7 @@ async function dispatchMessage(params: {
|
444 | 445 | await handleFeishuMessage({ |
445 | 446 | cfg, |
446 | 447 | event: params.event, |
| 448 | +botOpenId: params.botOpenId, |
447 | 449 | runtime, |
448 | 450 | channelRuntime: params.channelRuntime, |
449 | 451 | }); |
@@ -4164,6 +4166,150 @@ describe("handleFeishuMessage command authorization", () => {
|
4164 | 4166 | // No reply should be dispatched: empty message is silently skipped |
4165 | 4167 | expect(mockDispatchReplyFromConfig).not.toHaveBeenCalled(); |
4166 | 4168 | }); |
| 4169 | + |
| 4170 | +it("does not drop empty-text message when it quotes a parent message (#90177)", async () => { |
| 4171 | +// A Feishu reply containing only @bot (no additional text) was being |
| 4172 | +// dropped before the quoted message content was fetched. The handler |
| 4173 | +// should fetch quoted content first and only skip if all of current |
| 4174 | +// text, media, and quoted content are empty. |
| 4175 | +mockShouldComputeCommandAuthorized.mockReturnValue(false); |
| 4176 | +mockGetMessageFeishu.mockResolvedValueOnce({ |
| 4177 | +messageId: "om_quoted_001", |
| 4178 | +chatId: "oc-dm", |
| 4179 | +content: "quoted message content from parent", |
| 4180 | +contentType: "text", |
| 4181 | +}); |
| 4182 | + |
| 4183 | +const cfg: ClawdbotConfig = { |
| 4184 | +channels: { |
| 4185 | +feishu: { |
| 4186 | +dmPolicy: "open", |
| 4187 | +allowFrom: ["*"], |
| 4188 | +}, |
| 4189 | +}, |
| 4190 | +} as ClawdbotConfig; |
| 4191 | + |
| 4192 | +const event: FeishuMessageEvent = { |
| 4193 | +sender: { |
| 4194 | +sender_id: { |
| 4195 | +open_id: "ou-reply-only-bot", |
| 4196 | +}, |
| 4197 | +}, |
| 4198 | +message: { |
| 4199 | +message_id: "msg-empty-with-quote", |
| 4200 | +parent_id: "om_quoted_001", |
| 4201 | +chat_id: "oc-dm", |
| 4202 | +chat_type: "p2p", |
| 4203 | +message_type: "text", |
| 4204 | +// Empty text — only @bot mention, no additional content |
| 4205 | +content: JSON.stringify({ text: "" }), |
| 4206 | +}, |
| 4207 | +}; |
| 4208 | + |
| 4209 | +await dispatchMessage({ cfg, event }); |
| 4210 | + |
| 4211 | +// A reply should be dispatched because quoted content provides context |
| 4212 | +expect(mockDispatchReplyFromConfig).toHaveBeenCalledTimes(1); |
| 4213 | +}); |
| 4214 | + |
| 4215 | +it("dispatches mention-only group reply with quoted content in requireMention:true group (#90177)", async () => { |
| 4216 | +// #90177 is specifically about group chats. The empty-message drop happens |
| 4217 | +// after the group admission/mention gate, so the fix must also work when |
| 4218 | +// the sender mentions the bot in a requireMention:true group and quotes a |
| 4219 | +// parent message with meaningful content — the reply should dispatch with |
| 4220 | +// the quoted text in the body. |
| 4221 | +mockShouldComputeCommandAuthorized.mockReturnValue(false); |
| 4222 | +mockGetMessageFeishu.mockResolvedValueOnce({ |
| 4223 | +messageId: "om_group_quoted_001", |
| 4224 | +chatId: "oc-group-90177", |
| 4225 | +content: "parent message with context", |
| 4226 | +contentType: "text", |
| 4227 | +}); |
| 4228 | + |
| 4229 | +const cfg: ClawdbotConfig = { |
| 4230 | +channels: { |
| 4231 | +feishu: { |
| 4232 | +groupPolicy: "open", |
| 4233 | +groups: { |
| 4234 | +"oc-group-90177": { |
| 4235 | +requireMention: true, |
| 4236 | +}, |
| 4237 | +}, |
| 4238 | +}, |
| 4239 | +}, |
| 4240 | +} as ClawdbotConfig; |
| 4241 | + |
| 4242 | +const event: FeishuMessageEvent = { |
| 4243 | +sender: { |
| 4244 | +sender_id: { |
| 4245 | +open_id: "ou-group-sender", |
| 4246 | +}, |
| 4247 | +}, |
| 4248 | +message: { |
| 4249 | +message_id: "msg-group-empty-with-quote", |
| 4250 | +parent_id: "om_group_quoted_001", |
| 4251 | +chat_id: "oc-group-90177", |
| 4252 | +chat_type: "group", |
| 4253 | +message_type: "text", |
| 4254 | +// Empty text — only @bot mention, no additional content |
| 4255 | +content: JSON.stringify({ text: "" }), |
| 4256 | +// Bot mention so the message passes the requireMention gate |
| 4257 | +mentions: [ |
| 4258 | +{ key: "@_bot_1", id: { open_id: "ou-bot-90177" }, name: "Bot", tenant_key: "" }, |
| 4259 | +], |
| 4260 | +}, |
| 4261 | +}; |
| 4262 | + |
| 4263 | +await dispatchMessage({ cfg, event, botOpenId: "ou-bot-90177" }); |
| 4264 | + |
| 4265 | +expect(mockDispatchReplyFromConfig).toHaveBeenCalledTimes(1); |
| 4266 | +const context = mockCallArg<{ Body?: string }>(mockFinalizeInboundContext, 0, 0); |
| 4267 | +expect(context.Body).toContain("[Replying to:"); |
| 4268 | +expect(context.Body).toContain("parent message with context"); |
| 4269 | +}); |
| 4270 | + |
| 4271 | +it("does not over-fetch quoted message for unmentioned empty reply in requireMention:true group (#90177)", async () => { |
| 4272 | +// An empty-text reply that quotes a parent but does NOT mention the bot |
| 4273 | +// in a requireMention:true group should be rejected at the mention gate |
| 4274 | +// before the quoted message is fetched, so getMessageFeishu is never |
| 4275 | +// called and nothing is dispatched. |
| 4276 | +mockShouldComputeCommandAuthorized.mockReturnValue(false); |
| 4277 | + |
| 4278 | +const cfg: ClawdbotConfig = { |
| 4279 | +channels: { |
| 4280 | +feishu: { |
| 4281 | +groupPolicy: "open", |
| 4282 | +groups: { |
| 4283 | +"oc-group-90177-neg": { |
| 4284 | +requireMention: true, |
| 4285 | +}, |
| 4286 | +}, |
| 4287 | +}, |
| 4288 | +}, |
| 4289 | +} as ClawdbotConfig; |
| 4290 | + |
| 4291 | +const event: FeishuMessageEvent = { |
| 4292 | +sender: { |
| 4293 | +sender_id: { |
| 4294 | +open_id: "ou-group-sender-neg", |
| 4295 | +}, |
| 4296 | +}, |
| 4297 | +message: { |
| 4298 | +message_id: "msg-group-unmentioned-empty-quote", |
| 4299 | +parent_id: "om_group_quoted_neg", |
| 4300 | +chat_id: "oc-group-90177-neg", |
| 4301 | +chat_type: "group", |
| 4302 | +message_type: "text", |
| 4303 | +// Empty text with no bot mention |
| 4304 | +content: JSON.stringify({ text: "" }), |
| 4305 | +}, |
| 4306 | +}; |
| 4307 | + |
| 4308 | +await dispatchMessage({ cfg, event, botOpenId: "ou-bot-90177-neg" }); |
| 4309 | + |
| 4310 | +expect(mockGetMessageFeishu).not.toHaveBeenCalled(); |
| 4311 | +expect(mockDispatchReplyFromConfig).not.toHaveBeenCalled(); |
| 4312 | +}); |
4167 | 4313 | }); |
4168 | 4314 | |
4169 | 4315 | describe("createFeishuMessageReceiveHandler media dedupe", () => { |
|