fix(followup): scope session dedupe fingerprints to single-target runs

This commit is contained in:
KimGLee 2026-03-03 00:16:34 +08:00
parent 5625e3b2c0
commit 8ef9a77e63
2 changed files with 42 additions and 3 deletions

View File

@ -506,6 +506,40 @@ describe("createFollowupRunner messaging tool dedupe", () => {
expect(onBlockReply).toHaveBeenCalled();
});
it("does not reuse session-level text dedupe when prior run had multiple messaging targets", 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" },
{ tool: "message", provider: "telegram", to: "999" },
],
};
const sessionStore: Record<string, SessionEntry> = { main: sessionEntry };
runEmbeddedPiAgentMock.mockResolvedValueOnce({
payloads: [{ text: "hello world!" }],
meta: {},
});
const runner = createMessagingDedupeRunner(onBlockReply, {
sessionEntry,
sessionStore,
sessionKey: "main",
});
await runner({
...baseQueuedRun("telegram"),
originatingTo: "123",
});
expect(onBlockReply).toHaveBeenCalled();
});
it("drops media URL from payload when messaging tool already sent it", async () => {
const onBlockReply = vi.fn(async () => {});
runEmbeddedPiAgentMock.mockResolvedValueOnce({

View File

@ -335,22 +335,27 @@ export function createFollowupRunner(params: {
typeof sessionEntry?.lastMessagingToolSentAt === "number" &&
sessionEntry?.lastMessagingToolSessionId === queued.run.sessionId &&
now - sessionEntry.lastMessagingToolSentAt <= RECENT_MESSAGING_TOOL_DEDUPE_WINDOW_MS;
const previousSentTargets = sessionEntry?.lastMessagingToolSentTargets ?? [];
const recentTargetMatch =
recentWindowActive &&
shouldSuppressMessagingToolReplies({
messageProvider,
messagingToolSentTargets: sessionEntry?.lastMessagingToolSentTargets,
messagingToolSentTargets: previousSentTargets,
originatingTo,
accountId: originAccountId,
});
const canReuseSessionDedupeFingerprints =
recentTargetMatch && previousSentTargets.length <= 1;
const sentTexts = [
...(runResult.messagingToolSentTexts ?? []),
...(recentTargetMatch ? (sessionEntry?.lastMessagingToolSentTexts ?? []) : []),
...(canReuseSessionDedupeFingerprints ? (sessionEntry?.lastMessagingToolSentTexts ?? []) : []),
];
const sentMediaUrls = [
...(runResult.messagingToolSentMediaUrls ?? []),
...(recentTargetMatch ? (sessionEntry?.lastMessagingToolSentMediaUrls ?? []) : []),
...(canReuseSessionDedupeFingerprints
? (sessionEntry?.lastMessagingToolSentMediaUrls ?? [])
: []),
];
// Keep target-based suppression scoped to the current run only.
// Session-level dedupe state is used for text/media duplicate filtering when target matches.