mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-21 21:56:46 +00:00
fix(agents): dedupe messaging tool replies by route
This commit is contained in:
@@ -188,26 +188,23 @@ export async function buildReplyPayloads(params: {
|
||||
const dedupeRuntime = shouldCheckMessagingToolDedupe
|
||||
? await loadReplyPayloadsDedupeRuntime()
|
||||
: null;
|
||||
const suppressMessagingToolReplies =
|
||||
dedupeRuntime?.shouldSuppressMessagingToolReplies({
|
||||
messageProvider: resolveOriginMessageProvider({
|
||||
originatingChannel: params.originatingChannel,
|
||||
provider: params.messageProvider,
|
||||
}),
|
||||
messagingToolSentTargets,
|
||||
originatingTo: resolveOriginMessageTo({
|
||||
originatingTo: params.originatingTo,
|
||||
}),
|
||||
accountId: resolveOriginAccountId({
|
||||
originatingAccountId: params.accountId,
|
||||
}),
|
||||
}) ?? false;
|
||||
// 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 messagingToolPayloadDedupe = dedupeRuntime?.resolveMessagingToolPayloadDedupe({
|
||||
messageProvider: resolveOriginMessageProvider({
|
||||
originatingChannel: params.originatingChannel,
|
||||
provider: params.messageProvider,
|
||||
}),
|
||||
messagingToolSentTargets,
|
||||
originatingTo: resolveOriginMessageTo({
|
||||
originatingTo: params.originatingTo,
|
||||
}),
|
||||
accountId: resolveOriginAccountId({
|
||||
originatingAccountId: params.accountId,
|
||||
}),
|
||||
}) ?? {
|
||||
shouldDedupePayloads: shouldCheckMessagingToolDedupe && messagingToolSentTargets.length === 0,
|
||||
suppressReplies: false,
|
||||
};
|
||||
const dedupeMessagingToolPayloads = messagingToolPayloadDedupe.shouldDedupePayloads;
|
||||
const messagingToolSentMediaUrls = dedupeMessagingToolPayloads
|
||||
? await normalizeSentMediaUrlsForDedupe({
|
||||
sentMediaUrls: params.messagingToolSentMediaUrls ?? [],
|
||||
@@ -284,7 +281,7 @@ export async function buildReplyPayloads(params: {
|
||||
sentMediaUrls: blockSentMediaUrls,
|
||||
})
|
||||
: contentSuppressedPayloads;
|
||||
const replyPayloads = suppressMessagingToolReplies
|
||||
const replyPayloads = messagingToolPayloadDedupe.suppressReplies
|
||||
? []
|
||||
: filteredPayloads.filter(isRenderablePayload);
|
||||
|
||||
|
||||
@@ -43,6 +43,32 @@ describe("resolveFollowupDeliveryPayloads", () => {
|
||||
).toEqual([{ mediaUrl: undefined, mediaUrls: undefined }]);
|
||||
});
|
||||
|
||||
it("does not dedupe text sent via messaging tool to another target", () => {
|
||||
expect(
|
||||
resolveFollowupDeliveryPayloads({
|
||||
cfg: baseConfig,
|
||||
payloads: [{ text: "hello world!" }],
|
||||
messageProvider: "telegram",
|
||||
originatingTo: "telegram:123",
|
||||
sentTexts: ["hello world!"],
|
||||
sentTargets: [{ tool: "discord", provider: "discord", to: "channel:C1" }],
|
||||
}),
|
||||
).toEqual([{ text: "hello world!" }]);
|
||||
});
|
||||
|
||||
it("does not dedupe media sent via messaging tool to another target", () => {
|
||||
expect(
|
||||
resolveFollowupDeliveryPayloads({
|
||||
cfg: baseConfig,
|
||||
payloads: [{ text: "photo", mediaUrl: "file:///tmp/photo.jpg" }],
|
||||
messageProvider: "telegram",
|
||||
originatingTo: "telegram:123",
|
||||
sentMediaUrls: ["file:///tmp/photo.jpg"],
|
||||
sentTargets: [{ tool: "slack", provider: "slack", to: "channel:C1" }],
|
||||
}),
|
||||
).toEqual([{ text: "photo", mediaUrl: "file:///tmp/photo.jpg" }]);
|
||||
});
|
||||
|
||||
it("suppresses replies when a messaging tool already sent to the same provider and target", () => {
|
||||
expect(
|
||||
resolveFollowupDeliveryPayloads({
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
applyReplyThreading,
|
||||
filterMessagingToolDuplicates,
|
||||
filterMessagingToolMediaDuplicates,
|
||||
shouldSuppressMessagingToolReplies,
|
||||
resolveMessagingToolPayloadDedupe,
|
||||
} from "./reply-payloads.js";
|
||||
import { resolveReplyToMode } from "./reply-threading.js";
|
||||
|
||||
@@ -35,10 +35,11 @@ export function resolveFollowupDeliveryPayloads(params: {
|
||||
sentTargets?: MessagingToolSend[];
|
||||
sentTexts?: string[];
|
||||
}): ReplyPayload[] {
|
||||
const replyToChannel = resolveOriginMessageProvider({
|
||||
const replyMessageProvider = resolveOriginMessageProvider({
|
||||
originatingChannel: params.originatingChannel,
|
||||
provider: params.messageProvider,
|
||||
}) as OriginatingChannelType | undefined;
|
||||
});
|
||||
const replyToChannel = replyMessageProvider as OriginatingChannelType | undefined;
|
||||
const replyToMode = resolveReplyToMode(
|
||||
params.cfg,
|
||||
replyToChannel,
|
||||
@@ -62,16 +63,8 @@ export function resolveFollowupDeliveryPayloads(params: {
|
||||
replyToMode,
|
||||
replyToChannel,
|
||||
});
|
||||
const dedupedPayloads = filterMessagingToolDuplicates({
|
||||
payloads: replyTaggedPayloads,
|
||||
sentTexts: params.sentTexts ?? [],
|
||||
});
|
||||
const mediaFilteredPayloads = filterMessagingToolMediaDuplicates({
|
||||
payloads: dedupedPayloads,
|
||||
sentMediaUrls: params.sentMediaUrls ?? [],
|
||||
});
|
||||
const suppressMessagingToolReplies = shouldSuppressMessagingToolReplies({
|
||||
messageProvider: replyToChannel,
|
||||
const messagingToolPayloadDedupe = resolveMessagingToolPayloadDedupe({
|
||||
messageProvider: replyMessageProvider,
|
||||
messagingToolSentTargets: params.sentTargets,
|
||||
originatingTo: resolveOriginMessageTo({
|
||||
originatingTo: params.originatingTo,
|
||||
@@ -80,5 +73,17 @@ export function resolveFollowupDeliveryPayloads(params: {
|
||||
originatingAccountId: params.originatingAccountId,
|
||||
}),
|
||||
});
|
||||
return suppressMessagingToolReplies ? [] : mediaFilteredPayloads;
|
||||
const mediaFilteredPayloads = messagingToolPayloadDedupe.shouldDedupePayloads
|
||||
? filterMessagingToolMediaDuplicates({
|
||||
payloads: replyTaggedPayloads,
|
||||
sentMediaUrls: params.sentMediaUrls ?? [],
|
||||
})
|
||||
: replyTaggedPayloads;
|
||||
const dedupedPayloads = messagingToolPayloadDedupe.shouldDedupePayloads
|
||||
? filterMessagingToolDuplicates({
|
||||
payloads: mediaFilteredPayloads,
|
||||
sentTexts: params.sentTexts ?? [],
|
||||
})
|
||||
: mediaFilteredPayloads;
|
||||
return messagingToolPayloadDedupe.suppressReplies ? [] : dedupedPayloads;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export {
|
||||
filterMessagingToolDuplicates,
|
||||
filterMessagingToolMediaDuplicates,
|
||||
resolveMessagingToolPayloadDedupe,
|
||||
shouldSuppressMessagingToolReplies,
|
||||
type MessagingToolPayloadDedupeDecision,
|
||||
} from "./reply-payloads-dedupe.js";
|
||||
|
||||
@@ -204,3 +204,28 @@ export function shouldSuppressMessagingToolReplies(params: {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export type MessagingToolPayloadDedupeDecision = {
|
||||
shouldDedupePayloads: boolean;
|
||||
suppressReplies: boolean;
|
||||
};
|
||||
|
||||
export function resolveMessagingToolPayloadDedupe(params: {
|
||||
messageProvider?: string;
|
||||
messagingToolSentTargets?: MessagingToolSend[];
|
||||
originatingTo?: string;
|
||||
accountId?: string;
|
||||
}): MessagingToolPayloadDedupeDecision {
|
||||
const sentTargets = params.messagingToolSentTargets ?? [];
|
||||
const suppressReplies = shouldSuppressMessagingToolReplies({
|
||||
messageProvider: params.messageProvider,
|
||||
messagingToolSentTargets: sentTargets,
|
||||
originatingTo: params.originatingTo,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
|
||||
return {
|
||||
shouldDedupePayloads: suppressReplies || sentTargets.length === 0,
|
||||
suppressReplies,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../p
|
||||
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import {
|
||||
filterMessagingToolMediaDuplicates,
|
||||
resolveMessagingToolPayloadDedupe,
|
||||
shouldSuppressMessagingToolReplies,
|
||||
} from "./reply-payloads.js";
|
||||
|
||||
@@ -244,3 +245,43 @@ describe("shouldSuppressMessagingToolReplies", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMessagingToolPayloadDedupe", () => {
|
||||
it("dedupes by content when messaging tool target metadata is unavailable", () => {
|
||||
expect(
|
||||
resolveMessagingToolPayloadDedupe({
|
||||
messageProvider: "telegram",
|
||||
originatingTo: "123",
|
||||
}),
|
||||
).toEqual({
|
||||
shouldDedupePayloads: true,
|
||||
suppressReplies: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("suppresses final replies when a messaging tool sent to the same route", () => {
|
||||
expect(
|
||||
resolveMessagingToolPayloadDedupe({
|
||||
messageProvider: "telegram",
|
||||
originatingTo: "123",
|
||||
messagingToolSentTargets: [{ tool: "message", provider: "telegram", to: "123" }],
|
||||
}),
|
||||
).toEqual({
|
||||
shouldDedupePayloads: true,
|
||||
suppressReplies: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps final payloads intact when a messaging tool sent to another route", () => {
|
||||
expect(
|
||||
resolveMessagingToolPayloadDedupe({
|
||||
messageProvider: "telegram",
|
||||
originatingTo: "123",
|
||||
messagingToolSentTargets: [{ tool: "slack", provider: "slack", to: "channel:C1" }],
|
||||
}),
|
||||
).toEqual({
|
||||
shouldDedupePayloads: false,
|
||||
suppressReplies: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,5 +8,7 @@ export {
|
||||
export {
|
||||
filterMessagingToolDuplicates,
|
||||
filterMessagingToolMediaDuplicates,
|
||||
resolveMessagingToolPayloadDedupe,
|
||||
shouldSuppressMessagingToolReplies,
|
||||
type MessagingToolPayloadDedupeDecision,
|
||||
} from "./reply-payloads-dedupe.js";
|
||||
|
||||
Reference in New Issue
Block a user