diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index c80704f4cb9..36f0a173fa7 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -294,6 +294,11 @@ export async function runReplyAgent(params: { fallbackNoticeSelectedModel: undefined, fallbackNoticeActiveModel: undefined, fallbackNoticeReason: undefined, + lastMessagingToolSessionId: undefined, + lastMessagingToolSentAt: undefined, + lastMessagingToolSentTexts: undefined, + lastMessagingToolSentMediaUrls: undefined, + lastMessagingToolSentTargets: undefined, }; const agentId = resolveAgentIdFromSessionKey(sessionKey); const nextSessionFile = resolveSessionTranscriptPath( @@ -423,6 +428,7 @@ export async function runReplyAgent(params: { if (sessionDedupeEntry) { const now = Date.now(); if (sentTexts.length || sentMediaUrls.length || sentTargets.length) { + sessionDedupeEntry.lastMessagingToolSessionId = followupRun.run.sessionId; sessionDedupeEntry.lastMessagingToolSentAt = now; sessionDedupeEntry.lastMessagingToolSentTexts = sentTexts; sessionDedupeEntry.lastMessagingToolSentMediaUrls = sentMediaUrls; @@ -431,6 +437,7 @@ export async function runReplyAgent(params: { typeof sessionDedupeEntry.lastMessagingToolSentAt === "number" && now - sessionDedupeEntry.lastMessagingToolSentAt > RECENT_MESSAGING_TOOL_DEDUPE_WINDOW_MS ) { + delete sessionDedupeEntry.lastMessagingToolSessionId; delete sessionDedupeEntry.lastMessagingToolSentAt; delete sessionDedupeEntry.lastMessagingToolSentTexts; delete sessionDedupeEntry.lastMessagingToolSentMediaUrls; diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index add48e3a129..db4a086923c 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -421,6 +421,7 @@ describe("createFollowupRunner messaging tool dedupe", () => { const sessionEntry: SessionEntry = { sessionId: "session", updatedAt: Date.now(), + lastMessagingToolSessionId: "session", lastMessagingToolSentAt: Date.now(), lastMessagingToolSentTexts: ["hello world!"], lastMessagingToolSentTargets: [{ tool: "message", provider: "telegram", to: "123" }], @@ -446,6 +447,38 @@ describe("createFollowupRunner messaging tool dedupe", () => { expect(onBlockReply).not.toHaveBeenCalled(); }); + it("does not use session-level dedupe from a previous session id", async () => { + const onBlockReply = vi.fn(async () => {}); + const sessionEntry: SessionEntry = { + sessionId: "current-session", + updatedAt: Date.now(), + lastMessagingToolSessionId: "old-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"), + originatingTo: "123", + run: { ...baseQueuedRun("telegram").run, sessionId: "current-session" }, + }); + + expect(onBlockReply).toHaveBeenCalled(); + }); + it("does not use session-level text dedupe when recent target does not match", 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 a8f0222d090..2793f588c94 100644 --- a/src/auto-reply/reply/followup-runner.ts +++ b/src/auto-reply/reply/followup-runner.ts @@ -333,6 +333,7 @@ export function createFollowupRunner(params: { const now = Date.now(); const recentWindowActive = typeof sessionEntry?.lastMessagingToolSentAt === "number" && + sessionEntry?.lastMessagingToolSessionId === queued.run.sessionId && now - sessionEntry.lastMessagingToolSentAt <= RECENT_MESSAGING_TOOL_DEDUPE_WINDOW_MS; const recentTargetMatch = recentWindowActive && diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index e133b4ae300..06ad3a53441 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -76,6 +76,8 @@ export type SessionEntry = { lastHeartbeatSentAt?: number; /** Timestamp (ms) for the most recent message-tool send fingerprint (cross-run dedupe). */ lastMessagingToolSentAt?: number; + /** Session id that produced the most recent message-tool dedupe fingerprint. */ + lastMessagingToolSessionId?: string; /** Recently sent message-tool text payloads for short-window cross-run dedupe. */ lastMessagingToolSentTexts?: string[]; /** Recently sent message-tool media urls for short-window cross-run dedupe. */