diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a38f1e68ba..b42bde1a257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Cron/Telegram: preserve explicit `:topic:` delivery targets over stale session-derived thread IDs when isolated cron announces to Telegram forum topics. Carries forward #59069; refs #49704 and #43808. Thanks @roytong9. - Memory/Dreaming: retry Dream Diary once with the session default when a configured dreaming model is unavailable, while leaving subagent trust and allowlist errors visible instead of silently masking configuration problems. Refs #67409 and #69209. Thanks @Ghiggins18 and @everySympathy. - Feishu/inbound files: recover CJK filenames from plain `Content-Disposition: filename=` download headers when Feishu exposes UTF-8 bytes through Latin-1 header decoding, while leaving valid Latin-1 and JSON-derived names unchanged. (#48578, #50435, #59431) Thanks @alex-xuweilong, @lishuaigit, and @DoChaoing. diff --git a/src/cron/isolated-agent/delivery-target.test.ts b/src/cron/isolated-agent/delivery-target.test.ts index 099f479db0e..37251455315 100644 --- a/src/cron/isolated-agent/delivery-target.test.ts +++ b/src/cron/isolated-agent/delivery-target.test.ts @@ -116,6 +116,14 @@ beforeEach(() => { }, source: "test", }, + { + pluginId: "telegram", + plugin: createOutboundTestPlugin({ + id: "telegram", + outbound: createStubOutbound("Telegram"), + }), + source: "test", + }, ]), ); }); @@ -612,6 +620,51 @@ describe("resolveDeliveryTarget", () => { expect(result.accountId).toBe("bot-b"); }); + it("strips :topic: suffix from telegram targets when threadId is resolved", async () => { + setMainSessionEntry(undefined); + + const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, { + channel: "telegram", + to: "63448508:topic:1008013", + }); + + expect(result.ok).toBe(true); + expect(result.to).toBe("63448508"); + expect(result.threadId).toBe(1008013); + }); + + it("prefers explicit telegram :topic: targets over session-derived threadId", async () => { + setLastSessionEntry({ + sessionId: "sess-telegram-topic", + lastChannel: "telegram", + lastTo: "63448508:topic:1008013", + lastThreadId: "stale-thread", + }); + + const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, { + channel: "telegram", + to: "63448508:topic:1008013", + }); + + expect(result.ok).toBe(true); + expect(result.to).toBe("63448508"); + expect(result.threadId).toBe(1008013); + }); + + it("keeps explicit delivery threadId when stripping telegram :topic: targets", async () => { + setMainSessionEntry(undefined); + + const result = await resolveDeliveryTarget(makeCfg({ bindings: [] }), AGENT_ID, { + channel: "telegram", + to: "63448508:topic:1008013", + threadId: "42", + }); + + expect(result.ok).toBe(true); + expect(result.to).toBe("63448508"); + expect(result.threadId).toBe("42"); + }); + it("explicit delivery.accountId overrides bindings-derived accountId", async () => { setMainSessionEntry(undefined); const cfg = makeCfg({ diff --git a/src/cron/isolated-agent/delivery-target.ts b/src/cron/isolated-agent/delivery-target.ts index 7ac683a5d4d..fcd71d78585 100644 --- a/src/cron/isolated-agent/delivery-target.ts +++ b/src/cron/isolated-agent/delivery-target.ts @@ -158,12 +158,22 @@ export async function resolveDeliveryTarget( // or when delivering to the same recipient as the session's last conversation. // Session-derived threadIds are dropped when the target differs to prevent // stale thread IDs from leaking to a different chat. - const threadId = + let threadId = resolved.threadId && (resolved.threadIdExplicit || (resolved.to && resolved.to === resolved.lastTo)) ? resolved.threadId : undefined; + if (channel === "telegram" && typeof toCandidate === "string") { + const topicMatch = toCandidate.match(/:topic:(\d+)$/i); + if (topicMatch) { + if (jobPayload.threadId == null || jobPayload.threadId === "") { + threadId = Number(topicMatch[1]); + } + toCandidate = toCandidate.replace(/:topic:\d+$/i, ""); + } + } + if (!channel) { return { ok: false,