Skip to content

Add Telegram patch (second transport) - #1

Open
arlin17 wants to merge 1 commit into
nmonterroso:mainfrom
arlin17:telegram-patch
Open

Add Telegram patch (second transport)#1
arlin17 wants to merge 1 commit into
nmonterroso:mainfrom
arlin17:telegram-patch

Conversation

@arlin17

@arlin17 arlin17 commented Jul 20, 2026

Copy link
Copy Markdown

Adds Telegram as a second transport, implemented as a self-contained patch under src/patches/telegram/ behind the existing Patch interface — with zero changes to the transport-agnostic core beyond config wiring. The abstraction held: no core/ logic changed.

How it maps

Conversations are private chats (DMs) and forum topics in one configured supergroup:

Telegram Patchbay
Forum supergroup (TELEGRAM_GROUP_ID) the set of topic conversations
A forum topic one conversation_id (<chatId>:<threadId>) → one agent session
A DM to the bot one conversation_id (private chatId) → one session, named dm-<user>
Bash/Write/Edit approval Allow/Deny inline-keyboard buttons
Attachment getFile → download into the agent's .inbox/

conversation_id encodes the (chatId, threadId) pair the Bot API needs to deliver — "<chatId>" for DM/General, "<chatId>:<threadId>" for a topic (chat ids may be negative but never contain :, so one split round-trips). The core still treats it as opaque.

Access control mirrors Discord's forum-vs-DM split: topics gate on group membership (chat.id == TELEGRAM_GROUP_ID), DMs gate on TELEGRAM_ALLOWLIST, and permission verdicts are always allowlist-gated by the core.

Telegram realities handled (verified against the Bot API)

  • history: false — the Bot API has no history endpoint for bots, so fetchMessages returns [] and the core leans on its own message log. This is exactly the case discord.md anticipated for a history-less transport.
  • forum_topic_deleted doesn't exist — only created/closed/reopened are observable; a deleted topic just goes dormant and resumes on next message.
  • callback_data 64-byte cap — permission buttons carry a minted short token mapped back to the arbitrary-length request_id, not the id itself.
  • setMessageReaction fixed emoji set — reactions are normalized (✅→👍, ❌→👎, 🤖/⏳→👀, strips U+FE0F) and unreactable ones dropped rather than erroring.
  • Native commands have no typed args — everything after /cmd is free text → $ARGUMENTS; names are sanitized to [a-z0-9_]≤32 and mapped back to the core's original name. An unregistered /foo falls through to the agent as text.
  • Long polling (bot.start()) — no public URL/webhook needed for a self-hosted daemon.

Files

  • New patch: src/patches/telegram/{index,client,permissions,attachments}.ts
  • Wiring: TelegramConfig + telegram branch in core/config.ts and bin/patchbayd.ts; grammy dependency; .env.example block
  • Docs (per the repo's standing docs-in-sync rule): docs/telegram.md, a decisions.md entry, and the principles.md index row

Tests

src/patches/telegram/telegram.test.ts mirrors the Discord DM allowlist security regression, plus wrong-group rejection, conversation_id round-trip (incl. negative supergroup ids), reaction normalization, and text chunking.

  • bun run typecheck — clean
  • bun test53 pass, 0 fail (44 baseline + 9 new)

Not yet done

The live long-poll round-trip (real message → spawn → reply/buttons/attachments) needs a host with a real bot token; the one-time setup checklist is in docs/telegram.md.

🤖 Generated with Claude Code

Implements a Telegram transport adapter under src/patches/telegram/ behind the
existing Patch interface, with zero core changes beyond config wiring — the
transport-agnostic abstraction held.

Conversations map to private chats (DMs, allowlist-gated) and forum topics in one
configured supergroup (TELEGRAM_GROUP_ID, membership-gated like Discord's private
forum channel). conversation_id encodes the (chatId, threadId) pair the Bot API
needs to deliver: "<chatId>" for DM/General, "<chatId>:<threadId>" for a topic.

Transport specifics handled in the patch:
- grammY long polling (no public URL); history:false (Bot API has no history for bots)
- forum_topic_created/closed lifecycle (forum_topic_deleted doesn't exist in the API)
- permission Allow/Deny via inline keyboard; 64-byte callback_data limit worked around
  with a minted token -> request_id map
- reactions normalized into Telegram's fixed emoji set (✅→👍, ❌→👎, …)
- attachments via getFile + file endpoint, with file_id -> {name,mime} stash
- native commands (no typed args) sanitized to [a-z0-9_]≤32, mapped back to core names

Wiring: TelegramConfig + telegram branch in loadConfig/patchbayd; grammy dep;
.env.example block. Docs: docs/telegram.md, decisions.md entry, principles.md index.
Tests mirror the Discord DM allowlist regression plus id round-trip, reaction, and
chunking units.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant