a0-telegram/
├── plugin.yaml # Plugin manifest
├── default_config.yaml # Default settings
├── initialize.py # Dependency installer (aiohttp, pyyaml, python-telegram-bot)
├── hooks.py # Plugin lifecycle hooks (install/uninstall)
├── install.sh # Deployment script
├── .gitignore
├── helpers/
│ ├── __init__.py
│ ├── telegram_client.py # REST API client wrapper (aiohttp)
│ ├── telegram_bridge.py # Chat bridge bot (python-telegram-bot polling)
│ ├── sanitize.py # Prompt injection defense, input validation
│ ├── message_store.py # Persistent message storage (JSON)
│ └── poll_state.py # Background polling state
├── tools/
│ ├── telegram_read.py # Read messages, list chats, get chat info
│ ├── telegram_send.py # Send messages, photos, reactions, forward
│ ├── telegram_members.py # List group administrators
│ ├── telegram_summarize.py # LLM-powered conversation summaries
│ ├── telegram_manage.py # Pin/unpin, set title/description
│ └── telegram_chat.py # Chat bridge control
├── prompts/
│ ├── agent.system.tool.telegram_read.md
│ ├── agent.system.tool.telegram_send.md
│ ├── agent.system.tool.telegram_members.md
│ ├── agent.system.tool.telegram_summarize.md
│ ├── agent.system.tool.telegram_manage.md
│ └── agent.system.tool.telegram_chat.md
├── skills/
│ ├── telegram-research/SKILL.md
│ ├── telegram-communicate/SKILL.md
│ └── telegram-chat/SKILL.md
├── api/
│ ├── telegram_test.py # Connection test endpoint
│ ├── telegram_config_api.py # Custom actions (auth key generation)
│ └── telegram_bridge_api.py # Chat bridge start/stop/status
├── webui/
│ ├── main.html # Dashboard (status, bridge control)
│ └── config.html # Settings (Alpine.js x-model bindings)
├── extensions/
│ └── python/agent_init/_10_telegram_chat.py # Auto-start bridge
├── tests/
│ ├── regression_test.sh
│ ├── HUMAN_TEST_PLAN.md
│ ├── HUMAN_TEST_RESULTS.md
│ └── SECURITY_ASSESSMENT_RESULTS.md
└── docs/
├── README.md
├── QUICKSTART.md
├── SETUP.md
└── DEVELOPMENT.md
-
Start the dev container:
docker start agent-zero-dev
-
Install the plugin:
docker cp a0-telegram/. agent-zero-dev:/a0/usr/plugins/telegram/ docker exec agent-zero-dev bash /a0/usr/plugins/telegram/install.sh -
For iterative development (push changes without full reinstall):
docker cp a0-telegram/. agent-zero-dev:/a0/usr/plugins/telegram/ docker exec agent-zero-dev supervisorctl restart run_ui -
Run tests:
bash tests/regression_test.sh agent-zero-dev 50083
- Create
tools/telegram_<action>.pywith a Tool subclass - Create
prompts/agent.system.tool.telegram_<action>.mdwith JSON examples - Add API method to
helpers/telegram_client.pyif needed - Add import test and prompt test to
tests/regression_test.sh - Update
docs/README.mdtools table
from helpers.tool import Tool, Response
from plugins.telegram.helpers.telegram_client import TelegramClient, get_telegram_config
from plugins.telegram.helpers.sanitize import require_auth
class TelegramAction(Tool):
async def execute(self, **kwargs) -> Response:
config = get_telegram_config(self.agent)
try:
require_auth(config)
except ValueError as e:
return Response(message=f"Auth error: {e}", break_loop=False)
client = TelegramClient.from_config(agent=self.agent)
try:
# ... use client ...
result = await client.get_me()
finally:
await client.close()
return Response(message=str(result), break_loop=False)from plugins.telegram.helpers.telegram_client import get_telegram_config
# In a tool:
config = get_telegram_config(self.agent)
token = config.get("bot", {}).get("token", "")
bridge_config = config.get("chat_bridge", {})from helpers.api import ApiHandler, Request, Response
class TelegramMyApi(ApiHandler):
@classmethod
def get_methods(cls) -> list[str]:
return ["POST"]
@classmethod
def requires_csrf(cls) -> bool:
return True # MANDATORY — never return False
async def process(self, input: dict, request: Request) -> dict | Response:
action = input.get("action", "")
if action == "my_action":
return {"ok": True}
return {"error": "Unknown action"}from plugins.telegram.helpers.sanitize import (
sanitize_content,
sanitize_username,
require_auth,
generate_auth_key,
)
# Sanitize user input before passing to LLM
clean_text = sanitize_content(raw_text, config)
# Sanitize display names
safe_name = sanitize_username(raw_name)Config pages use A0's built-in plugin settings framework with Alpine.js x-model bindings. The config variable is provided by the parent scope — no custom load/save logic needed.
<html>
<head><title>Plugin Name</title></head>
<body>
<div x-data x-init="if (!config.section) config.section = {};">
<template x-if="config">
<div>
<div class="section-title">Section Name</div>
<div class="field">
<div class="field-label">
<div class="field-title">Field Title</div>
<div class="field-description">Help text.</div>
</div>
<div class="field-control">
<input type="text" x-model="config.section.field" />
</div>
</div>
</div>
</template>
</div>
</body>
</html>Dashboard pages use vanilla JS with data-tgm= prefixed attributes and window._tgMain namespace.
// Lazy fetchApi — resolves at call time, not at script init
function fetchApi(url, opts) { return (globalThis.fetchApi || fetch)(url, opts); }
// Use inline onclick handlers (not addEventListener) to survive component reloads
// <button onclick="window._tgMain.myAction()">
window._tgMain = { myAction: myAction };- Follow existing patterns from other published plugins (Discord, Signal, Bluesky)
- Use
async/awaitfor all I/O operations - Always close client connections in
try/finally - Return
Response(message=..., break_loop=False)from tools - Use
logging.getLogger()— never bareprint() - Use
self.set_progress()for long operations - Sanitize ALL external content before passing to LLM
- All API handlers must have
requires_csrf() -> True - WebUI main.html: use
data-tgm=attributes, not bare IDs - WebUI config.html: use Alpine.js
x-modelbindings (standard framework)
The plugin uses two different approaches for Telegram API access:
telegram_client.py— Lightweight REST wrapper usingaiohttpfor tool operations (read, send, manage). Simple, no polling, async-friendly.telegram_bridge.py— Usespython-telegram-botlibrary for the chat bridge. Provides the polling loop, message handlers, and bot lifecycle management needed for real-time operation.
Telegram enforces single-consumer semantics on getUpdates — only one caller can poll at a time per bot token. When the bridge is polling, tools fall back to reading from the persistent message_store.json instead of calling getUpdates directly. The is_bridge_polling() guard function coordinates this.
A0's infection_check monitors agent output for suspicious patterns. The chat bridge sends authenticated (elevated) messages as plain text through context.communicate() — the same path as WebUI messages — to avoid triggering false positives from prefixed message formats.
bash tests/regression_test.sh <container> <port>58+ automated tests covering container health, installation, imports, API endpoints, sanitization, tool loading, prompts, skills, WebUI, framework compatibility, and security.
Follow tests/HUMAN_TEST_PLAN.md for the 76-test manual verification covering all tools, bridge modes, authentication, security, and edge cases.
- Official docs: https://core.telegram.org/bots/api
- Key methods used: getMe, getUpdates, sendMessage, sendPhoto, getChat, getChatAdministrators, pinChatMessage, unpinChatMessage, setChatDescription, setChatTitle, forwardMessage, setMessageReaction
- Rate limits: 30 messages/second global, 1 message/second per chat, 20 messages/minute per group