fix(telegram): preserve forum topic thread in last-route delivery#53052
fix(telegram): preserve forum topic thread in last-route delivery#53052obviyus merged 5 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes a Telegram forum-topic routing bug where async and session follow-up replies were not preserving the originating forum topic Changes:
The implementation is correct. Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: extensions/telegram/src/bot-message-context.dm-topic-threadid.test.ts
Line: 75-93
Comment:
**Missing test coverage for General forum topic (id=1)**
The updated test verifies the forum-topic path when `message_thread_id` is present (id=99), but there is no test for a forum group message that arrives in the **General topic** (i.e., `is_forum: true` with no `message_thread_id`).
In that case, `resolveTelegramForumThreadId` returns `TELEGRAM_GENERAL_TOPIC_ID = 1`, so `resolvedThreadId = 1` and the new logic sets `updateLastRoute` with `threadId: "1"`. This is a new behavior — previously `updateLastRoute` was `undefined` for all group messages. While the API layer correctly strips `message_thread_id=1` before sending (via `buildTelegramThreadParams`), the effect of persisting `threadId: "1"` in the delivery context is now untested.
Consider adding a test case like:
```ts
it("sets updateLastRoute with threadId '1' for forum General topic (no message_thread_id)", async () => {
const ctx = await buildCtx({
message: {
chat: { id: -1001234567890, type: "supergroup", title: "Test Forum", is_forum: true },
text: "@bot hello",
// no message_thread_id → General topic
},
options: { forceWasMentioned: true },
resolveGroupActivation: () => true,
});
expect(ctx).not.toBeNull();
const updateLastRoute = getUpdateLastRoute() as { threadId?: string; to?: string } | undefined;
expect(updateLastRoute).toBeDefined();
expect(updateLastRoute?.to).toBe("telegram:-1001234567890");
expect(updateLastRoute?.threadId).toBe("1"); // General topic stored but stripped at API layer
});
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "style(telegram): format last-route updat..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a35753dc9a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| sessionKey: updateLastRouteSessionKey, | ||
| channel: "telegram", | ||
| to: `telegram:${chatId}`, | ||
| accountId: route.accountId, | ||
| threadId: updateLastRouteThreadId, |
There was a problem hiding this comment.
Skip topic last-route writes for sessions shared by multiple topics
When more than one Telegram topic is bound to the same targetSessionKey via configured ACP bindings or runtime thread bindings, this now stores each inbound topic's threadId on that shared session. Later implicit sends resolve from the mutable session delivery context (src/infra/outbound/targets.ts:109-118), so a completion started from topic 42 can be delivered into topic 99 after any message there updates the route. That shared-session setup is supported (src/channels/plugins/binding-routing.ts:60-80, extensions/telegram/src/thread-bindings.ts:606-612), so forum-topic traffic can now overwrite each other's destination instead of staying pinned to the originating topic.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
I don’t think this applies to the code path this PR changes.
This PR is about async/session follow-up replies. That path does not resolve from mutable session deliveryContext at send time...it captures the originating thread when the follow-up is queued in src/auto-reply/reply/get-reply-run.ts:519 and then routes with queued.originatingThreadId in src/auto-reply/reply/followup-runner.ts:101. The embedded follow-up run is also seeded from the same queued origin in src/auto-reply/reply/followup-runner.ts:182. The specific scenario described here would not affect the follow-up path this PR is fixing.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93b7cd6c1b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| updateLastRoute: | ||
| !isGroup || updateLastRouteThreadId != null | ||
| ? { | ||
| sessionKey: updateLastRouteSessionKey, | ||
| channel: "telegram", | ||
| to: `telegram:${chatId}`, | ||
| accountId: route.accountId, | ||
| threadId: updateLastRouteThreadId, |
There was a problem hiding this comment.
Avoid persisting topic thread IDs onto shared session routes
Fresh evidence beyond the earlier async-followup concern: even though queued followups carry ctx.MessageThreadId, session-scoped senders like cron and exec approvals still resolve from persisted deliveryContext (src/cron/isolated-agent/delivery-target.ts:66-99, src/infra/exec-approval-session-target.ts:51-68). Telegram forum topics can legitimately share one target session via the parent-peer/binding flow in extensions/telegram/src/conversation-route.ts:46-60, so this new branch will let any inbound topic overwrite that shared session’s stored threadId. In that setup, a later cron run or approval prompt for the same session can be delivered into whichever topic most recently had traffic, instead of the topic the session owner expects.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is a broader pre-existing shared-session routing issue, not the bug this PR is targeting right now. I'm happy to address it if the maintainers will have it though.
Summary
Fix Telegram forum-topic routing for async and session follow-up replies.
Previously, inbound Telegram messages only updated last-route delivery metadata for non-group chats. Forum-topic messages in Telegram supergroups therefore never persisted their topic
threadIdin the delivery context. When a later async completion or yielded follow-up replied, it had the group chat id but not the topic id, so the reply fell back to General instead of the originating topic.Root Cause
extensions/telegram/src/bot-message-context.session.tsonly updatedupdateLastRouteunder!isGroup, which meant forum-topic messages in group chats skipped last-route thread tracking entirely.What Changed
updateLastRouteThreadIdfromresolvedThreadIdfor group/forum messagesdmThreadIdfor DM topicsupdateLastRoutefor:mainDmOwnerPinDM-onlyWhy This Fix Works
The async reply path already knows how to send Telegram replies with a thread id. The missing piece was persisting the forum-topic thread id into the session last-route delivery context on inbound messages. With this change, later follow-up replies retain topic affinity instead of dropping into General.
Tests
pnpm test -- extensions/telegram/src/bot-message-context.dm-topic-threadid.test.tspnpm test -- extensions/telegram/src/bot-message-context.dm-threads.test.tsNotes
This is a narrow routing fix, not a broader Telegram delivery refactor.