diff --git a/src/auto-reply/reply/queue.collect-routing.test.ts b/src/auto-reply/reply/queue.collect-routing.test.ts index 215cffdae2a..cc2b214bf0d 100644 --- a/src/auto-reply/reply/queue.collect-routing.test.ts +++ b/src/auto-reply/reply/queue.collect-routing.test.ts @@ -9,7 +9,7 @@ function createRun(params: { originatingChannel?: FollowupRun["originatingChannel"]; originatingTo?: string; originatingAccountId?: string; - originatingThreadId?: number; + originatingThreadId?: string | number; }): FollowupRun { return { prompt: params.prompt, @@ -283,4 +283,86 @@ describe("followup queue collect routing", () => { expect(calls[0]?.originatingChannel).toBe("slack"); expect(calls[0]?.originatingTo).toBe("channel:A"); }); + + it("collects Slack messages in same thread and preserves string thread id", async () => { + const key = `test-collect-slack-thread-same-${Date.now()}`; + const calls: FollowupRun[] = []; + const runFollowup = async (run: FollowupRun) => { + calls.push(run); + }; + const settings: QueueSettings = { + mode: "collect", + debounceMs: 0, + cap: 50, + dropPolicy: "summarize", + }; + + enqueueFollowupRun( + key, + createRun({ + prompt: "one", + originatingChannel: "slack", + originatingTo: "channel:A", + originatingThreadId: "1706000000.000001", + }), + settings, + ); + enqueueFollowupRun( + key, + createRun({ + prompt: "two", + originatingChannel: "slack", + originatingTo: "channel:A", + originatingThreadId: "1706000000.000001", + }), + settings, + ); + + scheduleFollowupDrain(key, runFollowup); + await expect.poll(() => calls.length).toBe(1); + expect(calls[0]?.prompt).toContain("[Queued messages while agent was busy]"); + expect(calls[0]?.originatingThreadId).toBe("1706000000.000001"); + }); + + it("does not collect Slack messages when thread ids differ", async () => { + const key = `test-collect-slack-thread-diff-${Date.now()}`; + const calls: FollowupRun[] = []; + const runFollowup = async (run: FollowupRun) => { + calls.push(run); + }; + const settings: QueueSettings = { + mode: "collect", + debounceMs: 0, + cap: 50, + dropPolicy: "summarize", + }; + + enqueueFollowupRun( + key, + createRun({ + prompt: "one", + originatingChannel: "slack", + originatingTo: "channel:A", + originatingThreadId: "1706000000.000001", + }), + settings, + ); + enqueueFollowupRun( + key, + createRun({ + prompt: "two", + originatingChannel: "slack", + originatingTo: "channel:A", + originatingThreadId: "1706000000.000002", + }), + settings, + ); + + scheduleFollowupDrain(key, runFollowup); + await expect.poll(() => calls.length).toBe(2); + expect(calls[0]?.prompt).toBe("one"); + expect(calls[1]?.prompt).toBe("two"); + expect(calls[0]?.originatingThreadId).toBe("1706000000.000001"); + expect(calls[1]?.originatingThreadId).toBe("1706000000.000002"); + }); }); diff --git a/src/auto-reply/reply/queue/drain.ts b/src/auto-reply/reply/queue/drain.ts index 4340650c3cb..626e40af327 100644 --- a/src/auto-reply/reply/queue/drain.ts +++ b/src/auto-reply/reply/queue/drain.ts @@ -44,13 +44,13 @@ export function scheduleFollowupDrain( const to = item.originatingTo; const accountId = item.originatingAccountId; const threadId = item.originatingThreadId; - if (!channel && !to && !accountId && typeof threadId !== "number") { + if (!channel && !to && !accountId && threadId == null) { return {}; } if (!isRoutableChannel(channel) || !to) { return { cross: true }; } - const threadKey = typeof threadId === "number" ? String(threadId) : ""; + const threadKey = threadId != null ? String(threadId) : ""; return { key: [channel, to, accountId || "", threadKey].join("|"), }; @@ -80,7 +80,7 @@ export function scheduleFollowupDrain( (i) => i.originatingAccountId, )?.originatingAccountId; const originatingThreadId = items.find( - (i) => typeof i.originatingThreadId === "number", + (i) => i.originatingThreadId != null, )?.originatingThreadId; const prompt = buildCollectPrompt({