# Channel Telegram

> Run and operate a Telegram channel worker for LionClaw using the kernel channel bridge APIs.

- Skill: `moshthepitt/channel-telegram` (Agent Skill, multi-file: 14 files)
- Install (CLI): `npx skillmds@latest add moshthepitt/channel-telegram`
- Raw SKILL.md: https://api.skillmd.com/api/skills/moshthepitt/channel-telegram/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: moshthepitt (https://skillmd.com/u/moshthepitt)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/moshthepitt/channel-telegram

---


# Telegram Channel Worker

Use this skill when the user wants Telegram as a LionClaw channel.

Manual QA and Telegram-specific acceptance notes live in `README.md` in this
skill directory.

What you run:

1. start LionClaw for background work,
2. run the Telegram worker,
3. let it bridge Telegram traffic into LionClaw.

Under the hood, the worker:

1. receives Telegram updates through long polling by default, or through an
   optional webhook receiver with Telegram secret-token verification,
2. maps Telegram ids into stable refs such as `telegram:user:<id>`,
   `telegram:chat:<id>`, `telegram:topic:<id>`, and
   `telegram:message:<id>`,
3. posts normalized inbound facts to `/v0/channels/inbound` and treats the
   response as admission state, not completed work,
4. claims `lc_...` pairing tokens through the kernel pairing API without
   starting an agent turn for the claim message,
5. downloads and stages Telegram files only after the kernel returns
   `waiting_for_attachments`,
6. long-polls `/v0/channels/stream/pull` for progress events,
7. starts Telegram typing from kernel queue/runtime status events,
8. renders long-running turns as one provisional Telegram message and edits it
   on throttled progress state changes,
9. renders inline buttons for safe active-turn controls such as status and stop,
10. intercepts Telegram-local commands such as `/status` and `/stop` without
   stealing runtime slash commands such as `/compact`,
11. leases provider deliveries from `/v0/channels/outbox/pull`,
12. sends Telegram messages from outbox leases and reports provider outcomes to
   `/v0/channels/outbox/report`,
13. submits worker health checks through `/v0/channels/health/report`,
14. advances its progress cursor through `/v0/channels/stream/ack`.

## Prerequisites

- a configured LionClaw project instance
- Telegram bot token with DM access
- `python3` and `uv` for source/development worker runs

## Setup

Use the product path first:

```bash
printf 'TELEGRAM_BOT_TOKEN=...\n' > telegram.env
lionclaw connect telegram --env-file ./telegram.env
lionclaw doctor
```

`connect telegram` installs or refreshes the channel skill, stores declared
channel env in the selected instance home, and starts the background stack.

Direct worker invocation is for development and tests when you intentionally
provide the LionClaw worker environment. Polling is the default. For webhook
deployments, expose the worker's local receiver through HTTPS, set Telegram's
webhook URL with the same `secret_token`, and run the worker with its normal
LionClaw/Telegram env plus:

```bash
TELEGRAM_UPDATE_MODE=webhook \
TELEGRAM_WEBHOOK_SECRET_TOKEN=... \
TELEGRAM_WEBHOOK_HOST=127.0.0.1 \
TELEGRAM_WEBHOOK_PORT=8080 \
TELEGRAM_WEBHOOK_PATH=/telegram/webhook \
./skills/channel-telegram/scripts/worker
```

## Notes

- LionClaw enforces scoped pairing (`pending_approval` -> approved grant
  through `lionclaw channel pairing approve ...`).
- The worker accepts Telegram `message`, `edited_message`, `channel_post`, and
  `edited_channel_post` updates when they contain usable text, captions, or
  supported media. Inline `callback_query` updates are accepted only for
  HMAC-scoped LionClaw button payloads generated by this worker.
- Telegram usernames and display names are metadata only. Authorization identity
  uses numeric Telegram ids encoded in stable refs.
- Private chats map to `dm`; group mentions and bot replies use Telegram
  entities and the bot's numeric id; leading bot mentions are stripped from the
  runtime-facing text and captions; forum topics map to scoped thread refs.
- Supported inbound media descriptors include photos, documents, audio, voice,
  video, stickers, video notes, and animations. Telegram locations and venues
  are forwarded as readable text with structured provider metadata. Provider
  files are not downloaded for pending, blocked, ignored, or duplicate inbound.
  Unsupported user content such as contacts, polls, dice, and paid media is
  acknowledged with a clear local reply in DMs and addressed group routes, and
  is not submitted to the runtime.
- Back-to-back text updates from the same Telegram route are coalesced into one
  inbound turn when they arrive in the same provider batch. Telegram
  `media_group_id` albums are coalesced before attachment staging so the
  runtime sees one turn with all album files.
