* feat(telegram): add child thread-binding placement via createForumTopic
Enable ACP subagent spawn on Telegram by adding "child" placement
support to the thread-bindings adapter. When a child binding is
requested, the adapter creates a new forum topic via the Telegram
Bot API and binds the subagent session to it using the canonical
chatId:topic:topicId conversation ID format.
When the ACP spawn context provides only a topic ID (not a full
group chat ID), the adapter resolves the group from the configured
Telegram groups in openclaw.json.
This mirrors the Discord adapter's child placement behavior
(thread creation + session binding) and unblocks the orchestrator
pattern on Telegram forum-enabled groups.
Closes#5737
Ref #23414
* fix(telegram): return null with warning instead of silent group fallback for bare topic IDs in child bind
* telegram: fix ACP child thread spawn with group chat ID from agentGroupId
* telegram: scope agentGroupId substitution to telegram channel only
* Telegram: fix forum topic replies routing to root chat instead of topic thread
* fix: clean up dead guard in child bind + add explicit threadId override test
- Simplify bare-topic-ID guards in thread-bindings.ts: split into
separate !chatId and !chatId.startsWith("-") checks, removing
unreachable second condition
- Add regression test confirming explicit turnSourceThreadId overrides
session lastThreadId on same channel
* fix: guard threadId fallback against shared-session race
Codex review P1: when turnSourceTo differs from the session's stored
to, the session threadId may belong to a different chat/topic. Only
fall back to context.threadId when the destination also matches.
* fix(telegram): enable ACP spawn from forum topics without thread binding
extractExplicitGroupId returned topic-qualified IDs (-100...:topic:1264)
instead of bare group chat IDs, breaking agentGroupId resolution.
agentGroupId was also never wired in the inline actions path.
For Telegram forum topics, skip thread binding entirely — the delivery
plan already routes correctly via requester origin (to + threadId).
Creating new forum topics per child session is unnecessary; output goes
back to the same topic the user asked from.
* fix(acp): bind Telegram forum sessions to current topic
* fix: restore Telegram forum-topic routing (#56060) (thanks @one27001)
---------
Co-authored-by: openclaw <mgabrie.dev@gmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): allow RFC 2544 benchmark IPs in media download SSRF policy (#57452)
Telegram CDN file servers may resolve to IPs in the RFC 2544 benchmark range (198.18.0.0/15). The SSRF policy blocked these downloads while Discord and Slack correctly allowed them. Set allowRfc2544BenchmarkRange to true to match other channel plugins.
* fix: note Telegram media RFC2544 CDN downloads (#57624) (thanks @MoerAI)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): prevent polling watchdog from aborting in-flight message delivery
The polling-stall watchdog only tracked getUpdates timestamps to detect
network stalls. When the agent takes >90s to process a message (common
with local/large models), getUpdates naturally pauses, and the watchdog
misidentifies this as a stall. It then calls fetchAbortController.abort(),
which cancels all in-flight Telegram API requests — including the
sendMessage call delivering the agent's reply. The message is silently
lost with no retry.
Track a separate lastApiActivityAt timestamp that is updated whenever
any Telegram API call (sendMessage, sendChatAction, etc.) completes
successfully. The watchdog now only triggers when both getUpdates AND
all other API activity have been silent beyond the threshold, proving
the network is genuinely stalled rather than just busy processing.
Update existing stall test to account for the new timestamp, and add a
regression test verifying that recent sendMessage activity suppresses
the watchdog.
Fixes#56065
Related: #53374, #54708
* fix(telegram): guard watchdog against in-flight API calls
* fix(telegram): bound watchdog API liveness
* fix: track newest watchdog API activity (#56343) (thanks @openperf)
* fix: note Telegram watchdog delivery fix (#56343) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
When approvals.exec.targets routes to a Telegram DM, the recipient
receives inline approval buttons but may not have explicit
channels.telegram.execApprovals configured. This adds a fallback
isTelegramExecApprovalTargetRecipient check so those DM recipients
can act on the buttons they were sent.
Includes accountId scoping for multi-bot deployments and 9 new tests.
Build a topic-qualified routing target (telegram:<chatId>:topic:<threadId>)
for native commands in forum groups so /new and /reset stay scoped to
the active topic instead of falling back to General.
General topic (threadId=1) correctly falls through to the base chat
target since Telegram rejects message_thread_id=1 on sends.
Add regression tests for topic routing and General topic edge case.
Fixes#35963
Filter whitespace-only text chunks at the bot delivery fan-in before
they reach sendTelegramText(). Covers normal text replies, follow-up
text, and voice fallback text paths.
Media-only replies are unaffected. message_sent hook still fires with
success: false for suppressed empty replies.
Fixes#37278
Replace proportional text estimate with binary search for the largest
text prefix whose rendered Telegram HTML fits the character limit, then
split at the last whitespace boundary within that verified prefix.
Single words longer than the limit still hard-split (unavoidable).
Markdown formatting stays balanced across split points.
Fixes#36644
Add shared normalizeTelegramReplyToMessageId() that rejects non-numeric,
NaN, and mixed-content strings before they reach the Telegram Bot API.
Apply at all four API sinks: direct send, bot delivery, draft stream,
and bot helpers.
Prevents GrammyError 400 when non-numeric values from session metadata
slip through typed boundaries.
Fixes#37222
* fix: display model name instead of ID in Telegram model selector (#56165)
* fix(telegram): scope model display names by provider
Signed-off-by: sallyom <somalley@redhat.com>
---------
Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>