diff --git a/extensions/feishu/src/reply-dispatcher.test.ts b/extensions/feishu/src/reply-dispatcher.test.ts index d4527cc26..412bff70c 100644 --- a/extensions/feishu/src/reply-dispatcher.test.ts +++ b/extensions/feishu/src/reply-dispatcher.test.ts @@ -185,6 +185,23 @@ describe("createFeishuReplyDispatcher streaming behavior", () => { expect(sendMarkdownCardFeishuMock).not.toHaveBeenCalled(); }); + it("suppresses internal block payload delivery", async () => { + createFeishuReplyDispatcher({ + cfg: {} as never, + agentId: "agent", + runtime: {} as never, + chatId: "oc_chat", + }); + + const options = createReplyDispatcherWithTypingMock.mock.calls[0]?.[0]; + await options.deliver({ text: "internal reasoning chunk" }, { kind: "block" }); + + expect(streamingInstances).toHaveLength(0); + expect(sendMessageFeishuMock).not.toHaveBeenCalled(); + expect(sendMarkdownCardFeishuMock).not.toHaveBeenCalled(); + expect(sendMediaFeishuMock).not.toHaveBeenCalled(); + }); + it("uses streaming session for auto mode markdown payloads", async () => { createFeishuReplyDispatcher({ cfg: {} as never, diff --git a/extensions/feishu/src/reply-dispatcher.ts b/extensions/feishu/src/reply-dispatcher.ts index 35440396c..96338c343 100644 --- a/extensions/feishu/src/reply-dispatcher.ts +++ b/extensions/feishu/src/reply-dispatcher.ts @@ -192,6 +192,12 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP void typingCallbacks.onReplyStart?.(); }, deliver: async (payload: ReplyPayload, info) => { + // FIX: Filter out internal 'block' reasoning chunks immediately to prevent + // data leak and race conditions with streaming state initialization. + if (info?.kind === "block") { + return; + } + const text = payload.text ?? ""; const mediaList = payload.mediaUrls && payload.mediaUrls.length > 0 @@ -209,7 +215,7 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP if (hasText) { const useCard = renderMode === "card" || (renderMode === "auto" && shouldUseCard(text)); - if ((info?.kind === "block" || info?.kind === "final") && streamingEnabled && useCard) { + if (info?.kind === "final" && streamingEnabled && useCard) { startStreaming(); if (streamingStartPromise) { await streamingStartPromise;