Files
adolf/extensions/telegram/AGENTS.md
alvis bedb527145
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Vendor OpenClaw source as Adolf fork baseline
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
2026-07-05 09:36:54 +00:00

6.1 KiB

Telegram Plugin Guide

Read this before any change under extensions/telegram/. These are intentional maintainer decisions and review-binding invariants, not incidental implementation details. Also read extensions/AGENTS.md for the plugin boundary rules.

Verified against Telegram Bot API 10.1, July 1 2026.

Reliability Invariants

  • Durable-before-ack on both transports. Polling: the ingress worker advances its offset only after the parent's committed spool enqueue. Webhook: respond 200 only after the spool write; a spool-write failure returning non-200 is the redelivery contract, not an error to fix.
  • Completed spool rows tombstone via complete(), never delete. Telegram can refetch an update after dispatch, and callback side effects would rerun on a plain delete.
  • One retry policy. spooled-update-retry-policy.ts is the sole owner of spool backoff and dead-letter decisions; the polling and webhook drains both consume it. The dead-letter age gate is a product decision: over-limit updates keep retrying at the capped delay and only tombstone once older than the minimum age. Do not dead-letter on raw attempt counts, and do not "unstick" a lane by removing the gate.
  • Never swallow inbound processing errors. A transient store error on a spooled replay must record a failed-retryable processing result; a swallowed throw acks the update as completed and deletes the message.
  • No per-message full-store writes. Hot-path SQLite writes are per-entry. Rewriting a cache on every send or read stalls the event loop, and that stall masquerades as a polling stall (the sent-message-cache regression).
  • Transport error classification. The getUpdates worker retries Bot API 5xx and 429 locally, honoring parameters.retry_after; 401/404 stay fatal; 409 must propagate to the parent session, which owns webhook-conflict recovery. Bot API errors carry error_code, not .code; parse non-2xx bodies defensively (a 502 HTML page is not JSON).
  • Send funnel parity. The durable funnel (send.ts) and the streaming funnel (bot/delivery.*) must degrade identically: rich-entity 400 falls back to plain text, caption parse 400 falls back to a plain caption, quote-not-found 400 falls back to a legacy reply. New recoveries go into the shared predicates (send-error-predicates.ts, reply-parameters.ts), never into one funnel only.
  • Outbound flood waits honor retry_after up to TELEGRAM_OUTBOUND_RETRY_AFTER_CAP_MS; do not re-clamp Telegram sends to the generic channel retry ceiling.
  • Webhook security ordering. The secret header is validated first (constant-time compare, single-header enforcement, connection close on 401); the request rate limit budgets only failed-auth attempts so Telegram's own delivery is never throttled.
  • Every owned undici transport gets closed on all exit paths: polling session, webhook shutdown and startup failure, probe-cache eviction.

Streaming

  • Do not reintroduce sendMessageDraft for answer streaming. Telegram drafts are ephemeral 30-second previews in private chats; final delivery still requires a separate sendMessage. OpenClaw uses sendMessage plus editMessageText, then finalizes in place so the user sees one persistent answer.
  • Streaming owns one visible preview message. Edit it forward. Do not send an extra final bubble unless the final edit genuinely failed.
  • Keep the first-preview debounce. If a provider sends token-sized deltas, coalesce them into cumulative preview text instead of removing the debounce.
  • Respect Telegram limits in the Telegram layer. Text over 4096 chars chains into continuation messages. Polls keep the current Bot API 12-option cap.

Telegram API Ownership

  • Prefer grammY primitives and Telegram-native helpers when they model the behavior directly. Avoid custom Bot API wrappers for behavior grammY already owns.
  • Throttling is bot-token scoped. All Telegram API clients for the same token share one grammY apiThrottler() instance.
  • Do not silently retry failed topic sends without topic metadata. A wrong-surface success is worse than a loud Telegram error.
  • DM topics and forum topics are distinct. direct_messages_topic_id and message_thread_id are not interchangeable.

Context And Authorization

  • Reply context comes from OpenClaw-observed messages. Bot API updates expose reply_to_message, but there is no arbitrary getMessage(chat, id) hydration path later.
  • Current local chat context must outrank stale reply ancestry in the prompt. Old replied-to messages should not look like the active conversation.
  • The group history window is always on for groups and bounded by historyLimit. Do not reintroduce prompt-history gating modes; that regression blinded ambient rooms.
  • The group history window is rolling. Use self-entry watermark selection for "since your last reply" views; do not reintroduce destructive clears because room events are not persisted to the session and cleared context is unrecoverable.
  • Pairing is DM-only. Group and topic authorization need explicit config allowlists.
  • Telegram allowlists use numeric sender IDs. Usernames are optional, mutable, and not a reliable arbitrary-user lookup key in the Bot API.
  • Group and channel visible replies are policy-controlled. Normal room replies stay private unless messages.groupChat.visibleReplies: "automatic" is set or the agent explicitly calls message.send.

Interactive Surfaces

  • Native callbacks stay structured. Approval, native command, plugin, select, and multiselect callbacks must not fall through as raw callback text.
  • Preserve callback values exactly, including delimiters such as env|prod.
  • Native slash commands should remain fast-pathable before full workspace and agent-turn setup.

Review Standard

  • Telegram behavior PRs need real Telegram proof when they touch transport, streaming, topics, callbacks, authorization, or reply context. Prefer the bot-to-bot QA lane or an equivalent live Telegram probe over synthetic-only validation.
  • Reliability PRs (spool, drain, retry, ack, offset paths) need crash-window or restart-replay test proof, not just happy-path tests.