#14367: feat(telegram): add message read via inbound message store
docs
channel: telegram
agents
size: M
Cluster:
Telegram History and Features
## Summary
- add in-memory inbound message store (`src/telegram/inbound-message-store.ts`) — per-chat ring buffer (~200 msgs, 24h TTL) that records messages as they flow through the bot handler pipeline
- rewrite `readMessagesTelegram` to read from local store instead of calling `getUpdates` (which conflicts with the running long-poll loop and doesn't support per-chat history)
- wire up `read`/`readMessages` action in `telegram-actions.ts` with `before`/`after` pagination by message_id
- add `messages` to `TelegramActionConfig` for action gating
- update `docs/cli/message.md` with Telegram read docs
- add 10 unit tests for the inbound store
## Context
The Telegram Bot API has no per-chat message history endpoint (unlike Discord's REST API). The previous approach using `getUpdates` was fundamentally broken:
1. **409 Conflict** — `getUpdates` from a second `Bot` instance conflicts with the running `@grammyjs/runner` long-poll loop
2. **Consumes updates** — calling `getUpdates` with an offset acknowledges updates, causing the main bot to miss messages
3. **Not chat-specific** — returns ALL unprocessed updates across all chats, not history for a specific chat
The fix: record messages into a lightweight in-memory store as they arrive, then read from that store. No API calls needed.
Closes #12756
## Test
```bash
pnpm vitest run src/telegram/inbound-message-store.test.ts
```
10 tests covering: record/read, dedup, chat isolation, limit/before/after pagination, capacity eviction, field normalization, stats, edge cases.
## Design Decisions
- **In-memory only, no disk persistence** — avoids storing user message content on disk without explicit opt-in. Trade-off: messages lost on restart.
- **Records before access checks** — store reflects what the bot received (consistent with `sent-message-cache.ts`). Action gating controls read access separately.
- **Synchronous reads** — no API call, just a filtered slice from the buffer.
- **`before`/`after` by message_id** — matches Discord's pagination model.
## Limitations
- Only messages received while the process is running
- 24h eviction, ~200 per chat capacity
- No full chat history retrieval (Telegram platform constraint for bots)
## AI Disclosure
🤖 AI-assisted (Claude Opus 4.6 via OpenClaw). Fully tested, code reviewed by human. Both contributors understand the implementation and the Telegram Bot API constraints that motivated this approach.
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR adds an in-memory inbound message store for Telegram and updates the Telegram action tool to support reading messages from that store (instead of using `getUpdates`). It also adds config gating (`actions.messages`) and docs/test coverage for the store.
However, the new Telegram `read`/`readMessages` support is not wired into the channel action adapter used by `openclaw message read`. The Telegram plugin adapter does not implement the `read` action, so the CLI will still throw `Action read is not supported for provider telegram`. Additionally, the new handler expects `chatId`, while the CLI target plumbing maps `--target` to `to` for the `read` action, so even after wiring it, the parameter name mismatch would still break reads unless aligned.
<h3>Confidence Score: 2/5</h3>
- This PR should not be merged until the Telegram read action is correctly wired end-to-end.
- Core inbound store logic looks coherent, but the advertised `openclaw message read` support for Telegram is currently broken: the Telegram channel action adapter does not implement `read`, and the new handler expects `chatId` while CLI target plumbing provides `to`. This makes the feature non-functional as shipped.
- src/channels/plugins/actions/telegram.ts, src/agents/tools/telegram-actions.ts, src/infra/outbound/message-action-spec.ts, src/infra/outbound/channel-target.ts, docs/cli/message.md
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<details><summary><h4>Context used (3)</h4></summary>
- Context from `dashboard` - docs/cli/agents.md ([source](https://app.greptile.com/review/custom-context?memory=057a11aa-5c5f-48bb-8d53-91b27b0fe3a2))
- Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=fd949e91-5c3a-4ab5-90a1-cbe184fd6ce8))
- Context from `dashboard` - AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=0d0c8278-ef8e-4d6c-ab21-f5527e322f13))
</details>
<!-- /greptile_comment -->
Most Similar PRs
#7751: Telegram: persist inbound history (SQLite) + add read action
by welttowelt · 2026-02-03
89.2%
#19800: fix(telegram): add INFO-level logging at inbound message drop paths
by katalabut · 2026-02-18
81.0%
#15975: feat(telegram): add Business Mode for personal chat access
by scomma · 2026-02-14
80.3%
#11920: Telegram: create forum topics via message tool (thread-create)
by larsderidder · 2026-02-08
80.2%
#12950: feat: add Telegram pin/unpin message support
by alex-muradov · 2026-02-09
80.0%
#21029: Feature/telegram bot avatar clean
by aleonnet · 2026-02-19
79.3%
#12936: fix(telegram): omit message_thread_id for private DM chats
by omair445 · 2026-02-09
78.7%
#8166: fix(telegram): lifecycle fixes for duplicate messages and auto-reco...
by cheenu1092-oss · 2026-02-03
78.4%
#7058: feat(telegram): add channel_post handler for broadcast channels
by waifu7498173 · 2026-02-02
78.2%
#16501: feat(telegram): add inbound subagent queue with prefork progress
by cometjc · 2026-02-14
78.2%