Mainstream messaging

Telegram

Production-ready for bot DMs and groups via grammY. Long polling is the default mode; webhook mode is optional.

Quick setup

  • Create the bot token in BotFather

    Open Telegram and chat with @BotFather (confirm the handle is exactly @BotFather).

    Run /newbot, follow prompts, and save the token.

  • Configure token and DM policy

    json5
    {channels: {telegram: {  enabled: true,  botToken: "123:abc",  dmPolicy: "pairing",  groups: { "*": { requireMention: true } },},},}

    Env fallback: TELEGRAM_BOT_TOKEN=... (default account only). Telegram does not use OmeniaClaw channels login telegram; configure token in config/env, then start gateway.

  • Start gateway and approve first DM

    bash
    OmeniaClaw gatewayOmeniaClaw pairing list telegramOmeniaClaw pairing approve telegram <CODE>

    Pairing codes expire after 1 hour.

  • Add the bot to a group

    Add the bot to your group, then get both IDs that group access needs:

    • your Telegram user ID, used in allowFrom / groupAllowFrom
    • the Telegram group chat ID, used as the key under channels.telegram.groups

    For first-time setup, get the group chat ID from OmeniaClaw logs --follow, a forwarded-ID bot, or Bot API getUpdates. After the group is allowed, /whoami@<bot_username> can confirm the user and group IDs.

    Negative Telegram supergroup IDs that start with -100 are group chat IDs. Put them under channels.telegram.groups, not under groupAllowFrom.

  • Telegram side settings

    Privacy mode and group visibility

    Telegram bots default to Privacy Mode, which limits what group messages they receive.

    If the bot must see all group messages, either:

    • disable privacy mode via /setprivacy, or
    • make the bot a group admin.

    When toggling privacy mode, remove + re-add the bot in each group so Telegram applies the change.

    Group permissions

    Admin status is controlled in Telegram group settings.

    Admin bots receive all group messages, which is useful for always-on group behavior.

    Helpful BotFather toggles
    • /setjoingroups to allow/deny group adds
    • /setprivacy for group visibility behavior

    Access control and activation

    Group bot identity

    In Telegram groups and forum topics, an explicit mention of the configured bot handle (for example @my_bot) is treated as addressing the selected OmeniaClaw agent, even when the agent persona name differs from the Telegram username. The group silence policy still applies to unrelated group traffic, but the bot handle itself is not considered "someone else."

    DM policy

    channels.telegram.dmPolicy controls direct message access:

    • pairing (default)
    • allowlist (requires at least one sender ID in allowFrom)
    • open (requires allowFrom to include "*")
    • disabled

    dmPolicy: "open" with allowFrom: ["*"] lets any Telegram account that finds or guesses the bot username command the bot. Use it only for intentionally public bots with tightly restricted tools; one-owner bots should use allowlist with numeric user IDs.

    channels.telegram.allowFrom accepts numeric Telegram user IDs. telegram: / tg: prefixes are accepted and normalized. In multi-account configs, a restrictive top-level channels.telegram.allowFrom is treated as a safety boundary: account-level allowFrom: ["*"] entries do not make that account public unless the effective account allowlist still contains an explicit wildcard after merging. dmPolicy: "allowlist" with empty allowFrom blocks all DMs and is rejected by config validation. Setup asks for numeric user IDs only. If you upgraded and your config contains @username allowlist entries, run OmeniaClaw doctor --fix to resolve them (best-effort; requires a Telegram bot token). If you previously relied on pairing-store allowlist files, OmeniaClaw doctor --fix can recover entries into channels.telegram.allowFrom in allowlist flows (for example when dmPolicy: "allowlist" has no explicit IDs yet).

    For one-owner bots, prefer dmPolicy: "allowlist" with explicit numeric allowFrom IDs to keep access policy durable in config (instead of depending on previous pairing approvals).

    Common confusion: DM pairing approval does not mean "this sender is authorized everywhere". Pairing grants DM access. If no command owner exists yet, the first approved pairing also sets commands.ownerAllowFrom so owner-only commands and exec approvals have an explicit operator account. Group sender authorization still comes from explicit config allowlists. If you want "I am authorized once and both DMs and group commands work", put your numeric Telegram user ID in channels.telegram.allowFrom; for owner-only commands, make sure commands.ownerAllowFrom contains telegram:<your user id>.

    Finding your Telegram user ID

    Safer (no third-party bot):

    1. DM your bot.
    2. Run OmeniaClaw logs --follow.
    3. Read from.id.

    Official Bot API method:

    bash
    curl "https://api.telegram.org/bot<bot_token>/getUpdates"

    Third-party method (less private): @userinfobot or @getidsbot.

    Group policy and allowlists

    Two controls apply together:

    1. Which groups are allowed (channels.telegram.groups)

      • no groups config:
        • with groupPolicy: "open": any group can pass group-ID checks
        • with groupPolicy: "allowlist" (default): groups are blocked until you add groups entries (or "*")
      • groups configured: acts as allowlist (explicit IDs or "*")
    2. Which senders are allowed in groups (channels.telegram.groupPolicy)

      • open
      • allowlist (default)
      • disabled

    groupAllowFrom is used for group sender filtering. If not set, Telegram falls back to allowFrom. groupAllowFrom entries should be numeric Telegram user IDs (telegram: / tg: prefixes are normalized). Do not put Telegram group or supergroup chat IDs in groupAllowFrom. Negative chat IDs belong under channels.telegram.groups. Non-numeric entries are ignored for sender authorization. Security boundary (2026.2.25+): group sender auth does not inherit DM pairing-store approvals. Pairing stays DM-only. For groups, set groupAllowFrom or per-group/per-topic allowFrom. If groupAllowFrom is unset, Telegram falls back to config allowFrom, not the pairing store. Practical pattern for one-owner bots: set your user ID in channels.telegram.allowFrom, leave groupAllowFrom unset, and allow the target groups under channels.telegram.groups. Runtime note: if channels.telegram is completely missing, runtime defaults to fail-closed groupPolicy="allowlist" unless channels.defaults.groupPolicy is explicitly set.

    Owner-only group setup:

    json5
    {channels: {telegram: {  enabled: true,  dmPolicy: "pairing",  allowFrom: ["&lt;YOUR_TELEGRAM_USER_ID&gt;"],  groupPolicy: "allowlist",  groups: {    "&lt;GROUP_CHAT_ID&gt;": {      requireMention: true,    },  },},},}

    Test it from the group with @<bot_username> ping. Plain group messages do not trigger the bot while requireMention: true.

    Example: allow any member in one specific group:

    json5
    {channels: {telegram: {  groups: {    "-1001234567890": {      groupPolicy: "open",      requireMention: false,    },  },},},}

    Example: allow only specific users inside one specific group:

    json5
    {channels: {telegram: {  groups: {    "-1001234567890": {      requireMention: true,      allowFrom: ["8734062810", "745123456"],    },  },},},}

    Mention behavior

    Group replies require mention by default.

    Mention can come from:

    • native @botusername mention, or
    • mention patterns in:
      • agents.list[].groupChat.mentionPatterns
      • messages.groupChat.mentionPatterns

    Session-level command toggles:

    • /activation always
    • /activation mention

    These update session state only. Use config for persistence.

    Persistent config example:

    json5
    {channels: {telegram: {  groups: {    "*": { requireMention: false },  },},},}

    Group history context defaults to mention-only: prior group messages are included only when they were addressed to the bot, are replies to the bot, or are the bot's own messages. Set includeGroupHistoryContext: "recent" to include recent room history for trusted groups. Set includeGroupHistoryContext: "none" to send no prior Telegram group history with the next turn.

    json5
    {channels: {telegram: {  includeGroupHistoryContext: "recent",},},}

    Getting the group chat ID:

    • forward a group message to @userinfobot / @getidsbot
    • or read chat.id from OmeniaClaw logs --follow
    • or inspect Bot API getUpdates
    • after the group is allowed, run /whoami@<bot_username> if native commands are enabled

    Runtime behavior

    • Telegram is owned by the gateway process.
    • Routing is deterministic: Telegram inbound replies back to Telegram (the model does not pick channels).
    • Inbound messages normalize into the shared channel envelope with reply metadata, media placeholders, and persisted reply-chain context for Telegram replies the gateway has observed.
    • Group sessions are isolated by group ID. Forum topics append :topic:<threadId> to keep topics isolated.
    • DM messages can carry message_thread_id; OmeniaClaw preserves it for replies. DM topic sessions split only when Telegram getMe reports has_topics_enabled: true for the bot; otherwise DMs stay on the flat session.
    • Long polling uses grammY runner with per-chat/per-thread sequencing. Overall runner sink concurrency uses agents.defaults.maxConcurrent.
    • Multi-account startup bounds concurrent Telegram getMe probes so large bot fleets do not fan out every account probe at once.
    • Long polling is guarded inside each gateway process so only one active poller can use a bot token at a time. If you still see getUpdates 409 conflicts, another OmeniaClaw gateway, script, or external poller is likely using the same token.
    • Long-polling watchdog restarts trigger after 120 seconds without completed getUpdates liveness by default. Increase channels.telegram.pollingStallThresholdMs only if your deployment still sees false polling-stall restarts during long-running work. The value is in milliseconds and is allowed from 30000 to 600000; per-account overrides are supported.
    • Telegram Bot API has no read-receipt support (sendReadReceipts does not apply).

    Feature reference

    Live stream preview (message edits)

    OmeniaClaw can stream partial replies in real time:

    • direct chats: preview message + editMessageText
    • groups/topics: preview message + editMessageText

    Requirement:

    • channels.telegram.streaming is off | partial | block | progress (default: partial)
    • progress keeps one editable status draft for tool progress, clears it at completion, and sends the final answer as a normal message
    • streaming.preview.toolProgress controls whether tool/progress updates reuse the same edited preview message (default: true when preview streaming is active)
    • streaming.preview.commandText controls command/exec detail inside those tool-progress lines: raw (default, preserves released behavior) or status (tool label only)
    • streaming.progress.commentary (default: false) opts into assistant commentary/preamble text in the temporary progress draft
    • legacy channels.telegram.streamMode, boolean streaming values, and retired native draft preview keys are detected; run OmeniaClaw doctor --fix to migrate them to current streaming config

    Tool-progress preview updates are the short status lines shown while tools run, for example command execution, file reads, planning updates, patch summaries, or Codex preamble/commentary text in Codex app-server mode. Telegram keeps these enabled by default to match released OmeniaClaw behavior from v2026.4.22 and later.

    To keep the edited preview for answer text but hide tool-progress lines, set:

    json
    {  "channels": {    "telegram": {      "streaming": {        "mode": "partial",        "preview": {          "toolProgress": false        }      }    }  }}

    To keep tool-progress visible but hide command/exec text, set:

    json
    {  "channels": {    "telegram": {      "streaming": {        "mode": "partial",        "preview": {          "commandText": "status"        }      }    }  }}

    Use progress mode when you want visible tool progress without editing the final answer into that same message. Put the command-text policy under streaming.progress:

    json
    {  "channels": {    "telegram": {      "streaming": {        "mode": "progress",        "progress": {          "toolProgress": true,          "commandText": "status"        }      }    }  }}

    Use streaming.mode: "off" only when you want final-only delivery: Telegram preview edits are disabled and generic tool/progress chatter is suppressed instead of being sent as standalone status messages. Approval prompts, media payloads, and errors still route through normal final delivery. Use streaming.preview.toolProgress: false when you only want to keep answer preview edits while hiding the tool-progress status lines.

    For text-only replies:

    • short DM/group/topic previews: OmeniaClaw keeps the same preview message and performs the final edit in place
    • long text finals that split into multiple Telegram messages reuse the existing preview as the first final chunk when possible, then send only the remaining chunks
    • progress-mode finals clear the status draft and use normal final delivery instead of editing the draft into the answer
    • if the final edit fails before the completed text is confirmed, OmeniaClaw uses normal final delivery and cleans up the stale preview

    For complex replies (for example media payloads), OmeniaClaw falls back to normal final delivery and then cleans up the preview message.

    Preview streaming is separate from block streaming. When block streaming is explicitly enabled for Telegram, OmeniaClaw skips the preview stream to avoid double-streaming.

    Reasoning stream behavior:

    • /reasoning stream uses a supported channel's reasoning-preview path; on Telegram, it streams reasoning into the live preview while generating
    • the reasoning preview is deleted after final delivery; use /reasoning on when reasoning should remain visible
    • final answer is sent without reasoning text
    Rich message formatting

    Outbound text uses standard Telegram HTML messages by default so replies remain readable across current Telegram clients. This compatibility mode supports normal bold, italic, links, code, spoilers, and quotes, but not Bot API 10.1 rich-only blocks such as native tables, details, rich media, and formulas.

    Set channels.telegram.richMessages: true to opt into Bot API 10.1 rich messages:

    json5
    {channels: {telegram: {  richMessages: true,},},}

    When enabled:

    • The agent is told that Telegram rich messages are available for this bot/account.
    • Markdown text is rendered through OmeniaClaw's Markdown IR and sent as Telegram rich HTML.
    • Explicit rich HTML payloads preserve supported Bot API 10.1 tags such as headings, tables, details, rich media, and formulas.
    • Media captions still use Telegram HTML captions because rich messages do not replace captions.

    This keeps model text away from Telegram Rich Markdown sigils, so currency like $400-600K is not parsed as math. Long rich text is split automatically across Telegram's rich text and rich block limits. Tables over Telegram's column limit are sent as code blocks.

    Default: off for client compatibility. Rich messages require compatible Telegram clients; some current Desktop, Web, Android, and third-party clients display accepted rich messages as unsupported. Keep this option disabled unless every client used with the bot can render them. /status shows whether the current Telegram session has rich messages on or off.

    Link previews are enabled by default. channels.telegram.linkPreview: false skips automatic entity detection for rich text.

    Native commands and custom commands

    Telegram command menu registration is handled at startup with setMyCommands.

    Native command defaults:

    • commands.native: "auto" enables native commands for Telegram

    Add custom command menu entries:

    json5
    {channels: {telegram: {  customCommands: [    { command: "backup", description: "Git backup" },    { command: "generate", description: "Create an image" },  ],},},}

    Rules:

    • names are normalized (strip leading /, lowercase)
    • valid pattern: a-z, 0-9, _, length 1..32
    • custom commands cannot override native commands
    • conflicts/duplicates are skipped and logged

    Notes:

    • custom commands are menu entries only; they do not auto-implement behavior
    • plugin/skill commands can still work when typed even if not shown in Telegram menu

    If native commands are disabled, built-ins are removed. Custom/plugin commands may still register if configured.

    Common setup failures:

    • setMyCommands failed with BOT_COMMANDS_TOO_MUCH means the Telegram menu still overflowed after trimming; reduce plugin/skill/custom commands or disable channels.telegram.commands.native.
    • deleteWebhook, deleteMyCommands, or setMyCommands failing with 404: Not Found while direct Bot API curl commands work can mean channels.telegram.apiRoot was set to the full /bot&lt;TOKEN&gt; endpoint. apiRoot must be only the Bot API root, and OmeniaClaw doctor --fix removes an accidental trailing /bot&lt;TOKEN&gt;.
    • getMe returned 401 means Telegram rejected the configured bot token. Update botToken, tokenFile, or TELEGRAM_BOT_TOKEN with the current BotFather token; OmeniaClaw stops before polling so this is not reported as a webhook cleanup failure.
    • setMyCommands failed with network/fetch errors usually means outbound DNS/HTTPS to api.telegram.org is blocked.

    Device pairing commands (device-pair plugin)

    When the device-pair plugin is installed:

    1. /pair generates setup code
    2. paste code in iOS app
    3. /pair pending lists pending requests (including role/scopes)
    4. approve the request:
      • /pair approve <requestId> for explicit approval
      • /pair approve when there is only one pending request
      • /pair approve latest for most recent

    The setup code carries a short-lived bootstrap token. Built-in setup-code bootstrap is node-only: the first connect creates a pending node request, and after approval the Gateway returns a durable node token with scopes: []. It does not return a handed-off operator token; operator access requires a separate approved operator pairing or token flow.

    If a device retries with changed auth details (for example role/scopes/public key), the previous pending request is superseded and the new request uses a different requestId. Re-run /pair pending before approving.

    More details: Pairing.

    Inline buttons

    Configure inline keyboard scope:

    json5
    {channels: {telegram: {  capabilities: {    inlineButtons: "allowlist",  },},},}

    Per-account override:

    json5
    {channels: {telegram: {  accounts: {    main: {      capabilities: {        inlineButtons: "allowlist",      },    },  },},},}

    Scopes:

    • off
    • dm
    • group
    • all
    • allowlist (default)

    Legacy capabilities: ["inlineButtons"] maps to inlineButtons: "all".

    Message action example:

    json5
    {action: "send",channel: "telegram",to: "123456789",message: "Choose an option:",buttons: [[  { text: "Yes", callback_data: "yes" },  { text: "No", callback_data: "no" },],[{ text: "Cancel", callback_data: "cancel" }],],}

    Mini App button example:

    json5
    {action: "send",channel: "telegram",to: "123456789",message: "Open app:",presentation: {blocks: [  {    type: "buttons",    buttons: [{ label: "Launch", web_app: { url: "https://example.com/app" } }],  },],},}

    Telegram web_app buttons work only in private chats between a user and the bot.

    Callback clicks are passed to the agent as text: callback_data: <value>

    Telegram message actions for agents and automation

    Telegram tool actions include:

    • sendMessage (to, content, optional mediaUrl, replyToMessageId, messageThreadId)
    • react (chatId, messageId, emoji)
    • deleteMessage (chatId, messageId)
    • editMessage (chatId, messageId, content or caption, optional presentation inline buttons; button-only edits update reply markup)
    • createForumTopic (chatId, name, optional iconColor, iconCustomEmojiId)

    Channel message actions expose ergonomic aliases (send, react, delete, edit, sticker, sticker-search, topic-create).

    Gating controls:

    • channels.telegram.actions.sendMessage
    • channels.telegram.actions.deleteMessage
    • channels.telegram.actions.reactions
    • channels.telegram.actions.sticker (default: disabled)

    Note: edit and topic-create are currently enabled by default and do not have separate channels.telegram.actions.* toggles. Runtime sends use the active config/secrets snapshot (startup/reload), so action paths do not perform ad-hoc SecretRef re-resolution per send.

    Reaction removal semantics: /tools/reactions

    Reply threading tags

    Telegram supports explicit reply threading tags in generated output:

    • [[reply_to_current]] replies to the triggering message
    • [[reply_to:<id>]] replies to a specific Telegram message ID

    channels.telegram.replyToMode controls handling:

    • off (default)
    • first
    • all

    When reply threading is enabled and the original Telegram text or caption is available, OmeniaClaw includes a native Telegram quote excerpt automatically. Telegram caps native quote text at 1024 UTF-16 code units, so longer messages are quoted from the start and fall back to a plain reply if Telegram rejects the quote.

    Note: off disables implicit reply threading. Explicit [[reply_to_*]] tags are still honored.

    Forum topics and thread behavior

    Forum supergroups:

    • topic session keys append :topic:<threadId>
    • replies and typing target the topic thread
    • topic config path: channels.telegram.groups.<chatId>.topics.<threadId>

    General topic (threadId=1) special-case:

    • message sends omit message_thread_id (Telegram rejects sendMessage(...thread_id=1))
    • typing actions still include message_thread_id

    Topic inheritance: topic entries inherit group settings unless overridden (requireMention, allowFrom, skills, systemPrompt, enabled, groupPolicy). agentId is topic-only and does not inherit from group defaults. topics."*" sets defaults for every topic in that group; exact topic IDs still win over "*".

    Per-topic agent routing: Each topic can route to a different agent by setting agentId in the topic config. This gives each topic its own isolated workspace, memory, and session. Example:

    json5
    {  channels: {    telegram: {      groups: {        "-1001234567890": {          topics: {            "1": { agentId: "main" },      // General topic → main agent            "3": { agentId: "zu" },        // Dev topic → zu agent            "5": { agentId: "coder" }      // Code review → coder agent          }        }      }    }  }}

    Each topic then has its own session key: agent:zu:telegram:group:-1001234567890:topic:3

    Persistent ACP topic binding: Forum topics can pin ACP harness sessions through top-level typed ACP bindings (bindings[] with type: "acp" and match.channel: "telegram", peer.kind: "group", and a topic-qualified id like -1001234567890:topic:42). Currently scoped to forum topics in groups/supergroups. See ACP Agents.

    Thread-bound ACP spawn from chat: /acp spawn <agent> --thread here|auto binds the current topic to a new ACP session; follow-ups route there directly. OmeniaClaw pins the spawn confirmation in-topic. Requires channels.telegram.threadBindings.spawnSessions to remain enabled (default: true).

    Template context exposes MessageThreadId and IsForum. DM chats with message_thread_id keep reply metadata; they use thread-aware session keys only when Telegram getMe reports has_topics_enabled: true for the bot. The former dm.threadReplies and direct.*.threadReplies overrides are intentionally retired; use BotFather threaded mode as the single source of truth and run OmeniaClaw doctor --fix to remove stale config keys.

    Audio, video, and stickers

    Audio messages

    Telegram distinguishes voice notes vs audio files.

    • default: audio file behavior
    • tag [[audio_as_voice]] in agent reply to force voice-note send
    • inbound voice-note transcripts are framed as machine-generated, untrusted text in the agent context; mention detection still uses the raw transcript so mention-gated voice messages continue to work.

    Message action example:

    json5
    {action: "send",channel: "telegram",to: "123456789",media: "https://example.com/voice.ogg",asVoice: true,}

    Video messages

    Telegram distinguishes video files vs video notes.

    Message action example:

    json5
    {action: "send",channel: "telegram",to: "123456789",media: "https://example.com/video.mp4",asVideoNote: true,}

    Video notes do not support captions; provided message text is sent separately.

    Stickers

    Inbound sticker handling:

    • static WEBP: downloaded and processed (placeholder <media:sticker>)
    • animated TGS: skipped
    • video WEBM: skipped

    Sticker context fields:

    • Sticker.emoji
    • Sticker.setName
    • Sticker.fileId
    • Sticker.fileUniqueId
    • Sticker.cachedDescription

    Sticker descriptions are cached in OmeniaClaw SQLite plugin state to reduce repeated vision calls.

    Enable sticker actions:

    json5
    {channels: {telegram: {  actions: {    sticker: true,  },},},}

    Send sticker action:

    json5
    {action: "sticker",channel: "telegram",to: "123456789",fileId: "CAACAgIAAxkBAAI...",}

    Search cached stickers:

    json5
    {action: "sticker-search",channel: "telegram",query: "cat waving",limit: 5,}
    Reaction notifications

    Telegram reactions arrive as message_reaction updates (separate from message payloads).

    When enabled, OmeniaClaw enqueues system events like:

    • Telegram reaction added: 👍 by Alice (@alice) on msg 42

    Config:

    • channels.telegram.reactionNotifications: off | own | all (default: own)
    • channels.telegram.reactionLevel: off | ack | minimal | extensive (default: minimal)

    Notes:

    • own means user reactions to bot-sent messages only (best-effort via sent-message cache).
    • Reaction events still respect Telegram access controls (dmPolicy, allowFrom, groupPolicy, groupAllowFrom); unauthorized senders are dropped.
    • Telegram does not provide thread IDs in reaction updates.
      • non-forum groups route to group chat session
      • forum groups route to the group general-topic session (:topic:1), not the exact originating topic

    allowed_updates for polling/webhook include message_reaction automatically.

    Ack reactions

    ackReaction sends an acknowledgement emoji while OmeniaClaw is processing an inbound message. ackReactionScope decides when that emoji is actually sent.

    Emoji (ackReaction) resolution order:

    • channels.telegram.accounts.<accountId>.ackReaction
    • channels.telegram.ackReaction
    • messages.ackReaction
    • agent identity emoji fallback (agents.list[].identity.emoji, else "👀")

    Notes:

    • Telegram expects unicode emoji (for example "👀").
    • Use "" to disable the reaction for a channel or account.

    Scope (messages.ackReactionScope):

    The Telegram provider reads scope from messages.ackReactionScope (default "group-mentions"). There is no Telegram-account or Telegram-channel-level override today.

    Values: "all" (DMs + groups), "direct" (DMs only), "group-all" (every group message, no DMs), "group-mentions" (groups when the bot is mentioned; no DMs — this is the default), "off" / "none" (disabled).

    Config writes from Telegram events and commands

    Channel config writes are enabled by default (configWrites !== false).

    Telegram-triggered writes include:

    • group migration events (migrate_to_chat_id) to update channels.telegram.groups
    • /config set and /config unset (requires command enablement)

    Disable:

    json5
    {channels: {telegram: {  configWrites: false,},},}
    Long polling vs webhook

    Default is long polling. For webhook mode set channels.telegram.webhookUrl and channels.telegram.webhookSecret; optional webhookPath, webhookHost, webhookPort (defaults /telegram-webhook, 127.0.0.1, 8787).

    In long-polling mode OmeniaClaw persists its restart watermark only after an update dispatches successfully. If a handler fails, that update remains retryable in the same process and is not written as completed for restart dedupe.

    The local listener binds to 127.0.0.1:8787. For public ingress, either put a reverse proxy in front of the local port or set webhookHost: "0.0.0.0" intentionally.

    Webhook mode validates request guards, the Telegram secret token, and the JSON body before returning 200 to Telegram. OmeniaClaw then processes the update asynchronously through the same per-chat/per-topic bot lanes used by long polling, so slow agent turns do not hold Telegram's delivery ACK.

    Limits, retry, and CLI targets
    • channels.telegram.textChunkLimit default is 4000.
    • channels.telegram.chunkMode="newline" prefers paragraph boundaries (blank lines) before length splitting.
    • channels.telegram.mediaMaxMb (default 100) caps inbound and outbound Telegram media size.
    • channels.telegram.mediaGroupFlushMs (default 500) controls how long Telegram albums/media groups are buffered before OmeniaClaw dispatches them as one inbound message. Increase it if album parts arrive late; decrease it to reduce album reply latency.
    • channels.telegram.timeoutSeconds overrides Telegram API client timeout (if unset, grammY default applies). Bot clients clamp configured values below the 60-second outbound text/typing request guard so grammY does not abort visible reply delivery before OmeniaClaw's transport guard and fallback can run. Long polling still uses a 45-second getUpdates request guard so idle polls are not abandoned indefinitely.
    • channels.telegram.pollingStallThresholdMs defaults to 120000; tune between 30000 and 600000 only for false-positive polling-stall restarts.
    • group context history uses channels.telegram.historyLimit or messages.groupChat.historyLimit (default 50); 0 disables.
    • reply/quote/forward supplemental context is normalized into one selected conversation context window when the gateway has observed the parent messages; the observed-message cache lives in OmeniaClaw SQLite plugin state, and OmeniaClaw doctor --fix imports legacy sidecars. Telegram only includes one shallow reply_to_message in updates, so chains older than the cache are limited to Telegram's current update payload.
    • Telegram allowlists primarily gate who can trigger the agent, not a full supplemental-context redaction boundary.
    • DM history controls:
      • channels.telegram.dmHistoryLimit
      • channels.telegram.dms["<user_id>"].historyLimit
    • channels.telegram.retry config applies to Telegram send helpers (CLI/tools/actions) for recoverable outbound API errors. Inbound final-reply delivery also uses a bounded safe-send retry for Telegram pre-connect failures, but it does not retry ambiguous post-send network envelopes that could duplicate visible messages.

    CLI and message-tool send targets can be numeric chat ID, username, or a forum topic target:

    bash
    OmeniaClaw message send --channel telegram --target 123456789 --message "hi"OmeniaClaw message send --channel telegram --target @name --message "hi"OmeniaClaw message send --channel telegram --target -1001234567890:topic:42 --message "hi topic"

    Telegram polls use OmeniaClaw message poll and support forum topics:

    bash
    OmeniaClaw message poll --channel telegram --target 123456789 \--poll-question "Ship it?" --poll-option "Yes" --poll-option "No"OmeniaClaw message poll --channel telegram --target -1001234567890:topic:42 \--poll-question "Pick a time" --poll-option "10am" --poll-option "2pm" \--poll-duration-seconds 300 --poll-public

    Telegram-only poll flags:

    • --poll-duration-seconds (5-600)
    • --poll-anonymous
    • --poll-public
    • --thread-id for forum topics (or use a :topic: target)

    Telegram send also supports:

    • --presentation with buttons blocks for inline keyboards when channels.telegram.capabilities.inlineButtons allows it
    • --pin or --delivery '{"pin":true}' to request pinned delivery when the bot can pin in that chat
    • --force-document to send outbound images, GIFs, and videos as documents instead of compressed photo, animated-media, or video uploads

    Action gating:

    • channels.telegram.actions.sendMessage=false disables outbound Telegram messages, including polls
    • channels.telegram.actions.poll=false disables Telegram poll creation while leaving regular sends enabled
    Exec approvals in Telegram

    Telegram supports exec approvals in approver DMs and can optionally post prompts in the originating chat or topic. Approvers must be numeric Telegram user IDs.

    Config path:

    • channels.telegram.execApprovals.enabled (auto-enables when at least one approver is resolvable)
    • channels.telegram.execApprovals.approvers (falls back to numeric owner IDs from commands.ownerAllowFrom)
    • channels.telegram.execApprovals.target: dm (default) | channel | both
    • agentFilter, sessionFilter

    channels.telegram.allowFrom, groupAllowFrom, and defaultTo control who can talk to the bot and where it sends normal replies. They do not make someone an exec approver. The first approved DM pairing bootstraps commands.ownerAllowFrom when no command owner exists yet, so the one-owner setup still works without duplicating IDs under execApprovals.approvers.

    Channel delivery shows the command text in the chat; only enable channel or both in trusted groups/topics. When the prompt lands in a forum topic, OmeniaClaw preserves the topic for the approval prompt and the follow-up. Exec approvals expire after 30 minutes by default.

    Inline approval buttons also require channels.telegram.capabilities.inlineButtons to allow the target surface (dm, group, or all). Approval IDs prefixed with plugin: resolve through plugin approvals; others resolve through exec approvals first.

    See Exec approvals.

    Error reply controls

    When the agent encounters a delivery or provider error, Telegram can either reply with the error text or suppress it. Two config keys control this behavior:

    Key Values Default Description
    channels.telegram.errorPolicy reply, silent reply reply sends a friendly error message to the chat. silent suppresses error replies entirely.
    channels.telegram.errorCooldownMs number (ms) 60000 Minimum time between error replies to the same chat. Prevents error spam during outages.

    Per-account, per-group, and per-topic overrides are supported (same inheritance as other Telegram config keys).

    json5
    {  channels: {    telegram: {      errorPolicy: "reply",      errorCooldownMs: 120000,      groups: {        "-1001234567890": {          errorPolicy: "silent", // suppress errors in this group        },      },    },  },}

    Troubleshooting

    Bot does not respond to non mention group messages
    • If requireMention=false, Telegram privacy mode must allow full visibility.
      • BotFather: /setprivacy -> Disable
      • then remove + re-add bot to group
    • OmeniaClaw channels status warns when config expects unmentioned group messages.
    • OmeniaClaw channels status --probe can check explicit numeric group IDs; wildcard "*" cannot be membership-probed.
    • quick session test: /activation always.
    Bot not seeing group messages at all
    • when channels.telegram.groups exists, group must be listed (or include "*")
    • verify bot membership in group
    • review logs: OmeniaClaw logs --follow for skip reasons
    Commands work partially or not at all
    • authorize your sender identity (pairing and/or numeric allowFrom)
    • command authorization still applies even when group policy is open
    • setMyCommands failed with BOT_COMMANDS_TOO_MUCH means the native menu has too many entries; reduce plugin/skill/custom commands or disable native menus
    • deleteMyCommands / setMyCommands startup calls and sendChatAction typing calls are bounded and retry once through Telegram's transport fallback on request timeout. Persistent network/fetch errors usually indicate DNS/HTTPS reachability issues to api.telegram.org
    Startup reports unauthorized token
    • getMe returned 401 is a Telegram authentication failure for the configured bot token.
    • Re-copy or regenerate the bot token in BotFather, then update channels.telegram.botToken, channels.telegram.tokenFile, channels.telegram.accounts.<id>.botToken, or TELEGRAM_BOT_TOKEN for the default account.
    • deleteWebhook 401 Unauthorized during startup is also an auth failure; treating it as "no webhook exists" would only defer the same bad-token failure to later API calls.
    Polling or network instability
    • Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
    • Some hosts resolve api.telegram.org to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.
    • If logs include TypeError: fetch failed or Network request for 'getUpdates' failed!, OmeniaClaw now retries these as recoverable network errors.
    • During polling startup, OmeniaClaw reuses the successful startup getMe probe for grammY so the runner does not need a second getMe before the first getUpdates.
    • If deleteWebhook fails with a transient network error during polling startup, OmeniaClaw continues into long polling instead of making another pre-poll control-plane call. A still-active webhook surfaces as a getUpdates conflict; OmeniaClaw then rebuilds the Telegram transport and retries webhook cleanup.
    • If Telegram sockets recycle on a short fixed cadence, check for a low channels.telegram.timeoutSeconds; bot clients clamp configured values below the outbound and getUpdates request guards, but older releases could abort every poll or reply when this was set below those guards.
    • If logs include Polling stall detected, OmeniaClaw restarts polling and rebuilds the Telegram transport after 120 seconds without completed long-poll liveness by default.
    • OmeniaClaw channels status --probe and OmeniaClaw doctor warn when a running polling account has not completed getUpdates after startup grace, when a running webhook account has not completed setWebhook after startup grace, or when the last successful polling transport activity is stale.
    • Increase channels.telegram.pollingStallThresholdMs only when long-running getUpdates calls are healthy but your host still reports false polling-stall restarts. Persistent stalls usually point to proxy, DNS, IPv6, or TLS egress issues between the host and api.telegram.org.
    • Telegram also honors process proxy env for Bot API transport, including HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and their lowercase variants. NO_PROXY / no_proxy can still bypass api.telegram.org.
    • If the OmeniaClaw managed proxy is configured through OmeniaClaw_PROXY_URL for a service environment and no standard proxy env is present, Telegram uses that URL for Bot API transport too.
    • On VPS hosts with unstable direct egress/TLS, route Telegram API calls through channels.telegram.proxy:
    yaml
    channels:telegram:proxy: socks5://<user>:<password>@proxy-host:1080
    • Node 22+ defaults to autoSelectFamily=true (except WSL2). Telegram DNS result order honors OmeniaClaw_TELEGRAM_DNS_RESULT_ORDER, then channels.telegram.network.dnsResultOrder, then the process default such as NODE_OPTIONS=--dns-result-order=ipv4first; if none applies, Node 22+ falls back to ipv4first.
    • If your host is WSL2 or explicitly works better with IPv4-only behavior, force family selection:
    yaml
    channels:telegram:network:  autoSelectFamily: false
    • RFC 2544 benchmark-range answers (198.18.0.0/15) are already allowed for Telegram media downloads by default. If a trusted fake-IP or transparent proxy rewrites api.telegram.org to some other private/internal/special-use address during media downloads, you can opt in to the Telegram-only bypass:
    yaml
    channels:telegram:network:  dangerouslyAllowPrivateNetwork: true
    • The same opt-in is available per account at channels.telegram.accounts.<accountId>.network.dangerouslyAllowPrivateNetwork.
    • If your proxy resolves Telegram media hosts into 198.18.x.x, leave the dangerous flag off first. Telegram media already allows the RFC 2544 benchmark range by default.
    • Environment overrides (temporary):
      • OmeniaClaw_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1
      • OmeniaClaw_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY=1
      • OmeniaClaw_TELEGRAM_DNS_RESULT_ORDER=ipv4first
    • Validate DNS answers:
    bash
    dig +short api.telegram.org Adig +short api.telegram.org AAAA

    More help: Channel troubleshooting.

    Configuration reference

    Primary reference: Configuration reference - Telegram.

    High-signal Telegram fields
    • startup/auth: enabled, botToken, tokenFile, accounts.* (tokenFile must point to a regular file; symlinks are rejected)
    • access control: dmPolicy, allowFrom, groupPolicy, groupAllowFrom, groups, groups.*.topics.*, top-level bindings[] (type: "acp")
    • topic defaults: groups.<chatId>.topics."*" applies to unmatched forum topics; exact topic IDs override it
    • exec approvals: execApprovals, accounts.*.execApprovals
    • command/menu: commands.native, commands.nativeSkills, customCommands
    • threading/replies: replyToMode
    • streaming: streaming (preview), streaming.preview.toolProgress, blockStreaming
    • formatting/delivery: textChunkLimit, chunkMode, richMessages, linkPreview, responsePrefix
    • media/network: mediaMaxMb, mediaGroupFlushMs, timeoutSeconds, pollingStallThresholdMs, retry, network.autoSelectFamily, network.dangerouslyAllowPrivateNetwork, proxy
    • custom API root: apiRoot (Bot API root only; do not include /bot&lt;TOKEN&gt;)
    • webhook: webhookUrl, webhookSecret, webhookPath, webhookHost
    • actions/capabilities: capabilities.inlineButtons, actions.sendMessage|editMessage|deleteMessage|reactions|sticker
    • reactions: reactionNotifications, reactionLevel
    • errors: errorPolicy, errorCooldownMs
    • writes/history: configWrites, historyLimit, dmHistoryLimit, dms.*.historyLimit
    Was this useful?
    On this page

    On this page