From 642e1dfcdfb1298bfdf9dcb37d0e2dd2213e031f Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 18:10:55 -0700 Subject: [PATCH] fix(agents): preserve messaging dedupe thread ids --- CHANGELOG.md | 1 + src/auto-reply/reply/reply-payloads-dedupe.ts | 6 +----- src/auto-reply/reply/reply-payloads.test.ts | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d147a8bd3c..3cd1539b6ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai - Gateway/logging: expand leading `~` in `logging.file` before creating the file logger, preventing startup crash loops for home-relative log paths. Fixes #73587. - Channels/CLI: keep `openclaw channels list --json` usable when provider usage fetching fails, and report per-provider usage errors without aborting the channel list. Refs #67595. - Agents/messaging: deliver distinct final commentary after same-target `message` tool sends while still deduping text/media already sent by the tool, so short closing remarks are no longer silently dropped. Fixes #76915. Thanks @hclsys. +- Agents/messaging: preserve string thread IDs when matching message-tool reply dedupe routes, avoiding precision loss on numeric-looking topic IDs before channel plugin comparison. Thanks @vincentkoc. - OpenAI Codex: let SSRF-guarded provider requests inherit OpenClaw's undici IPv4/IPv6 fallback policy, so ChatGPT-backed Codex runs recover on IPv4-working hosts when DNS still returns unreachable IPv6 addresses. Fixes #76857. Thanks @jplavoiemtl and @SymbolStar. - Gateway/systemd: preserve operator-added secrets in the Gateway env file across re-stage while clearing OpenClaw-managed keys (such as `OPENCLAW_GATEWAY_TOKEN`) so a fresh staging value is never shadowed by a stale env-file copy; operator secrets are also retained when the state-dir `.env` is empty. Fixes #76860. Thanks @hclsys. - Plugin updates: do not short-circuit trusted official npm updates as unchanged when the default/latest spec still resolves to an already-installed prerelease that the installer should replace with a stable fallback. Thanks @vincentkoc. diff --git a/src/auto-reply/reply/reply-payloads-dedupe.ts b/src/auto-reply/reply/reply-payloads-dedupe.ts index 66cb4d36a63..ce2506b862c 100644 --- a/src/auto-reply/reply/reply-payloads-dedupe.ts +++ b/src/auto-reply/reply/reply-payloads-dedupe.ts @@ -88,11 +88,7 @@ function normalizeProviderForComparison(value?: string): string | undefined { } function normalizeThreadIdForComparison(value?: string): string | undefined { - const normalized = stringifyRouteThreadId(value); - if (!normalized) { - return undefined; - } - return /^-?\d+$/.test(normalized) ? String(Number.parseInt(normalized, 10)) : normalized; + return stringifyRouteThreadId(value); } function resolveTargetProviderForComparison(params: { diff --git a/src/auto-reply/reply/reply-payloads.test.ts b/src/auto-reply/reply/reply-payloads.test.ts index 1a7cbe257c2..e9b58dd37dd 100644 --- a/src/auto-reply/reply/reply-payloads.test.ts +++ b/src/auto-reply/reply/reply-payloads.test.ts @@ -197,6 +197,21 @@ describe("shouldDedupeMessagingToolRepliesForRoute", () => { ).toBe(true); }); + it("preserves string thread ids before plugin reply-suppression matching", () => { + installTelegramSuppressionRegistry(); + const largeThreadId = "9007199254740993"; + + expect( + shouldDedupeMessagingToolRepliesForRoute({ + messageProvider: "telegram", + originatingTo: `telegram:group:-100123:topic:${largeThreadId}`, + messagingToolSentTargets: [ + { tool: "message", provider: "telegram", to: "-100123", threadId: largeThreadId }, + ], + }), + ).toBe(true); + }); + it("does not match telegram topic-origin replies when explicit threadId differs", () => { expect( shouldDedupeMessagingToolRepliesForRoute({