fix(replies): keep finals for cross-target messaging sends

Co-authored-by: Ion Mudreac <mudreac@gmail.com>
This commit is contained in:
Peter Steinberger
2026-02-22 19:32:06 +01:00
parent 40680432b4
commit 0342bed289
4 changed files with 60 additions and 8 deletions

View File

@@ -43,4 +43,32 @@ describe("buildReplyPayloads media filter integration", () => {
// Text filter removes the payload entirely (text matched), so nothing remains.
expect(replyPayloads).toHaveLength(0);
});
it("does not dedupe text for cross-target messaging sends", () => {
const { replyPayloads } = buildReplyPayloads({
...baseParams,
payloads: [{ text: "hello world!" }],
messageProvider: "telegram",
originatingTo: "telegram:123",
messagingToolSentTexts: ["hello world!"],
messagingToolSentTargets: [{ tool: "discord", provider: "discord", to: "channel:C1" }],
});
expect(replyPayloads).toHaveLength(1);
expect(replyPayloads[0]?.text).toBe("hello world!");
});
it("does not dedupe media for cross-target messaging sends", () => {
const { replyPayloads } = buildReplyPayloads({
...baseParams,
payloads: [{ text: "photo", mediaUrl: "file:///tmp/photo.jpg" }],
messageProvider: "telegram",
originatingTo: "telegram:123",
messagingToolSentMediaUrls: ["file:///tmp/photo.jpg"],
messagingToolSentTargets: [{ tool: "slack", provider: "slack", to: "channel:C1" }],
});
expect(replyPayloads).toHaveLength(1);
expect(replyPayloads[0]?.mediaUrl).toBe("file:///tmp/photo.jpg");
});
});

View File

@@ -91,14 +91,24 @@ export function buildReplyPayloads(params: {
originatingTo: params.originatingTo,
accountId: params.accountId,
});
const dedupedPayloads = filterMessagingToolDuplicates({
payloads: replyTaggedPayloads,
sentTexts: messagingToolSentTexts,
});
const mediaFilteredPayloads = filterMessagingToolMediaDuplicates({
payloads: dedupedPayloads,
sentMediaUrls: params.messagingToolSentMediaUrls ?? [],
});
// Only dedupe against messaging tool sends for the same origin target.
// Cross-target sends (for example posting to another channel) must not
// suppress the current conversation's final reply.
// If target metadata is unavailable, keep legacy dedupe behavior.
const dedupeMessagingToolPayloads =
suppressMessagingToolReplies || messagingToolSentTargets.length === 0;
const dedupedPayloads = dedupeMessagingToolPayloads
? filterMessagingToolDuplicates({
payloads: replyTaggedPayloads,
sentTexts: messagingToolSentTexts,
})
: replyTaggedPayloads;
const mediaFilteredPayloads = dedupeMessagingToolPayloads
? filterMessagingToolMediaDuplicates({
payloads: dedupedPayloads,
sentMediaUrls: params.messagingToolSentMediaUrls ?? [],
})
: dedupedPayloads;
// Filter out payloads already sent via pipeline or directly during tool flush.
const filteredPayloads = shouldDropFinalPayloads
? []

View File

@@ -876,6 +876,19 @@ describe("runReplyAgent messaging tool suppression", () => {
expect(result).toMatchObject({ text: "hello world!" });
});
it("keeps final reply when text matches a cross-target messaging send", async () => {
runEmbeddedPiAgentMock.mockResolvedValueOnce({
payloads: [{ text: "hello world!" }],
messagingToolSentTexts: ["hello world!"],
messagingToolSentTargets: [{ tool: "discord", provider: "discord", to: "channel:C1" }],
meta: {},
});
const result = await createRun("slack");
expect(result).toMatchObject({ text: "hello world!" });
});
it("delivers replies when account ids do not match", async () => {
runEmbeddedPiAgentMock.mockResolvedValueOnce({
payloads: [{ text: "hello world!" }],