diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index c0da2f757eb..ac9634568db 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -447,6 +447,38 @@ describe("createFollowupRunner messaging tool dedupe", () => { expect(onBlockReply).not.toHaveBeenCalled(); }); + it("does not reuse session-level dedupe fingerprints for queued user turns", async () => { + const onBlockReply = vi.fn(async () => {}); + const sessionEntry: SessionEntry = { + sessionId: "session", + updatedAt: Date.now(), + lastMessagingToolSessionId: "session", + lastMessagingToolSentAt: Date.now(), + lastMessagingToolSentTexts: ["hello world!"], + lastMessagingToolSentTargets: [{ tool: "message", provider: "telegram", to: "123" }], + }; + const sessionStore: Record = { main: sessionEntry }; + + runEmbeddedPiAgentMock.mockResolvedValueOnce({ + payloads: [{ text: "hello world!" }], + meta: {}, + }); + + const runner = createMessagingDedupeRunner(onBlockReply, { + sessionEntry, + sessionStore, + sessionKey: "main", + }); + + await runner({ + ...baseQueuedRun("telegram"), + messageId: "user-msg-123", + originatingTo: "123", + }); + + expect(onBlockReply).toHaveBeenCalledTimes(1); + }); + it("does not use session-level dedupe from a previous session id", async () => { const onBlockReply = vi.fn(async () => {}); const sessionEntry: SessionEntry = { diff --git a/src/auto-reply/reply/followup-runner.ts b/src/auto-reply/reply/followup-runner.ts index 7eceda2628f..234161d2498 100644 --- a/src/auto-reply/reply/followup-runner.ts +++ b/src/auto-reply/reply/followup-runner.ts @@ -353,8 +353,9 @@ export function createFollowupRunner(params: { }), ), ); + const isSystemGeneratedFollowup = !queued.messageId; const canReuseSessionDedupeFingerprints = - recentTargetMatch && uniquePreviousTargets.size <= 1; + isSystemGeneratedFollowup && recentTargetMatch && uniquePreviousTargets.size <= 1; const sentTexts = [ ...(runResult.messagingToolSentTexts ?? []),