@wsz987/channel-telegram
v0.5.0
Published
Telegram Bot API channel adapter for DeepSeek Harness (credentials seam, control-plane definition, edit streaming, file hydration)
Readme
@wsz987/channel-telegram
Telegram Bot API channel adapter for DeepSeek Harness.
Install
pnpm add @wsz987/channel-telegramBundled community channel
This package is part of the community-maintained @wsz987/dsh-channels bundle.
It is not an official DeepSeek Harness or Telegram package.
It implements the same Channel Contract as weixin / qq / dingtalk / lark,
with no changes to channel-core, channel-harness or the other adapters.
Setup and credentials go through the Channel Control Plane; the bot token is
stored via ctx.credentials (tokenRef), never in profile config.
The bundle patch inserts this adapter as
channels-telegramand the Web settings panel shows it with the other bundled channels.
Contract coverage:
ChannelAdaptercontract (start/stop/send/getHealth/ capabilities)runChannelAdapterContract— the testkit's full contract suite passes- Cordis plugin shape (
name/inject/applyviactx.effect) - Schemastery
Configwith an injectableHttpTransport(offline fake in tests) - Fixture-driven mapper tests (
fixtures/telegram/*.json, Bot API 10.2 shapes) - M4 governance:
readonly manifestclass field +manifest.tsforchannels doctorcompatibility checks
The adapter remains experimental until its real-platform live gate passes;
offline contract tests and fixtures do not by themselves justify tested.
Pointing it at a real bot
In your profile config:
{
"plugins": {
"channel-telegram": {
"enabled": true,
"accountId": "main",
"baseUrl": "https://api.telegram.org",
"tokenRef": "TELEGRAM_BOT_TOKEN",
"longPollTimeoutMs": 25000
}
}
}The token is a secret resolved through ctx.credentials (tokenRef): it is never logged (bearer-style path segments are
redacted in transport error messages) and it only ever appears in the Bot API
request path built by the upstream driver. It is never written into fixture
files.
Inbound delivery currently uses Telegram Bot API long polling (getUpdates),
matching OpenClaw's local-install default. Startup removes an existing webhook
before polling because Telegram makes webhook and getUpdates delivery mutually
exclusive. This is an operational takeover of the Bot's update receiver: do not
reuse the same Bot for another webhook consumer. A hosted webhook transport is
not implemented yet.
Capabilities
| capability | value |
| ------------ | --------- |
| text / image / file / audio / video | ✅ |
| markdown | ✅ Rich Markdown |
| reactions | ❌ |
| cards | ❌ |
| threads | ✅ |
| streaming | DM Rich Draft; group plain preview + rich final edit; set streaming.enabled: false for buffered |
Known limits
- Minimum supported upstream is Telegram Bot API 10.2. Older or pinned custom
Bot API servers are not supported; use
formatting.mode: plainonly as an explicit presentation choice, not as an old-server compatibility mode. getUpdatessubscribes tomessageandcallback_query. Button interactions are currently intended only for callback queries carryingmessage.chat; inline-message callbacks without chat context are not a supported routing surface and require a mapper hardening change before release.- The ordinary message mapper still needs a complete zod trust-boundary schema; the current partial envelope validation and TypeScript casts are an identified release blocker, not evidence that arbitrary Telegram updates are supported.
- Media sends validate the Bot API
okenvelope and returnedmessage_id; anok: falseresponse is surfaced as a delivery failure. Offline coverage does not replace the required real-bot live gate. - Inbound image, file, audio, and video media hydrate bytes through
getFile/download when declared as byte-capable. Failures retainresourceRefplus ingress-failure metadata. - Telegram albums (
media_group_id) are intentionally delivered one update at a time. Each image is downloaded, dispatched, retried and acknowledged independently; no cross-update buffering or delayed album aggregation is performed. - Media captions are preserved as a text part before the image or document, so the model receives both the caption and the shared attachment representation.
- Media outbound accepts trusted
localDataviamultipart/form-data, a publicurl, or a platformfile_id(resourceRef). Multiple supported media parts are sent sequentially; an unsupported part fails the send rather than being silently dropped. - Inbound media maps Telegram
file_idto the contract'sresourceRefcarrier (an opaque platform handle), never tourl—urlis reserved for realhttp(s)URLs. beginAuth/pollAuthare omitted: auth is token-driven (getMe() check at start).- Forum topics preserve
message_thread_id;chat.type === 'channel'currently maps to admconversation.
Development
pnpm --filter @wsz987/channel-telegram build
pnpm --filter @wsz987/channel-telegram typecheck
pnpm --filter @wsz987/channel-telegram test