Normalize telegram topic targets in delivery resolution (#59069)

* Normalize telegram topic targets in delivery resolution

* fix(cron): preserve explicit Telegram topic targets

* fix(clownfish): address review for ghcrawl-165998-agentic-merge (1)

---------

Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
This commit is contained in:
roytong9
2026-04-28 13:27:42 +08:00
committed by GitHub
parent 9577703249
commit a3fd97570f
3 changed files with 65 additions and 1 deletions

View File

@@ -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.

View File

@@ -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({

View File

@@ -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,