From d94de5c4a163cbf41b876e61ce2d2389440a7047 Mon Sep 17 00:00:00 2001 From: bboyyan Date: Tue, 3 Mar 2026 00:32:06 +0800 Subject: [PATCH] fix(cron): normalize topic-qualified target.to in messaging tool suppress check (#29480) * fix(cron): pass job.delivery.accountId through to delivery target resolution * fix(cron): normalize topic-qualified target.to in messaging tool suppress check When a cron job targets a Telegram forum topic (e.g. delivery.to = "-1003597428309:topic:462"), delivery.to is stripped to the chatId only by resolveOutboundTarget. However, the agent's message tool may pass the full topic-qualified address as its target, causing matchesMessagingToolDeliveryTarget to fail the equality check and not suppress the tool send. Strip the :topic:NNN suffix from target.to before comparing so the suppress check works correctly for topic-bound cron deliveries. Without this, the agent's message tool fires separately using the announce session's accountId (often "default"), hitting 403 when default bot is not in the multi-account target group. * fix(cron): remove duplicate accountId keys after rebase --------- Co-authored-by: jaxpkm Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> --- src/cron/isolated-agent/delivery-dispatch.ts | 6 +++++- src/cron/isolated-agent/delivery-target.ts | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/cron/isolated-agent/delivery-dispatch.ts b/src/cron/isolated-agent/delivery-dispatch.ts index db8f03d383a..272f327fd8e 100644 --- a/src/cron/isolated-agent/delivery-dispatch.ts +++ b/src/cron/isolated-agent/delivery-dispatch.ts @@ -36,7 +36,11 @@ export function matchesMessagingToolDeliveryTarget( if (target.accountId && delivery.accountId && target.accountId !== delivery.accountId) { return false; } - return target.to === delivery.to; + // Strip :topic:NNN suffix from target.to before comparing — the cron delivery.to + // is already stripped to chatId only, but the agent's message tool may pass a + // topic-qualified target (e.g. "-1003597428309:topic:462"). + const normalizedTargetTo = target.to.replace(/:topic:\d+$/, ""); + return normalizedTargetTo === delivery.to; } export function resolveCronDeliveryBestEffort(job: CronJob): boolean { diff --git a/src/cron/isolated-agent/delivery-target.ts b/src/cron/isolated-agent/delivery-target.ts index a8051e65c4f..3905ab695bd 100644 --- a/src/cron/isolated-agent/delivery-target.ts +++ b/src/cron/isolated-agent/delivery-target.ts @@ -42,6 +42,7 @@ export async function resolveDeliveryTarget( jobPayload: { channel?: "last" | ChannelId; to?: string; + /** Explicit accountId from job.delivery — overrides session-derived and binding-derived values. */ accountId?: string; sessionKey?: string; }, @@ -118,6 +119,11 @@ export async function resolveDeliveryTarget( } } + // job.delivery.accountId takes highest precedence — explicitly set by the job author. + if (jobPayload.accountId) { + accountId = jobPayload.accountId; + } + // Carry threadId when it was explicitly set (from :topic: parsing or config) // or when delivering to the same recipient as the session's last conversation. // Session-derived threadIds are dropped when the target differs to prevent