- Pairing invite tokens can be claimed through Telegram with `/start lc_<token>`.
  DM links use `https://t.me/<bot_username>?start=lc_<token>`; group links use
  `https://t.me/<bot_username>?startgroup=lc_<token>` where Telegram exposes the
  payload to the bot. Approved Telegram hosts can create a short-lived one-use
  group link from `/connections` in DM; unconnected groups are instructed to use
  that link path rather than exposing `pc_...` operator approval codes.
  Connected groups authorize the destination only. The Telegram sender must also
  have an approved direct host grant before group prompts, runtime slash
  commands, or local group controls run.
- The worker defaults `consumer_id` to `telegram:<channel_id>` and
  `start_mode=resume`, so unacked progress events are replayed after worker
  restart. `LIONCLAW_STREAM_START_MODE` accepts `resume` or `tail`.
- Telegram delivery is outbox-driven: typing comes from progress streams, final
  answers come from durable outbox leases, no reasoning lane delivery.
- Telegram has its own visible command menu. In DMs, `/help`, `/status`,
  `/stop`, `/settings`, and `/connections` are channel-local controls. Session
  mutation commands stay namespaced as `/lionclaw reset` and `/lionclaw retry`;
  bare slash commands pass through to the runtime after Telegram-only addressing
  syntax is removed.
  Connected groups get chat-scoped `/ask`, `/help`, `/status`, `/stop`, and
  `/settings` menu commands. In groups, `/ask message` strips `/ask` and submits
  only the message body to the runtime; empty `/ask` opens a selective Telegram
  reply prompt for the asker.
  `/stop` uses the channel-safe active turn cancellation action with the
  expected turn id guard. Bot commands explicitly targeted at a different
  Telegram bot are never captured as LionClaw-local controls, even inside an
  active forum topic. In groups, commands with a leading bot mention, for example
  `@lionclaw_bot /status`, are treated like first-column bot commands; runtime
  slash commands are forwarded without the leading mention or `@lionclaw_bot`
  command target.
- Inline buttons carry compact HMAC-protected callback payloads bound to the
  conversation, topic, original Telegram sender, and active turn. Stale,
  wrong-route, or wrong-actor callbacks are acknowledged without mutating
  runtime state.
- Fast turns only show typing. Long turns create one provisional message after a
  short threshold, edit it at a throttled cadence, and delete it when the durable
  outbox answer is ready. Cancelled and failed turns leave a terminal status.
  Transient edit failures are retried; permanent edit failures disable editing
  for that message and fall back to a normal Telegram message. Transient delete
  failures are persisted under the channel runtime directory and retried after
  worker restart so stale provisional messages are cleaned up.
- The worker also sets best-effort Telegram reactions on the original inbound
  message as lightweight receipts: accepted, completed, stopped, or failed.
  Reaction failures are ignored because Telegram reactions are not available in
  every chat.
- Outbound text is sent as plain text by default. Markdown hints render to
  Telegram-safe HTML only when the rendered chunks fit Telegram limits; otherwise
  delivery falls back to plain text. Link previews stay disabled so local
  workspace paths do not become broken Telegram links.
- Outbox attachments are sent as native Telegram media where possible
  (`sendPhoto`, `sendVideo`, `sendAudio`/`sendVoice`) and fall back to
  documents by MIME type. Short text on media deliveries is used as the first
  attachment caption when the rendered caption fits Telegram's limits. Native
  Telegram media groups are used for compatible photo/video batches with two
  to ten attachments.
- Topic `thread_ref` and `reply_to_ref` are converted back into Telegram
  delivery parameters, and each outbox lease is reported with its `attempt_id`.
- Runtime selection comes from the selected instance's default runtime; workers do not send `runtime_id` in inbound requests.
- Polling mode stores Telegram offset in
  `$LIONCLAW_HOME/runtime/channels/$LIONCLAW_CHANNEL_ID/telegram.offset` by
  default. Webhook mode is enabled with `TELEGRAM_UPDATE_MODE=webhook` and
  requires `TELEGRAM_WEBHOOK_SECRET_TOKEN`; `TELEGRAM_WEBHOOK_HOST`,
  `TELEGRAM_WEBHOOK_PORT`, `TELEGRAM_WEBHOOK_PATH`, and
  `TELEGRAM_WEBHOOK_MAX_BODY_BYTES` tune the local receiver.
- The worker reports health every 60 seconds by default, configurable with
  `LIONCLAW_HEALTH_REPORT_INTERVAL_SECS`. Checks cover a fresh Telegram
  `getMe`, polling or webhook intake health, update lag, malformed provider
  updates quarantined by the current worker process, and delivery failures
  observed by the current worker process.

## Development Checks

Run these from the repository root when changing this skill:

```bash
uv run --project skills/channel-telegram black --check skills/channel-telegram/lionclaw_channel_telegram skills/channel-telegram/tests
uv run --project skills/channel-telegram ruff check skills/channel-telegram/lionclaw_channel_telegram skills/channel-telegram/tests
uv run --project skills/channel-telegram python -m unittest discover -s skills/channel-telegram/tests -q
```

