From 99cfa50451cc7bb8d4f03e06eb50b666d19e3830 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 24 Apr 2026 23:42:42 +0100 Subject: [PATCH] test(slack): cover first native stream thread target --- .../dispatch.preview-fallback.test.ts | 43 ++++++++++++++++--- .../dispatch.streaming.test.ts | 11 +++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts b/extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts index 7f3f8342297..e75951452ed 100644 --- a/extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts +++ b/extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts @@ -30,6 +30,7 @@ class TestSlackStreamNotDeliveredError extends Error { let mockedNativeStreaming = false; let mockedBlockStreamingEnabled: boolean | undefined = false; let capturedReplyOptions: { disableBlockStreaming?: boolean } | undefined; +let mockedReplyThreadTs: string | undefined = THREAD_TS; let mockedDispatchSequence: Array<{ kind: "tool" | "block" | "final"; payload: { text: string; isError?: boolean; mediaUrl?: string; mediaUrls?: string[] }; @@ -52,7 +53,15 @@ function createDraftStreamStub() { }; } -function createPreparedSlackMessage() { +function createPreparedSlackMessage(params?: { + message?: Partial<{ + channel: string; + ts: string; + thread_ts?: string; + user: string; + }>; + replyToMode?: "off" | "first" | "all" | "batched"; +}) { return { ctx: { cfg: {}, @@ -77,6 +86,7 @@ function createPreparedSlackMessage() { ts: "171234.111", thread_ts: THREAD_TS, user: "U123", + ...params?.message, }, route: { agentId: "agent-1", @@ -88,7 +98,7 @@ function createPreparedSlackMessage() { ctxPayload: { MessageThreadId: THREAD_TS, }, - replyToMode: "all", + replyToMode: params?.replyToMode ?? "all", isDirectMessage: false, isRoomish: false, historyKey: "history-key", @@ -248,13 +258,13 @@ vi.mock("../config.runtime.js", () => ({ vi.mock("../replies.js", () => ({ createSlackReplyDeliveryPlan: () => ({ - peekThreadTs: () => THREAD_TS, - nextThreadTs: () => THREAD_TS, + peekThreadTs: () => mockedReplyThreadTs, + nextThreadTs: () => mockedReplyThreadTs, markSent: () => {}, }), deliverReplies: deliverRepliesMock, readSlackReplyBlocks: () => undefined, - resolveSlackThreadTs: () => THREAD_TS, + resolveSlackThreadTs: () => mockedReplyThreadTs, })); vi.mock("../reply.runtime.js", () => ({ @@ -311,6 +321,7 @@ describe("dispatchPreparedSlackMessage preview fallback", () => { mockedNativeStreaming = false; mockedBlockStreamingEnabled = false; capturedReplyOptions = undefined; + mockedReplyThreadTs = THREAD_TS; mockedDispatchSequence = [{ kind: "final", payload: { text: FINAL_REPLY_TEXT } }]; createSlackDraftStreamMock.mockReturnValue(createDraftStreamStub()); @@ -347,6 +358,28 @@ describe("dispatchPreparedSlackMessage preview fallback", () => { expect(capturedReplyOptions?.disableBlockStreaming).toBe(true); }); + it("starts native streams in the first-reply thread for top-level channel messages", async () => { + mockedNativeStreaming = true; + mockedReplyThreadTs = "171234.111"; + mockedDispatchSequence = [{ kind: "final", payload: { text: FINAL_REPLY_TEXT } }]; + + await dispatchPreparedSlackMessage( + createPreparedSlackMessage({ + message: { thread_ts: undefined }, + replyToMode: "all", + }), + ); + + expect(startSlackStreamMock).toHaveBeenCalledWith( + expect.objectContaining({ + channel: "C123", + threadTs: "171234.111", + text: FINAL_REPLY_TEXT, + }), + ); + expect(deliverRepliesMock).not.toHaveBeenCalled(); + }); + it("keeps same-content tool and final payloads distinct after preview fallback", async () => { mockedDispatchSequence = [ { kind: "tool", payload: { text: SAME_TEXT } }, diff --git a/extensions/slack/src/monitor/message-handler/dispatch.streaming.test.ts b/extensions/slack/src/monitor/message-handler/dispatch.streaming.test.ts index c7d8ddfb078..8b66a680f27 100644 --- a/extensions/slack/src/monitor/message-handler/dispatch.streaming.test.ts +++ b/extensions/slack/src/monitor/message-handler/dispatch.streaming.test.ts @@ -156,6 +156,17 @@ describe("slack native streaming thread hint", () => { ).toBe("1000.2"); }); + it("uses the message timestamp for top-level channel replies when replyToMode=all", () => { + expect( + resolveSlackStreamingThreadHint({ + replyToMode: "all", + incomingThreadTs: undefined, + messageTs: "1000.4", + isThreadReply: false, + }), + ).toBe("1000.4"); + }); + it("uses the existing incoming thread regardless of replyToMode", () => { expect( resolveSlackStreamingThreadHint({