Merged
Size
M
Change Breakdown
Feature65%
Refactor25%
Testing10%
#65973feat(telegram): expose forum topic names in agent context

Telegram forum topic names now in agent context

Telegram forum bots can now reference topics by human-readable names like "Design-learning" instead of opaque numeric IDs. Topic names are learned from Telegram service messages and cached in a bounded LRU.

Telegram's Bot API exposes numeric topic IDs but withholds topic names — leaving agents to work with cryptic numbers like "563" instead of usable labels. The fix intercepts Telegram's forum service messages (topic created, edited, closed, reopened) to build a bounded in-memory cache mapping thread IDs to human-readable names. When a message arrives in a forum topic, the cache is consulted and the name is injected into the agent's prompt metadata alongside the existing topic_id. The module uses a true LRU strategy via updatedAt timestamps rather than insertion order, capped at 2048 entries to prevent unbounded growth. A fallback seed from reply_to_message.forum_topic_created handles topics created before the bot started listening. The feature plugs into the existing type so templates gain {{TopicName}} substitution, and plugin hooks receive topicName in their event metadata — enabling downstream consumers like topic-specific config loaders to operate on names rather than IDs. This addresses a long-standing gap flagged across several linked issues around agent routing and topic-specific context in Telegram forums.

View Original GitHub Description

Summary

  • Problem: Telegram Bot API doesn't expose a method to look up forum topic names by message_thread_id. Agents only see a numeric topic_id (e.g. 563) with no way to know the human-readable name.
  • Why it matters: Agents need topic names to provide contextual responses, auto-match topic-specific config/files, and display meaningful labels — all impossible with just a numeric ID.
  • What changed:
    • New bounded in-memory LRU cache (topic-name-cache.ts) populated from forum_topic_created, forum_topic_edited, forum_topic_closed, forum_topic_reopened service messages
    • Fallback seed from reply_to_message.forum_topic_created for topics created before the bot started
    • TopicName added to MsgContext (available as {{TopicName}} in templates)
    • topic_name added to the agent prompt metadata block (alongside existing topic_id)
    • topicName propagated to plugin hook event metadata
  • What did NOT change: Existing MessageThreadId, IsForum, topic_id fields. Non-forum messages. Other channels. No new dependencies.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #36260
  • Related #43007, #43231, #33398
  • Related #36916 (prior attempt, closed — this PR addresses all review feedback from that PR)

Root Cause (if applicable)

N/A — new feature, not a bug fix.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/telegram/src/topic-name-cache.test.ts
  • Scenario the test should lock in: Cache stores/retrieves topic names, handles renames, tracks close/reopen, respects LRU eviction at 2048 entries
  • Why this is the smallest reliable guardrail: The cache is a pure function module with no I/O; unit tests cover all branches including eviction
  • If no new test is added, why not: New tests ARE added (9 test cases)

User-visible / Behavior Changes

  • topic_name now appears in the agent's "Conversation info (untrusted metadata)" block for Telegram forum topic messages. Example: topic_name: "Design-learning" alongside topic_id: "42".
  • {{TopicName}} is available in prompt templates.
  • Plugin hooks (message:received, claim events, internal contexts) now include topicName in metadata.
  • Cache is in-memory only; after a gateway restart, falls back to the creation-time name from reply_to_message.forum_topic_created until a live rename event repopulates.

Diagram (if applicable)

Before:
[telegram msg in topic] -> agent sees: topic_id: "563"

After:
[telegram msg in topic] -> topic-name-cache lookup -> agent sees: topic_id: "563", topic_name: "Design-learning"

Cache population:
[forum_topic_created msg] -> updateTopicName(chatId, threadId, {name})
[forum_topic_edited msg]  -> updateTopicName(chatId, threadId, {name})  (captures renames)
[forum_topic_closed msg]  -> updateTopicName(chatId, threadId, {closed: true})
[regular msg with reply_to_message.forum_topic_created] -> seed cache if empty (creation-time name)

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS (dev) + Amazon Linux 2023 on EC2 (VPS test)
  • Runtime: Node.js 22
  • Integration/channel: Telegram (forum supergroup)
  • Relevant config: Standard OpenClaw Telegram config with forum supergroup enabled

Steps

  1. Deploy OpenClaw with this change to a VPS connected to a Telegram bot
  2. Create or use an existing forum topic in a supergroup where the bot is a member
  3. Send a message in the topic
  4. Observe the agent's response — it should reference the topic name
  5. Rename the topic, send another message — agent should reflect the new name

Expected

  • topic_name populated in agent prompt metadata with the current topic name
  • Renames captured live from forum_topic_edited service messages

Actual

  • Verified working end-to-end on live Telegram forum supergroup

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Test results:

pnpm test:extension telegram
 Test Files  91 passed (91)
      Tests  1224 passed (1224)

pnpm test:contracts
 exit_code: 0

Debug trace confirming rename capture:

[TOPIC-DEBUG] chat=-1003633263090 thread=563 ftEdited={"name":"GLM vs Gemma vs M2.7"}
[TOPIC-DEBUG] stored from ftEdited: "GLM vs Gemma vs M2.7"
[TOPIC-DEBUG] final resolved name for -1003633263090:563 = "GLM vs Gemma vs M2.7"

Human Verification (required)

  • Verified scenarios: forum_topic_created seeding from reply_to_message, forum_topic_edited live rename capture, topic_name appearing in agent prompt metadata, agent referencing topic name in natural language response
  • Edge cases checked: cold cache (restart then fallback to creation-time name), LRU eviction at 2048 entries (unit test), missing message_thread_id, non-forum chats
  • What I did not verify: forum_topic_closed / forum_topic_reopened live (unit tested only), other channels unaffected (code inspection only)

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — new optional field, existing behavior unchanged
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: Cache is in-memory only; topic names lost on restart
    • Mitigation: Fallback seed from reply_to_message.forum_topic_created provides creation-time name immediately. Live renames repopulate the cache. Persistence could be added as a follow-up.
  • Risk: Eviction scan is O(n) over cache entries
    • Mitigation: Cache is capped at 2048 entries; linear scan over 2048 entries is sub-millisecond. Only runs when cache is full and a new entry is inserted.

AI-Assisted PR Disclosure

  • This PR was AI-assisted (Cursor + Claude)
  • Testing level: fully tested — unit tests + live Telegram verification on VPS
  • I understand what the code does
  • Bot review conversations will be resolved as addressed

Addresses all feedback from the prior closed PR #36916:

  1. FIFO eviction → true LRU via updatedAt timestamp (not Map insertion order)
  2. Unsafe Record<string, unknown> cast → uses grammy's native typed fields directly
  3. topic_name missing from prompt metadata (P1) → added to inbound-meta.ts + plugin hooks
  4. Added reply_to_message.forum_topic_created seed (not in #36916)
  5. Added forum_topic_closed/forum_topic_reopened lifecycle handling (not in #36916)
© 2026 · via Gitpulse