fix(routing): unify session delivery invariants for duplicate suppression (#33786)

* Routing: unify session delivery invariants

* Routing: address PR review feedback

* Routing: tighten topic and session-scope suppression

* fix(chat): inherit routes for per-account channel-peer sessions
This commit is contained in:
Tak Hoffman
2026-03-03 21:40:38 -06:00
committed by GitHub
parent 1be39d4250
commit 7f2708a8c3
12 changed files with 436 additions and 28 deletions

View File

@@ -4,6 +4,7 @@ import { normalizeChannelId } from "../../channels/plugins/index.js";
import type { ReplyToMode } from "../../config/types.js";
import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js";
import { normalizeOptionalAccountId } from "../../routing/account-id.js";
import { parseTelegramTarget } from "../../telegram/targets.js";
import type { OriginatingChannelType } from "../templating.js";
import type { ReplyPayload } from "../types.js";
import { extractReplyToTag } from "./reply-tags.js";
@@ -162,6 +163,62 @@ function normalizeProviderForComparison(value?: string): string | undefined {
return PROVIDER_ALIAS_MAP[lowered] ?? lowered;
}
function normalizeThreadIdForComparison(value?: string): string | undefined {
const trimmed = value?.trim();
if (!trimmed) {
return undefined;
}
if (/^-?\d+$/.test(trimmed)) {
return String(Number.parseInt(trimmed, 10));
}
return trimmed.toLowerCase();
}
function resolveTargetProviderForComparison(params: {
currentProvider: string;
targetProvider?: string;
}): string {
const targetProvider = normalizeProviderForComparison(params.targetProvider);
if (!targetProvider || targetProvider === "message") {
return params.currentProvider;
}
return targetProvider;
}
function targetsMatchForSuppression(params: {
provider: string;
originTarget: string;
targetKey: string;
targetThreadId?: string;
}): boolean {
if (params.provider !== "telegram") {
return params.targetKey === params.originTarget;
}
const origin = parseTelegramTarget(params.originTarget);
const target = parseTelegramTarget(params.targetKey);
const explicitTargetThreadId = normalizeThreadIdForComparison(params.targetThreadId);
const targetThreadId =
explicitTargetThreadId ??
(target.messageThreadId != null ? String(target.messageThreadId) : undefined);
const originThreadId =
origin.messageThreadId != null ? String(origin.messageThreadId) : undefined;
if (origin.chatId.trim().toLowerCase() !== target.chatId.trim().toLowerCase()) {
return false;
}
if (originThreadId && targetThreadId != null) {
return originThreadId === targetThreadId;
}
if (originThreadId && targetThreadId == null) {
return false;
}
if (!originThreadId && targetThreadId != null) {
return false;
}
// chatId already matched and neither side carries thread context.
return true;
}
export function shouldSuppressMessagingToolReplies(params: {
messageProvider?: string;
messagingToolSentTargets?: MessagingToolSend[];
@@ -182,16 +239,14 @@ export function shouldSuppressMessagingToolReplies(params: {
return false;
}
return sentTargets.some((target) => {
const targetProvider = normalizeProviderForComparison(target?.provider);
if (!targetProvider) {
const targetProvider = resolveTargetProviderForComparison({
currentProvider: provider,
targetProvider: target?.provider,
});
if (targetProvider !== provider) {
return false;
}
const isGenericMessageProvider = targetProvider === "message";
if (!isGenericMessageProvider && targetProvider !== provider) {
return false;
}
const targetNormalizationProvider = isGenericMessageProvider ? provider : targetProvider;
const targetKey = normalizeTargetForProvider(targetNormalizationProvider, target.to);
const targetKey = normalizeTargetForProvider(targetProvider, target.to);
if (!targetKey) {
return false;
}
@@ -199,6 +254,11 @@ export function shouldSuppressMessagingToolReplies(params: {
if (originAccount && targetAccount && originAccount !== targetAccount) {
return false;
}
return targetKey === originTarget;
return targetsMatchForSuppression({
provider,
originTarget,
targetKey,
targetThreadId: target.threadId,
});
});
}