Skip to main content
Glama
tg-agent (Telegram → SQLite → MCP)

tg-agent (Telegram → SQLite → MCP)

by WhiteBite

Telegram-группы и темы для агента в opencode

tests

Read-only связка: Telethon (твой аккаунт) → локальный SQLite → MCP-сервер → агент.

Главная идея — агент видит не всё подряд, а только то, что ты отметил сам. Тем может быть десятки, в каналах — годы переписки; вываливать это в контекст бессмысленно. Поэтому:

  1. ты выделяешь нужное (папка в Telegram или точечно темы через watch.py),

  2. это синхронизируется в локальный индекс (инкрементально, старая история — чанками),

  3. агент получает 16 read-only инструментов, которые знают твои алиасы и «что нового с прошлого раза».



Быстрый старт: одна команда

python3 start.py

Она сама решает, что нужно: нет ключей — спросит и запомнит; не залогинен — покажет QR; дальше открывает визуал в браузере, где всё остальное мышкой: 「+ из Telegram」 — список чатов прямо из Telegram (форум/группа/канал, пометка «в индексе»), фильтр, окно дней и кнопка «Слить»; ★ — отдать тему агенту. Ни флагов, ни имён чатов, ни запоминания команд.

Всё описанное ниже — те же шаги, но вручную, для тех, кому нужен точный контроль (в терминале есть и запасной выбор чатов: python3 start.py --cli).

Related MCP server: telegram-user-mcp

Как «визуально выделить нужное»

Вариант 0 — рабочее пространство в браузере

python3 ui.py                 # http://127.0.0.1:8899

Главный экран «Требует внимания» показывает просрочки, блокировки, открытые вопросы и непроверенные выводы. Разделы «Проекты», «Задачи», «Решения», «Вопросы» ведут к карточке с историей и сообщениями-доказательствами; «Загрузки» — раздел скачивания и пересылки медиа: задания, файлы, пересылки, подборки. Светлая тема по умолчанию, тёмная и системная доступны в настройках; мобильная компоновка — без постоянных трёх колонок переписки.

  • таблицы с фильтрами проекта, ответственного, статуса и срока, сортировкой и пагинацией;

  • единый поиск по сущностям и сообщениям (FTS5 с безопасным LIKE-фолбэком), фильтры периода/проекта/человека, история поиска и локальные сохранённые представления;

  • ручное исправление полей и проверка с аудитом; устаревшая версия карточки отклоняется с 409, новые события вновь требуют проверки;

  • изменения проекта используют event-id и отдельную явную отметку просмотра: она не меняет watch.last_read и не подтверждает правильность извлечения;

  • настройка привязки синхронизированных тем к проекту и ручной справочник людей;

  • раздел «Переписка»: дерево чатов/тем, watch, алиасы, закрепление, отметки прочтения, дата, FTS, загрузка старых сообщений и копирование выдержки для агента;

  • синхронизация и добавление чатов из Telegram запускаются только по нажатию, идущую синхронизацию можно отменить (POST /api/sync/cancel);

  • раздел 101 показывает только локальное состояние и запросы-кандидаты. Он не выполняет авторизацию, проверку сети или отправку и не двигает курсоры экспорта.

Ограничения: контракт полей задач 101 ещё не подтверждён. Наличие токена или локального GUID не означает работающую интеграцию. Отклонение записи человеком является локальной отметкой: экспортёр пока не использует её как запрет отправки. Настройки уведомлений позволяют посмотреть сводку без отправки; реальные получатели по-прежнему задаются в TG_NOTIFY_ROUTES на сервере.

Выбор пишется в ту же таблицу watch, которую читает MCP-сервер, — агент в opencode видит отметки сразу, без перегенерации конфигов. watch.py остаётся для терминала и скриптов.

Посмотреть без Telegram: python3 seed_demo.py demo.sqlite && TG_DB=demo.sqlite python3 ui.py.

Вариант A — папка в Telegram

В приложении: Settings → Folders → New folder, назови например AI Agent, положи туда нужные каналы и группы. Отмечать в коде ничего не нужно — что в папке, то и читает агент:

python3 sync.py --folder <id папки>     # id видно в веб-версии Telegram в URL папки
sync.py --dry-run --folder 7            # сначала посмотреть, что попадёт

Каналы-вещания из папки берутся автоматически (раз положил — значит нужно). Пополнять папку можно и без приложения — агенту достаточно попросить:

python3 folders.py list                                  # папки и их состав
python3 folders.py add --folder 15 --chat @ai_channel    # доложить чат в папку
python3 folders.py remove --folder 15 --chat @ai_channel

Создавать и настраивать сами чаты можно из терминала (реальные операции — только при TG_ALLOW_MANAGE=1):

python3 manage.py create --title "Моя группа"
python3 manage.py edit --chat -1002000001 --title "Новое"
python3 manage.py topic-create --chat @forum --title "Тема"

Релиз-посты и readme тем

python3 announce.py draft --repo ../tg-agent --tag v0.10.0 --product tg-agent --tpl-out post.yaml
python3 announce.py post --tpl post.yaml --chat @whitebite_devsoft --topic 323 --send --react 🔥 --pin
python3 announce.py readme --topic-title "🪡 Stitch · ядро" --chat @whitebite_devsoft --topic 325 --send

Пост собирается из YAML-шаблона: badge, title, what, inside (список), ps, warning, links, banner. Баннер рендерится PIL в 1080x720, если в шаблоне задан. Без --send любой режим — dry-run: печатает текст и сохраняет баннер, к Telegram не обращается. Реальная отправка (кросспост --also, реакция --react, пин --pin) только при TG_ALLOW_MANAGE=1. У MCP-сервера есть зерцало для агента — инструмент tg_manage_post, регистрируется за тем же гейтом и требует confirm=True.

Вариант B — точечно темы внутри одной группы

Когда группа одна (твоя AI), а тем в ней десятки:

python3 watch.py topics --chat @mygroup      # темы с цифрами: сколько сообщений, закрыта ли
python3 watch.py add --chat @mygroup --topic 123456 --alias "ML-новости" --pin
python3 watch.py add --chat @mygroup --topic 789   --alias "Релизы"
python3 watch.py list                        # что отмечено и сколько там непрочитанного
python3 watch.py suggest                     # топ-тем по активности + готовые команды add
python3 sync.py --watched                    # синхронизировать только отмеченное

Алиас — это то, что увидит агент вместо chat_id=-1001234567890 / topic_id=789.

Оба варианта совмещаются: папка даёт каналы, watch.py — темы внутри них.


Инструменты агента (все read-only)

Инструмент

Что делает

tg_watch

Первым делом. Список отмеченного: алиас, название темы, сколько всего сообщений, сколько новых, дата последнего

tg_new

Новые сообщения темы с момента прошлого прочтения. Основной для «что там нового»

tg_read

История темы: date_from/date_to (архивы), before_id+next_before (листание), budget_chars (защита контекста)

tg_find

Полнотекстовый поиск по всей истории, включая посты двухлетней давности (SQLite FTS5, релевантность через bm25)

tg_stats

Активность: разбивка по темам, топ авторов, динамика по дням, медиа

tg_mark_read

Отметить тему прочитанной (агент зовёт сам только после того, как показал разбор)

tg_index_stats

Что лежит в индексе и за какой период

tg_links

Что шерили: топ @упоминаний, t.me, инвайтов (с пометкой жива/истекла из кэша checklinks), URL и источников пересылок

tg_topics

Структура индекса: чаты, а по chat_id — его темы со счётчиками

tg_digests

Готовые выжимки новых сообщений (создаёт digest.py)

tg_entities

Извлечённые сущности (задачи, решения, обещания, вопросы): project, kind, status, owner, due_ts; фильтры pack/kind/status/project/owner/chat_id и query — подстрока в title/body

tg_entity

Одна сущность по id: все поля и история событий — какое поле менялось, когда и с каким доказательством из сообщения

tg_overdue

Открытые сущности со сроком: просроченные (days_ahead=0) или на горизонте N дней, по возрастанию due_ts

tg_changes

Сущности с событиями новее since_ts: что менялось после отметки, у каждой — события с цитатой-доказательством; фильтры pack/project

tg_project_brief

Сводка по проекту: counts по статусам, open, overdue, next_due, открытые вопросы, обещания без срока/ответственного, топ владельцев

tg_gaps

Пробелы по проекту или пакету: открытые вопросы без ответа, обещания без срока и без ответственного, с причиной попадания

Отправки нет вообще — пока не включён гейт: по умолчанию в server.py нет инструмента, который что-то пишет в Telegram. Пишущих модуля четыре, все вне MCP-сервера по умолчанию: notify.py (сообщения себе, за гейтом TG_ALLOW_SEND=1), folders.py (состав папок — add/remove чатов; контент не пишет), manage.py (создание и настройку чатов, участники, инвайт-ссылки; за гейтом TG_ALLOW_MANAGE=1) и modules/downloads.py (скачивание медиа на диск — гейт TG_ALLOW_DOWNLOADS=1; пересылка медиа в явно указанный чат — гейт TG_ALLOW_FORWARDS=1). MCP-инструменты управления чатами (tg_manage_*) регистрируются только при TG_ALLOW_MANAGE=1 и требуют confirm=True для каждой операции; tg_downloads_run/status/collections — при TG_ALLOW_DOWNLOADS=1, tg_downloads_forward — при TG_ALLOW_FORWARDS=1 (статус и подборки регистрируются при любом из двух гейтов), запуск задания и пересылка требуют confirm=True. tg_sql (произвольный read-only SQL) скрыт по умолчанию, включается TG_EXPOSE_SQL=1.


Диагностика

python3 doctor.py          # офлайн: библиотеки, креды, сессия, БД, FTS5, self-test маскировки
python3 doctor.py --live   # + реальное подключение к Telegram
python3 doctor.py --providers  # opt-in: одна мини-компляция к настроенному extract/digest-провайдеру (живой ключ; мёртвый — HTTP 401)
python3 doctor.py --quality    # заполненность owner/due/body по пакетам + гистограмма причин отказа из rejects_json

Гоняй перед первым sync.py и после любых «почему не работает».

Установка

python3 -m venv ~/.venvs/tg-agent
~/.venvs/tg-agent/bin/pip install -r requirements.txt   # telethon, mcp>=2, cryptg, qrcode

api_id / api_hash: https://my.telegram.org/apps (бесплатно).

Больше ничего руками делать не надо: login.py при первом запуске сам спросит ключи и запомнит их в .env (файл в .gitignore). Хочешь заранее — скопируй .env.example в .env и заполни: все скрипты (sync / login / live / watch / server / ui / digest / doctor) читают .env сами, а переменные окружения, если заданы, имеют приоритет. Кодировка файла не важна: UTF-8, UTF-8 с BOM (notepad) и UTF-16 (Out-File в Windows PowerShell) читаются одинаково.

Первый логин: один скан QR

python3 login.py

В терминале появится QR: Telegram → Настройки → Устройства → Подключить устройство — наведи камеру. Телефон и код вводить не нужно (код из Telegram приходит только тебе, обойти это нельзя — а QR убирает ввод целиком). Если на аккаунте включена двухэтапная проверка — спросит облачный пароль (до трёх попыток, ввод не отображается — следи за раскладкой); повторный запуск не требует нового скана: QR уже принят, спросит только пароль. Сессия ляжет в tg_agent_session.session и переиспользуется навсегда. Альтернативы: login.py --phone (телефон+код), login.py --export (напечатает TELEGRAM_SESSION_STRING для контейнеров).

Переменная TG_ENV_FILE управляет тем, что читается вместо .env (пустое значение — не читать .env вовсе; так тесты и CI остаются герметичными).

Синхронизация

python3 sync.py --dry-run                    # что найдётся, ничего не записывая
python3 sync.py --folder 7                   # всё из папки
python3 sync.py --watched                    # только watch-лист
python3 sync.py --chat @mygroup --days 30    # конкретная группа, окно 30 дней

Первый запуск попросит QR уже не должен — логинься заранее login.py; но и sync.py сам спросит телефон + код (или QR), если сессии нет. Сессия — в tg_agent_session.session: это полный доступ к аккаунту, chmod 600, не коммитить.

Каналы с историей в годы

Обычная синхронизация идёт сверху вниз и обрывается на первом известном сообщении — до конца архива так не дойти. Для архива:

python3 sync.py --chat @old_channel --backfill 3000

Бэкфилл идёт снизу вверх чанками по 3000, печатает прогресс (чанк 0->42: просмотрено 2, новых 2) и сохраняет курсор после каждого чанка, поэтому прерванный запуск продолжается с того же места. Для очень длинных архивов крути в фоне и добавь задержку (по умолчанию 0.5 c между чанками) — Telegram за агрессивный парсинг может ограничить аккаунт.

Повторный запуск любого варианта тянет только новое, а также подхватывает правки сообщений (edit_date → edited_ts; в отчёте — edited_messages). Для регулярности — cron раз в 15–60 минут: sync.py --watched.

Как определяются темы

Telegram хранит темы как треды: у сообщения внутри темы reply_to.forum_topic=True, id темы — в reply_to.reply_to_msg_id. Сообщения без reply_to в форуме относятся к General (id 1). Поэтому sync.py делает один проход по группе и раскладывает сообщения по темам сам: иначе General недосчитывается.

Список тем — через messages.GetForumTopicsRequest(peer, offset_date, offset_id, offset_topic, limit). Обёртки в Telethon нет, а в functions.channels этого метода нет вообще (там только ToggleForumRequest) — запрос делается напрямую.

Глобальный поиск: найти новые каналы по слову

python3 search.py "нейросети"                # каналы, группы и юзеры
python3 search.py "нейросети" --type channel # только каналы-вещания
python3 search.py "ИИ" --type group --limit 30

Обёртка над messages.SearchGlobalRequest — сервер Telegram сам ищет по названиям и содержимому публичных чатов (обычный API-запрос, не парсинг). Результат: тип, название, @username, подписчики; дальше sync.py --chat @username — и канал в индексе. Так закрывается сценарий «найди все каналы по ИИ», с которого начинается кураторство: поиск → выбор → синк → watch.

Агент может искать сам: при TG_ALLOW_LIVE=1 у MCP-сервера появляется инструмент tg_live_search (тоже только чтение) — агент находит каналы и предлагает тебе их синхронизировать, сам ничего не добавляет.

Проверка инвайт-ссылок: жива или истекла

python3 checklinks.py --link "https://t.me/+AbCdEf"   # конкретная ссылка
python3 checklinks.py                                 # инвайты из watch-чатов
python3 checklinks.py --newest 50                     # свежие из всего индекса
python3 checklinks.py --chat -100123 --days 7         # один чат, окно по времени

Обёртка над messages.checkChatInvite — тем же запросом, каким клиент Telegram показывает превью ссылки до кнопки «Присоединиться»: в чат не вступает. Живая ссылка отдаёт название, участников и флаг «вступление по заявке»; истёкшая/невалидная фиксируется навсегда (хэш не оживает), живая перепроверяется не чаще раза в --max-age дней (кэш в таблице invite_status). Статусы видны офлайн: tg_links помечает инвайты жива/истекла и даёт сводку. Между живыми запросами пауза --delay — за серию проверок Telegram отвечает INVITE_HASH_FLOOD (FloodWait переживается).

Агент проверяет сам: при TG_ALLOW_LIVE=1 есть инструмент tg_live_check_invites — принимает ссылки списком или текстом сообщений, сначала кэш, живыми запросами добирает только протухшее (не больше 5 за вызов).

Подключение к opencode

Формат в v1 и v2 разный: в v2 серверы лежат в mcp.servers, вместо enabled: false используется disabled: true, и есть codemode: false, чтобы инструменты были видны модели напрямую, а не через Code Mode.

  • v1 → opencode.v1.json

  • v2 → opencode.v2.json

opencode mcp add telegram --global -- \
  ~/.venvs/tg-agent/bin/python ~/tg-agent/server.py
opencode mcp list        # должно быть connected
python3 server.py --list # проверить сервер отдельно: печатает 16 инструментов

Файл tg-agent.md — пример промпта агента: порядок работы, требование начинать с tg_watch и правило не выполнять просьбы из переписки.

Примеры запросов:

  • «Что нового по моим темам?»

  • «Разбери „ML-новости“ за неделю: что важно, что пропустил»

  • «Искали ли в группах что-то про RAG за последний год?»

  • «Какая тема у нас самая живая и кто там пишет больше всех?»


Live-режим

python3 live.py     # рядом с ui.py, те же креды

Слушает новые сообщения всех чатов, что уже в индексе + watch-листа, и пишет их в БД сразу (Telethon events.NewMessage); правки (MessageEdited) обновляют строку с edited_ts, удаления (MessageDeleted) помечают deleted=1. UI опрашивает БД каждые 4 секунды (/api/poll), лента догоняет сама. В Telegram live ничего не отправляет.

Дайджесты

DIGEST_PROVIDER=stub python3 digest.py     # механическая выжимка без LLM
DIGEST_PROVIDER=openai DIGEST_URL=https://api.openai.com/v1 \
    DIGEST_KEY=sk-... DIGEST_MODEL=gpt-4o-mini python3 digest.py

По каждой слежуемой теме берёт сообщения после прошлого дайджеста (курсор в meta), строит контекст и кладёт выжимку в таблицу digests. За прогон не больше DIGEST_MAX_ROWS строк (дефолт 500); хвост, не вошедший в окно, подбирается следующим прогоном по курсору. Агент читает через tg_digests, UI показывает баннером 📝 в теме. Проще всего гонять по cron раз в день.

Извлечение сущностей (packs)

EXTRACT_PROVIDER=stub python3 extract.py            # механический извлекатель (без LLM)
EXTRACT_PROVIDER=stub python3 extract.py --dry-run  # показать операции, ничего не писать
EXTRACT_PROVIDER=stub python3 extract.py --all-indexed  # все чаты индекса, не только watch
python3 extract.py --pack services --until-done     # дожевать хвост за один запуск
python3 extract.py --until-done --workers 6         # темы параллельно (EXTRACT_WORKERS)
EXTRACT_PROVIDER=openai EXTRACT_URL=https://api.openai.com/v1 \
    EXTRACT_KEY=sk-... EXTRACT_MODEL=gpt-4o-mini python3 extract.py --pack construction

Сообщения остаются неизменяемым источником истины; сущности — пересчитываемая проекция: удалил и перегенерил заново, ничего не потеряно. Движок extract.py generic, предметная область описана декларативным пакетом в packs/; пакет задаёт kinds, prompt-шаблон, окно первого прогона (backfill_days) и scope. Пакеты: construction (PM для стройки: decision / task / promise / question) и services (сервисы и триалы: service / trial / promo; ключ дедупа — dedup_hint: домен или слаг сервиса, поэтому «Триал 30 дней на Acme» и «Acme Cloud trial» схлопываются в одну сущность). Дедуп собирает один сервис из разных чатов в одну сущность с несколькими источниками (entity_sources); слияние пишется в entity_events (op='merge'), редирект старого id — в entity_aliases, tg_entity резолвит его автоматически. Область дедупа задаёт поле dedup_scope: у construction он по проекту, у services остаётся глобальным. services прицелен в темы watch-листа с проектом «сервисы» (watch.py add --project сервисы) — не шумит в код-чатах; scope меняется в packs/services.py. В packs/construction.py — scope_filter.require_match с exclude_project_regex: "^сервисы$": констракшн требует непустой проект и не запускается на проекте «сервисы», скоупы изолированы. --until-done крутит раунды (по 200 сообщений на тему), пока хвост истории не кончится; --workers N (или EXTRACT_WORKERS) дожёвывает темы параллельно — разовая накачка базы на десятки тысяч сообщений вместо часов занимает минуты-десятки минут, а каждый скоуп коммитится независимо. Один огромный скоуп делится на окна msg_id и жуётся тем же пулом параллельно (EXTRACT_WINDOW_MIN, дефолт 2000 сообщений; --windows N): план и sub-курсоры окон живут в meta, главный курсор продвигается одной транзакцией, когда все окна дожёваны, — прерванный прогон продолжается с того же места.

По умолчанию область прогона — watch-лист; --all-indexed расширяет на все пары (chat, topic) индекса — для массовой обработки «кучи групп», без курирования. По каждой паре (пакет, тема) курсор extract:<chat>:<topic>:<pack> в meta; за один прогон не больше 200 сообщений, остаток подберёт следующий. Курсор после прогона всегда выставляется на максимальный msg_id обработанной пачки (поле covered_upto_msg_id в операциях провайдера убрано; clamp по overflow оставлен). Провайдер отвечает JSON-операциями new/update/close/reopen; каждая обязана нести evidence — цитаты сообщений (minItems: 1). Цитата проверяется как подстрока текста сохранённого сообщения (плюс транскрипты медиа); операция без валидного evidence отклоняется, причины отказа пишутся в extraction_runs.rejects_json (не больше 50 за прогон), нормализация цитаты сводит ё→е. Поле confidence из схем пакетов убрано (колонка осталась, не заполняется). Операции применяются одной транзакцией (entities, entity_events, extraction_runs), сбой провайдера курсор не двигает. Срок из текста: если провайдер не дал due_ts, детерминированный fallback в extract.py разбирает due_text — абсолютные даты (20.10.2026, 20.10.26), русские названия месяцев («19 августа»), «N числа», относительные («сегодня»/«завтра»/«послезавтра», «через N дней/недель/месяц») и ISO-даты — всё резолвится по дате сообщения; срок ставится на конец дня (23:59:59 UTC), а не на полночь. Update без явного due_ts больше не затирает ранее выставленный точный срок. supersedes: решение new/update может сослаться на открытую сущность того же scope — цель закрывается, в entity_events пишется строка op='supersede'. Валидных операций больше 50 за прогон: применяются первые по позиции evidence, остаток фиксируется как ops_overflow (status=partial), а курсор остаётся до первой отброшенной — следующий прогон дочитает без потерь. Батч, отклонённый контент-фильтром провайдера (data_inspection_failed), бисектится до сообщения-триггера; триггер пропускается с фиксацией run status='filtered', остальные сообщения обрабатываются штатно. Сетевой сбой ретраит батч до 3 раз (интервал EXTRACT_NET_RETRY_DELAY, по умолчанию 15с); сломанная область завершает прогон ненулевым кодом — незавершённый хвост виден сразу, курсоры позволяют перезапустить с места. Отредактированные и удалённые сообщения перепрогоняются: id пишутся в meta-ключ dirty:{chat}:{topic}, следующий прогон темы их снимает и переизвлекает по ним.

Теги проектов: python3 watch.py add ... --project "Объекд" или python3 watch.py project --chat @mygroup --topic 789 --project "Объекд". Проект у строки watch — сущности наследуют его от темы.

Автономный конвейер

python3 pipeline.py                  # sync -> media -> extract -> fuzzy -> export -> digest -> monitor
python3 pipeline.py --pack services  # один пакет извлечения
python3 pipeline.py --skip sync      # офлайн-шаги, без Telegram (--skip media/fuzzy/export тоже можно)
python3 pipeline.py --days-ahead 7 --send
python3 pipeline.py --loop 30 --send # демон: полный цикл каждые 30 минут

Одна команда для автономного режима: шаги независимы, сбой одного не останавливает остальные (упавший шаг — в отчёте и в exit code 1); каждый шаг зажат по времени PIPELINE_STEP_BUDGET (дефолт 900с) — зависший шаг помечается упавшим, конвейер не виснет; extract вне --loop дожёвывает хвост до конца, в --loop — по окну за цикл (курсоры продолжают следующий). run() захватывает локи proclock, поэтому разовый запуск не гоняется параллельно с loop-демоном (лок занят — выход 0 «уже запущен»). В отчёте report["extract"] — сводные ops_rejected/ops_overflow. После извлечения fuzzy сливает дубли сущностей по нормализованному title (FUZZY_MAX_MERGES, дефолт 500), а export дописывает JSONL-фид для внешних потребителей (по умолчанию export.jsonl рядом с базой, путь — TG_EXPORT_PATH; формат строки {"entity", "events", "exported_ts"}, курсор в integrations). Регулярность — --loop N (демон, cron не нужен) или cron; live.py остаётся отдельным демоном для мгновенной подхватки новых сообщений.

Автономка (Windows)

На машине настроены три компонента:

Компонент

Роль

Запуск

pipeline.py --loop 30

полный цикл каждые 30 минут (sync → media → extract → fuzzy → export → digest → monitor)

при входе в систему: tg-agent.cmd в папке Startup

live.py

мгновенная подхватка новых сообщений в индекс

тот же .cmd

задача tg-agent-morning (09:00)

утренняя сводка в Saved Messages (pipeline.py --skip sync --skip media --send)

Task Scheduler

Каждый демон получает собственную копию Telethon-сессии (tg_agent_session_cli, tg_agent_session_live, tg_agent_session_morning): два клиента на одном session-файле конфликтуют через его SQLite (database is locked). Копии делаются обычным копированием файла авторизованной сессии — auth-ключ один, локальные базы разные.

Двойной старт блокирует proclock (байтовая блокировка <db>.pipeline.lock / <db>.live.lock; при смерти процесса ОС снимает её сама — протухших файлов не остаётся). Логи дописываются в pipeline.log / live.log / morning.log (в gitignore). Утренней сводке sync не нужен — loop-демон и так держит данные свежими, а Telegram-API не конкурирует. Задаче утренней сводки нужен TG_MONITOR_FORCE=1: иначе дедуп monitor по хэшу отчёта глушит повторную отправку в тот же день. Остановка: снять задачи Get-CimInstance Win32_Process -Filter "Name='python.exe'" и убить PID'ы демонов; для отключения автозапуска — удалить tg-agent.cmd из Startup и schtasks /delete /tn tg-agent-morning /f.

Мониторинг и уведомления

python3 monitor.py                       # dry-run: сводка по проектам и дедлайны
python3 monitor.py --days-ahead 7        # добавить задачи на неделю вперёд
python3 monitor.py --project "Объекд"    # один проект
python3 monitor.py --news-hours 24       # окно блока «новое» (по умолчанию сутки)
python3 monitor.py --send                # отправить сводку в Saved Messages

Считает по entities: просроченные — открытые сущности со сроком, чей due_ts в прошлом; --days-ahead N дополнительно достаёт задачи до N дней включительно. Сводка начинается с блока «новое за Nч» — свежесозданные сущности всех пакетов и кросс-чатные повторы (события merge/dedup — сервис снова всплыл в другом чате) со временем появления: утренний pipeline.py --loop 1440 --send превращается в ежедневную сводку о триалах. Есть блок «зависшие вопросы» — открытые question старше MONITOR_QUESTION_DAYS (дефолт 3). Сканы ограничены по объёму. --send дедупится по хэшу отчёта (meta['monitor:last_hash']): одинаковая сводка второй раз не отправляется, TG_MONITOR_FORCE=1 обходит проверку. --send — это notify.py:

python3 notify.py "текст"                # dry-run: показать, что ушло бы
TG_ALLOW_SEND=1 python3 notify.py --send "текст"
echo "текст" | TG_ALLOW_SEND=1 python3 notify.py --send   # из stdin, построчно

notify.py — модуль репозитория, который умеет писать сообщения в Telegram (в свои Saved Messages). Реальная отправка только при TG_ALLOW_SEND=1, по умолчанию везде dry-run; TG_NOTIFY_CAP (по умолчанию 10) ограничивает число сообщений за вызов. Отправка идёт в worker-потоке на собственном event loop, поэтому работает и изнутри уже запущенного цикла (pipeline --send раньше падал с asyncio.run() cannot be called from a running event loop). MCP-сервер notify.py не импортирует — агент отправки по-прежнему не видит.

Медиа-транскрипция

MEDIA_PROVIDER=stub python3 media.py                    # заглушка без сети
MEDIA_PROVIDER=openai python3 media.py --limit 50       # скачивание + транскрипция
python3 media.py --sweep-pending                        # разовый дренаж легаси-pending -> skipped, без скачивания

sync.py и live.py ставят в очередь только транскрибируемое аудио — голосовые и аудио-документы (гейт media.is_transcribable, обычные документы в очередь больше не попадают); воркер рефетчит сообщение из Telegram по id (файловая ссылка живёт только на свежем объекте), классифицирует аудио по атрибутам (DocumentAttributeVoice/Audio, mime audio/* как фолбэк) и до скачивания отсекает не-аудио, файлы крупнее 10MB и длиннее 5 минут статусом skipped; удалённые сообщения — missing, сбой провайдера — failed. openai-провайдер — DashScope qwen3-asr-flash через chat/completions: аудио инлайнится base64 data-URL, asr_options идёт верхним уровнем (MEDIA_URL/MEDIA_KEY/MEDIA_MODEL/MEDIA_TIMEOUT; ключ — список через запятую с ротацией на 401/403). MEDIA_LIMIT (дефолт 25) ограничивает ассеты за прогон, --limit/--kinds — то же в CLI. Скачивание файла реализовано (Telethon download_media в _run_with_client); если байтов аудио нет, openai-провайдер отвечает ValueError. Готовые транскрипции extract.py подмешивает в промпт извлечения (и evidence-проверка учитывает их текст). pipeline.py гоняет шаг автоматически после sync. --sweep-pending — разовый дренаж строк pending, накопившихся до гейта is_transcribable: одним UPDATE они помечаются skipped и не скачиваются.

Скачивание видео и медиа-библиотека

python3 downloads.py plan --sources watch --days 90   # задание без запуска
python3 downloads.py run --sources watch              # создать и выполнить
python3 downloads.py run --job 3 --retry --force      # продолжить/дожать
python3 downloads.py run --sources watch --mode forward --to saved
python3 downloads.py status [--job 3]                 # задания и библиотека
python3 downloads.py files [--chat -100123]           # файлы с числом источников
python3 downloads.py forwards [--chat -100123]        # пересланные медиа
python3 downloads.py collections save Видео --chats -100111,-100222
python3 downloads.py job pause|resume|cancel 3

Скачивает видео (по желанию videonote и photo) из отмеченных каналов, папки, подборки, всех чатов индекса или точечного чата. Источники — спека через запятую: watch | all | folder:<id> | collection:<имя> | chat:<id|@username>. Подборка (collections) — сохранённая группа каналов для повторяемых заданий; watch-лист и доступ агента она не меняет.

Дедупликация точных дублей — два слоя. До скачивания — по id файла Telegram: пересланный в другой чат ролик не качается повторно. После скачивания — по sha256 содержимого: тот же ролик, залитый заново, остаётся одной копией, а второй id файла алиасится на уже скачанный файл. Хранение контентно-адресное: downloads/files/<sha[:2]>/<sha><ext> — одна строка media_files на уникальное содержимое, у файла накапливаются все сообщения-источники (media_file_sources). Повторное задание по умолчанию не включает уже скачанные сообщения.

Задание — снимок списка сообщений на момент создания (таблицы jobs / job_items), поэтому прерванный прогон продолжается с места остановки, а не начинается заново; --retry дожимает упавшие пункты, pause/resume/ cancel управляют ходом. Один пункт не обрывает пакет: причины пропуска (duplicate, duplicate-content, too-big, missing, kind, chat-unavailable) и падения — в отчёте задания. Лимит размера — TG_DOWNLOADS_MAX_MB (дефолт 2000, 0 — без лимита), каталог библиотеки — TG_DOWNLOADS_DIR (дефолт downloads/ рядом с репо).

Режим forward — пересылка без скачивания. --mode forward --to saved (или @username / числовой id своего чата) пересылает сообщения серверно: байты медиа через клиент не идут, одна пересылка — один API-вызов. Дедуп остаётся однослойным, по id файла Telegram и адресу (forward_files / forward_sources): тот же файл из любого канала повторно не пересылается, у записи накапливаются все сообщения-источники. Контентный sha256-дедуп здесь невозможен — перезалитая копия с другим id пройдёт как новое видео. Ключ реестра (doc_id, адресат): смена адресата — свои пересылки заново (в план берутся через --all-messages). Длинный FloodWait (>120 с) уводит задание в pause, короткий пережидается молча; каналы с запретом пересылки пропускаются (forwards-restricted). Пересылка — операция записи в Telegram, поэтому MCP-инструмент tg_downloads_forward живёт за отдельным гейтом TG_ALLOW_FORWARDS=1 и требует confirm=True; HTTP-маршрут — POST downloads/forward, список — GET downloads/forwards.

Раздел «Загрузки» в рабочем пространстве. ui.py показывает реестр заданий с прогрессом и детали задания (параметры, счётчики, причины пропусков, упавшие пункты) с кнопками «Запустить/Пауза/Продолжить/ Отменить/Дожать упавшие» и автообновлением, пока задание активно; форма нового задания (источники-спека с быстрыми подстановками и подборками, режим скачивание/пересылка, типы медиа, окно дней, лимит МБ) создаёт и запускает прогон, списки файлов и пересылок фильтруются по чату, подборки создаются и удаляются. UI использует те же маршруты downloads/*, что CLI и MCP.

Это первый модуль нового контракта: явный реестр modules/ (dict: id, version, schema, workspace-маршруты, MCP-спеки, runner). Ядро подключает модуль в трёх точках — db.py (миграции v7/v8 создают таблицы модуля), workspace_api.py (маршруты downloads/* в UI API) и server.py (MCP-инструменты за гейтом; у каждой спеки опциональный gate: downloads, forwards, any). Общая инфраструктура — jobs.py (сохраняемые задания) и sources.py (выбор источников + подборки): следующий модуль переиспользует их без правки ядра. TG_MODULES_OFF="downloads" убирает модуль целиком — ни маршрутов, ни инструментов, ни таблиц.

Похожие, но не идентичные ролики (пережатые, обрезанные, с вотермаркой) — кандидаты на слияние для человеческого решения: автоматически не удаляются и не сливаются, это следующий шаг модуля.

Экспорт

python3 export.py                                  # JSONL рядом с базой
python3 export.py --system file --out /tmp/export.jsonl
python3 export.py --system 101                     # POST в 101 (нужны EXPORT_101_URL/TOKEN)
python3 export.py --system notion                  # структурированная ошибка: нет API-доступа

Выгружает сущности и их события во внешние системы. Путь file-провайдера: --out, иначе TG_EXPORT_PATH, иначе export.jsonl рядом с базой. Курсор каждого провайдера живёт в таблице integrations (entity_id, system): выгружаются только сущности с событиями свежее курсора; сущности без событий не выгружаются никогда. Курсор 101 — по entity_events.id, а не по вторичному ts (события в одну секунду раньше молча терялись). 101 — реальный REST-провайдер (stdlib urllib): EXPORT_101_URL (https://app.101-group.ru), auth — EXPORT_101_TOKEN либо EXPORT_101_LOGIN + EXPORT_101_PASSWORD (вход /api/auth/sign-in/, refresh пары на 401 через /api/auth/refresh-token/). Сущности kinds из EXPORT_101_KINDS (дефолт task,promise) уходят задачами в EXPORT_101_PATH (дефолт /api/tasks/v2/): create — POST, при наличии integrations.external_id — PATCH .../{guid}/; EXPORT_101_COMMENTS=1 пишет событие комментарием; переименование полей под контракт 101 — EXPORT_101_MAP ({"title","body","due_ts"} → поля 101); EXPORT_101_DRY=1 — показать, что уедет, без отправки (курсор не двигается). 5xx/сеть ретраятся (EXPORT_101_RETRIES, EXPORT_101_BACKOFF), 4xx не ретраится; упавшая сущность не обрывает прогон (уходит в failed), external_id из ответа (guid) пишется в integrations.external_id, курсор — только после 2xx; без кредов — структурированный {"ok": false, "error": ...} без исключения. Тела задач в сваггере 101 нет — дефолтный EXPORT_101_MAP консервативен и настраивается. notion остаётся заглушкой со словарём-ошибкой.

Безопасность

  • Отправить сообщение агент не может — по умолчанию таких инструментов нет. Пишущие модули живут вне MCP-сервера: notify.py (сообщения в Saved Messages, гейт TG_ALLOW_SEND=1), folders.py (состав папок — только membership, без контента; правка = явный запуск команды), manage.py (создание и настройка чатов, участники, инвайт-ссылки; гейт TG_ALLOW_MANAGE=1) и downloads-модуль (скачивание медиа на диск — гейт TG_ALLOW_DOWNLOADS=1; пересылка медиа в явно указанный чат — гейт TG_ALLOW_FORWARDS=1). MCP-инструменты управления чатами (tg_manage_*) регистрируются только при TG_ALLOW_MANAGE=1 и требуют confirm=True для каждой операции; tg_downloads_run/status/collections — при TG_ALLOW_DOWNLOADS=1, tg_downloads_forward — при TG_ALLOW_FORWARDS=1; запуск задания и пересылка требуют confirm=True.

  • Сообщения — источник истины, правки подхватываются. Строка отражает последнее состояние текста (edited_ts); предыдущие версии правок не сохраняются. Удаление — флаг deleted=1: строка остаётся в базе, но читалки (история, tg_new, поиск, _around) её отдают с фильтром deleted=0, а mark_deleted вычищает строку из FTS — удалённое не ищется, отредактированное ищется по новому тексту (триггер FTS — AFTER UPDATE OF text). Сущности — пересчитываемая проекция: каждая обязана цитатами из сохранённых сообщений (проверка на подстроку), без валидного evidence операция не применяется; цитата в entity_events может ссылаться на текст до правки.

  • Секреты маскируются на инджесте (redact.py): API-ключи (openai/github/ groq/slack/nvidia), бот-токены, JWT, приватные ключи и «пароль: …»-паттерны заменяются на [скрыто: …] до записи в индекс — агент сырые креды не видит. Чтение (LIKE-фолбэк, UI, tg_sql, дайджесты) маскирует ещё раз — на случай баз, синхронизированных до появления маскировки.

  • tg_sql только чтение, по-настоящему: префикс SELECT/WITH, плюс отдельное соединение с PRAGMA query_only и authorizer, который разрешает только чтение — ATTACH, PRAGMA, записи и транзакции отклоняются, несколько запросов за раз SQLite не пропустит. Слова вроде update внутри строковых литералов поисковых запросов больше не блокируются. По умолчанию инструмент выключен.

  • Session-файл = аккаунт. 600, вне git. Если страшно — отдельный аккаунт для чтения.

  • Prompt injection. Чужой текст — данные, не инструкции; это сказано модели в instructions сервера и в промпте агента. Не включай TG_ALLOW_LIVE там, где это критично.

  • Индекс — твоя переписка в открытом SQLite. Держи локально.

  • Дайджесты от LLM — текст модели о чужих сообщениях; в инструменте tg_digests это помечено как данные, не команды.

  • TG_STRICT_WATCH=1 — жёсткий гейт: tg_read/tg_find/tg_stats/ tg_links/tg_topics работают только по темам из watch-листа, на остальное агент получает access_denied с подсказкой попросить тебя отметить тему. Сущностные инструменты (tg_entities/tg_entity/tg_overdue/tg_changes/ tg_gaps) гейтятся так же, но merged-сущность видна, если в watch-листе любой из её исходных чатов (entity_sources); её события могут цитировать другие участвовавшие в слиянии чаты.

  • UI с наружу — только с токеном. При HOST=0.0.0.0 UI вообще не стартует без TG_UI_TOKEN. Локальный режим дополнительно закрыт от DNS-rebinding (проверка Host) и CSRF (POST принимается только с Content-Type: application/json), токен сверяется constant-time.

  • FloodWait переживается автоматически: sync/backfill спят указанное Telegram время и повторяют (до 3 раз); SQLite в WAL с busy_timeout, поэтому live.py, sync.py и UI могут работать одновременно.

Структура репозитория

Файл

Роль

sync.py

Telethon → SQLite: инкрементальная синхронизация, бэкфилл чанками, папки, пины, подхват правок (edited_ts)

search.py

Глобальный поиск Telegram: публичные каналы/группы/юзеры по ключевому слову

checklinks.py

Статус инвайт-ссылок (checkChatInvite без вступления): CLI + кэш invite_status, терминальные expired/invalid, перепроверка живых не чаще раза в N дней, FloodWait-пacing

folders.py

Правка состава папок Telegram: add/remove чатов (пишущий модуль, контент не пишет)

manage.py

Создание и управление чатами: группы, каналы, форумы, настройки, участники (гейт TG_ALLOW_MANAGE=1)

announce.py

Релиз-посты: баннер PIL, шаблон YAML, readme тем из org.yaml, черновик из CHANGELOG; гейт TG_ALLOW_MANAGE=1

org.yaml

Реестр продуктов WhiteBite: репо, ссылки, эмодзи, разметка тем

pipeline.py

Автономный конвейер: sync → media → extract → fuzzy → export → digest → monitor одним прогоном; бюджет шага, proclock-лок, ops-сводка в отчёте

db.py

Индекс: схема с миграциями по PRAGMA user_version, FTS5, watch-лист, refs, пины, дайджесты, правки/удаления сообщений (update_message / mark_deleted, удалённые скрыты от читалок и FTS), read-only соединение (DB(path, readonly=True)), каскад проекта в watch_set_project, run_sql с блокировками

server.py

MCP-сервер (stdio): 16 read-only инструментов; tg_mark_read отклоняет id выше максимума индекса темы

live.py

Новые сообщения в реальном времени (events.NewMessage), правки и удаления (MessageEdited / MessageDeleted)

digest.py

Дайджесты: stub или openai-совместимый LLM

extract.py

Извлечение сущностей по пакетам из packs/: stub или openai-LLM, evidence-гейт, due_text→due_ts fallback (ru-месяцы, относительные, конец дня), supersedes, причины отказа в rejects_json, перепрогон dirty

packs/

Декларативные пакеты извлечения: construction (decision/task/promise/question), services (service/trial/promo); scope-фильтры (require_match, exclude_project_regex) и dedup_scope

monitor.py

Сводка просроченного и дедлайнов по проектам, зависшие вопросы; --send через notify.py, дедуп по хэшу отчёта

notify.py

Уведомления в Saved Messages; пишущий модуль, гейт TG_ALLOW_SEND=1

media.py

Очередь транскрипции аудио (voice + audio-документы): гейт is_transcribable на инджесте, рефетч и скачивание из Telegram, гейты до скачивания, --sweep-pending, qwen3-asr-flash (stub/openai)

modules/

Реестр функциональных модулей (контракт: id, version, schema, workspace-маршруты, MCP-спеки, runner; TG_MODULES_OFF); modules/downloads.py — скачивание медиа с дедупом

downloads.py

CLI модуля скачивания: план, прогон, статус, файлы, подборки, управление заданием

jobs.py

Сохраняемые задания: jobs/job_items, claim с защитой от гонок, pause/resume/cancel, retry упавших, отчёт по пунктам

sources.py

Выбор источников: watch/all/folder:/collection:/chat: (объединение через запятую) и подборки каналов

export.py

Выгрузка сущностей во внешние системы (file / 101 — REST-задачи с JWT sign-in+refresh, id-курсор, ретраи, upsert по guid; notion — заглушка-ошибка)

services_report.py

Каталог сервисов из сущностей: rollup по dedup_key, --days/--query/--top/--hide-closed

fuzzy_dedup.py

Maintenance-проход: слияние дублей сущностей по нормализованному title (richer-keeper, cap, идемпотентный)

proclock.py

Lockfile одиночного инстанса для демонов (pipeline --loop, live.py)

ui.py + ui.html + ui_assets/

Рабочее пространство в браузере: «Требует внимания», сущности, «Переписка», «Загрузки»; раздаёт /assets/* и /api/workspace/* за общим токен-гейтом, синхронизацию можно отменить

workspace_store.py / workspace_views.py / workspace_write.py / workspace_search.py / workspace_api.py

Бэкенд рабочего пространства: фильтры/сортировка/пагинация, поиск FTS5+LIKE-фоллбек, правка полей с аудитом (409 на устаревшую ревизию), проверка карточек, курсор просмотра проекта, привязка тем, справочник людей, локальные превью 101 и уведомлений

DESIGN.md

Дизайн-спека рабочего пространства: палитра, типографика, сетка, светлая/тёмная темы

watch.py

То же в терминале; --project и подкоманда project — тег проекта у темы

login.py

Логин QR-кодом (или телефон+код), --export — строка сессии

env.py

Чтение .env всеми скриптами (env-переменные приоритетнее)

redact.py / sanitize.py

Маскировка секретов / чистка bidi-инъекций (sanitize — из chigwell, Apache-2.0)

doctor.py

Диагностика окружения; --providers (opt-in health-check провайдера), --quality (заполненность полей и гистограмма отказов), --cost (расход токенов по extraction_runs)

seed_demo.py

Демо-база без Telegram: python3 seed_demo.py demo.sqlite

test_offline.py

1275 офлайн-проверок (без аккаунта Telegram) — свои проверки плюс пятнадцать suites ниже подпроцессами, гоняются в CI; своя temp-база через tempfile.mkdtemp

tests_index_media.py

правки/удаления сообщений в индекс, гейт медиа-очереди (запускается из test_offline.py)

tests_packs_semantics.py

пак: срок из due_text, no-downgrade, изоляция по проекту, supersedes, схемы пакетов (запускается из test_offline.py)

tests_export_101.py

HTTP-экспорт 101 против локального фейк-сервера (запускается из test_offline.py)

tests_ops_report.py

операционная автономка: notify в живом loop, бюджет шагов, lock, дедуп сводки, doctor --quality (запускается из test_offline.py)

tests_media_ingest.py

sweep pending + граница tg_mark_read (запускается из test_offline.py)

tests_db_core.py

user_version-миграции, read-only, deleted/FTS, dirty, каскад проекта (запускается из test_offline.py)

tests_readers.py

читалки: изменения с прошлого раза, бриф проекта, пробелы, фильтр owner (запускается из test_offline.py)

tests_pack_schema.py

валидация схем паков при загрузке (запускается из test_offline.py)

tests_watchdog.py

свежесть прогона конвейера (pipeline:last_ok_ts) (запускается из test_offline.py)

tests_ui_projects.py

/api/projects и курсор визита проекта в UI (запускается из test_offline.py)

tests_manage.py

Офлайн-тесты manage.py: создание, настройки, темы, участники, MCP (запускается из test_offline.py)

tests_workspace_api.py

HTTP-тесты workspace-API на реальном сервере: фильтры, 409-ревизии, 401/415, отмена синхронизации, секреты не утекают, превью 101 без сети (запускается из test_offline.py)

tests_question_signals.py

сигналы по вопросам: ответ/реакции не закрывают вопрос (запускается из test_offline.py)

tests_announce.py

Офлайн-тесты announce.py: рендер баннера, сборка текста, post с отправкой на фейк-клиенте, readme из org.yaml, draft из CHANGELOG, гейт (запускается из test_offline.py)

tests_downloads.py

Офлайн-тесты downloads-модуля: дедуп doc-id/sha256 + алиасы, forward-режим (дедуп по файлу и адресату, FloodWait-пауза, запрет пересылки), resume, пауза/отмена, подборки, реестр и TG_MODULES_OFF, MCP-гейты TG_ALLOW_DOWNLOADS/TG_ALLOW_FORWARDS, фоновый загрузчик (запускается из test_offline.py)

opencode.v1.json / opencode.v2.json

Готовые конфиги подключения к opencode

tg-agent.md

Памятка-промпт для агента

Лицензия — MIT (LICENSE); атрибуция заимствований — там же и в шапках файлов. *.sqlite, *.session* и .env в .gitignore — в репозиторий не попадают.

Альтернативы: готовые MCP

Если не хочется своего кода:

Проект

Темы

Комментарий

chigwell/telegram-mcp (~1.8k★)

✅ list_topics, get_history(topic_id=...)

Самый полный: мультиаккаунт, Docker, TELEGRAM_EXPOSED_TOOLS=read-only, транскрипция голосовых. Минус — 100+ инструментов в контексте и нет watch-листа

m0n0x41d/telegram-mcp

❌

7 инструментов, read_messages_since для мониторинга, uvx

DmitryKhali/telegram-mcp

❌

«суммаризируй чат», отправка только через confirm=true

⚠️ Не ставь pip install telegram-mcp / uvx telegram-mcp — в PyPI это другой проект; передав ему API_HASH и session-string, отдашь доступ к аккаунту постороннему коду. Только из git по явному URL.

Про клиентов

Под капотом всех MCP — обычные user-клиенты MTProto: Telethon (Python, здесь), GramJS (JS/TS), TDLib (C++, удобнее для очень больших дампов), opentele (конвертация tdata Telegram Desktop в Telethon-сессию). Разница только в языке: данные MTProto отдаёт одни и те же.


Майнинг готовых MCP: что взято

Склонированы и прочитаны четыре репы; взято только то, что ложится в нашу архитектуру без чужих рантайм-зависимостей:

Откуда

Что

Статус

chigwell/telegram-mcp

sanitize.py целиком (prompt-injection защита выдачи)

✅ взято как есть, применяется ко всей выдаче сервера и UI

chigwell/telegram-mcp

паттерн пинов (get_messages(filter=InputMessagesFilterPinned) + фолбэк)

✅ портирован в sync.fetch_pins, таблица pins, баннер в UI и pinned в tg_watch

0xMRK95/telegram-mcp-server

regexp extract_refs (@упоминания, t.me, инвайты, форварды)

✅ портирован: таблица refs на инджесте + инструмент tg_links

chigwell/telegram-mcp

transcription.py (голосовые: groq/openai/whisper + SQLite-кэш)

⛔ не берём: намертво привязан к их runtime.py; если нужны голосовые — проще вторым MCP поднять их сервер с TELEGRAM_EXPOSED_TOOLS=read-only

chigwell/telegram-mcp

TELEGRAM_EXPOSED_TOOLS (read-only поверхность)

💡 идея учтена: у нас инструментов отправки нет вообще, tg_sql скрыт за TG_EXPOSE_SQL=1

m0n0x41d/telegram-mcp

scan-state с курсорами и TRUNCATED-семантикой

≈ не берём: наши курсоры в meta делают то же; их doctor (диагностика сессии) — кандидат на потом

m0n0x41d/telegram-mcp

auth-визард с 2FA и лечением протухшей сессии

кандидат на потом (у нас пока client.start())

andvseti/telegram-mcp-server

get_recent_media/download_media в каталог

кандидат для медиа-превью (слой 4)

chigwell/telegram-mcp

Docker + HTTP-транспорт (общий демон на несколько клиентов)

кандидат, если визуал и opencode будут на разных машинах

Проверено / не проверено

python3 test_offline.py — 1275 проверок: свои проверки плюс пятнадцать suites подпроцессами (tests_index_media.py, tests_packs_semantics.py, tests_export_101.py, tests_ops_report.py, tests_media_ingest.py, tests_db_core.py, tests_readers.py, tests_pack_schema.py, tests_watchdog.py, tests_ui_projects.py, tests_manage.py, tests_announce.py, tests_workspace_api.py, tests_question_signals.py, tests_downloads.py) — все зелёные (работают и на Linux, и на Windows). Реально исполняются:

  • sync.run() на подставном клиенте: определение групп/каналов, --pattern, --chat @канал, --folder, --watched, --dry-run, раскладка по темам (включая General), инкрементальность по курсорам

  • sync.backfill(): досасывание истории чанками снизу вверх, идемпотентность, сохранение курсора, resume прерванного прогона (meta:backfill)

  • id-формы Telethon: entity.id в marked-форме (-100…) на границе синка — live.py и watch.py видят одни и те же id

  • db.py: темы, история, окно по времени, FTS5-поиск (включая фильтр по чату), статистика, watch-лист (watch_new / watch_mark_read / удаление), миграции по PRAGMA user_version (DDL не повторяется на каждом открытии), read-only-путь (DB(readonly=True) без миграций и с query_only), каскад проекта в watch_set_project, скрытие deleted=1 от читалок; run_sql и его гейты (query_only + authorizer: DELETE, PRAGMA, SELECT; DROP, UPDATE, ATTACH — блокируются; слово в LIKE-литерале — нет)

  • маскировка секретов на инджесте: токен не доезжает до messages.text, raw_json и refs; LIKE-фолбэк и run_sql выдают замаскированное

  • FTS-триггёр удаления: DELETE FROM messages чистит индекс (раньше падал)

  • правки/удаления в индексе: db.update_message / db.mark_deleted, FTS отдаёт новый текст после правки, удалённое скрыто от читалок и из FTS; sync подхватывает edit_date в rescan и backfill (edited_messages в отчёте); live.handle_edit / live.handle_delete на подставных событиях

  • статусная модель: open / in_progress / blocked / done (терминал — closed_ts); ops start/block/unblock с проверкой переходов, close→done; миграция v3 (closed→done); update не трогает статус

  • сигналы по вопросам: ответ другого отправителя (reply_received) и реакции (reaction_received) требуют проверки, но сами не закрывают вопрос; завершение остаётся явной операцией извлечения или ручным исправлением статуса

  • реакции в индексе: live.handle_reactions + db.update_reactions (агрегатные счётчики)

  • читалки агента: изменения с прошлого раза, бриф проекта, пробелы (вопросы без ответа, обещания без срока/ответственного), фильтр по ответственному

  • MCP: 16 инструментов — tg_changes / tg_project_brief / tg_gaps + owner в tg_entities

  • маршрутизация сводок по проектам (TG_NOTIFY_ROUTES, персональные хэш-ключи); monitor показывает обещания без срока/ответственного и «застряло» (blocked+просрочка)

  • наблюдаемость: doctor --cost, штамп pipeline:last_ok_ts + watchdog.py, UI-панель /api/projects с курсором визита проекта

  • гигиена: валидация схем паков на импорте, провайдеры экспорта из конфига (TG_EXTRA_PROVIDERS)

  • настоящий server.py как подпроцесс по MCP stdio: initialize → tools/list (16 инструментов, tg_sql скрыт) → tools/call по каждому, включая сценарий «прочитал → отметил → непрочитанного нет»; tg_new честно держит truncated при лимите; tg_mark_read отклоняет id выше максимума индекса темы; отдельный прогон с TG_ALLOW_LIVE=1 — live-инструменты регистрируются, без кредов отвечают JSON-ошибкой, а не зависают

  • downloads-модуль: дедуп по id файла Telegram до скачивания и по sha256 после (повторная загрузка алиасится на файл), у файла все сообщения-источники, контентно-адресное хранение; forward-режим — пересылка без скачивания: дедуп по (id файла, адресат), длинный FloodWait уводит задание в pause, короткий пережидается, запрет пересылки — skip; resume упавших пунктов (--retry --force), пауза/отмена задания, подборки каналов, реестр модулей и TG_MODULES_OFF, миграции v6→v8 и v7→v8 (таблицы пересылок довариваются в существующую базу); MCP-гейты TG_ALLOW_DOWNLOADS=1 (16+3) и TG_ALLOW_FORWARDS=1 (16+3), оба — 16+4; запуск и пересылка — confirm=True; фоновый загрузчик без кредов помечает задание failed и не зависает

  • workspace-API: реальный HTTP-сервер из ui.py (ThreadingHTTPServer, токен): фильтры/сортировка/пагинация сущностей, правка полей с 409 на устаревшую ревизию, 401 без токена и 415 без JSON, отмена незапущенной синхронизации — 409, секреты маскируются, превью 101 не ходит в сеть и не двигает экспортные курсоры

  • папки: GetDialogFiltersRequest на фейках → состав папок в индексе

  • топ по реакциям (stats.top_reactions)

  • live.handle_message: вставка + идемпотентность + refs; collect_ids подхватывает чаты, добавленные после старта

  • настоящий ui.py как подпроцесс: /api/poll отдаёт сообщения по курсору; Host-гейт (403 на чужой Host), POST без application/json → 415, отказ поднимать 0.0.0.0 без токена; /api/messages, /api/poll, /api/state выдают замаскированный текст

  • digest.run: stub-выжимки создаются, повторно не дублируются (курсор), отдаются через MCP tg_digests; сбой LLM-провайдера не двигает курсор

  • гейты: FloodWaitError на фейках (sync дожидается и доделывает; посреди прохода счётчик new не занижается), busy_timeout, before_id/date_to, budget_chars+truncated, tg_topics, live-тема с заголовком, UI-токен (401 без / 200 с)

  • TG_STRICT_WATCH=1: второй сервер-подпроцесс — чужой чат отклонён, слежуемый читается, tg_find/tg_topics вне watch слепы

  • doctor.run_checks(): формат чеков, FTS5 и redact self-test; --quality (заполненность полей по пакетам + гистограмма причин из rejects_json) и --providers (без настроенного openai-провайдера сеть не трогается)

  • бюджет контекста дайджеста: build_context вписывается в DIGEST_MAX_CHARS и честно помечает обрезку; строки за прогон ограничены DIGEST_MAX_ROWS (дефолт 500), хвост подбирается курсором

  • extract.run: курсор по (пакет, тема), окно backfill_days на первом прогоне, лимит 200 сообщений; операции new/update/close/reopen одной транзакцией; evidence-гейт: цитата вне текста сообщения отклоняется, причины отказа пишутся в rejects_json, сбой провайдера курсор не двигает; overflow: операции сверх 50 фиксируются в ops_overflow, курсор остаётся до первой отброшенной, следующий прогон дочитывает; --all-indexed покрывает все (chat, topic) индекса; дедуп dedup_hint сильнее title; отдаётся через MCP tg_entities/tg_entity/ tg_overdue

  • monitor.collect: просрочка и горизонт --days-ahead, фильтр по проекту, блок зависших вопросов; дедуп --send по хэшу отчёта с обходом через TG_MONITOR_FORCE=1; notify: dry-run по умолчанию, реальная отправка только за TG_ALLOW_SEND=1, cap TG_NOTIFY_CAP; server.py его не импортирует

  • media: sync/live ставят в очередь только транскрибируемое аудио (voice + audio-документы, гейт is_transcribable), stub-транскрипт попадает в промпт извлечения; openai без скачанных байтов — ValueError; --sweep-pending помечает легаси-pending как skipped одним UPDATE

  • export.run: file-провайдер пишет JSONL, курсор в integrations (повторный прогон дубликатов не даёт; краш посреди прогона — закоммичено до сущности, повтор выгружает только остаток); 101 — реальный HTTP-провайдер, проверен против локального фейк-сервера (POST, тело, id-курсор по entity_events.id, ретраи на 5xx, continue-on-error со списком failed, Idempotency-Key, запись external_id, курсор только после 2xx, ошибка без кредов); notion — словарь-ошибка без исключения. Живой пуш в 101 не проверялся (нет кредов/контракта)

  • topic_id канонически NOT NULL (=1 General): вставка без темы и миграция старых баз с NULL, запросы идут по индексу ix_msg_chat_topic (EXPLAIN PLAN проверяется), watch/history/search/stats видят то же

  • server._db() кэширует соединение на поток — схема/миграции не гоняются на каждый tool-call; миграции дополнительно гейтятся PRAGMA user_version

  • tg_live_* + tg_live_search: живые запросы регистрируются при TG_ALLOW_LIVE=1; без кредов — JSON-ошибка, не зависание

  • search.search_global: каналы/группы/юзеры с marked-id и kind, фильтр типа уходит флагом в запрос (SearchGlobalRequest на фейк-клиенте)

  • checklinks: хэш из +/joinchat/текста/сырого вида, checkChatInvite на фейк-клиенте (превью ok, member, expired/invalid терминальные, FloodWait повтором, сетевой сбой без падения прогона), кэш invite_status (свежее не перепроверяется, протухшее да, force не трогает терминальные, live_cap), invite_refs дедупит и сортирует по свежести, refs_summary джойнит статусы, миграция v5→v6; tg_live_check_invites без кредов — JSON-ошибка

  • пакет services: триал с due_text через dedup_hint-схему, другая формулировка с тем же hint — дубликат; stub создаёт service/trial/promo

  • семантика паков (tests_packs_semantics.py): детерминированный due_text → due_ts (абсолютные даты, ru-месяцы, относительные, ISO — по дате сообщения, конец дня вместо полуночи), no-downgrade точного срока при update; изоляция construction от проекта «сервисы» через require_match + exclude_project_regex; dedup_scope project/global; схемы пакетов: evidence minItems=1, без confidence; supersedes: цель закрывается, событие op='supersede' в entity_events. Качество и частота извлечения на реальных чатах не проверены: rate promise и реальная заполняемость due/supersedes ждут живого прогона EXTRACT_PROVIDER=openai

  • pipeline.run: sync→extract→digest→monitor на фейк-клиенте (10 сообщений, 2 дайджеста), второй прогон инкрементален, --skip sync — офлайн-шаги; в отчёте extract — сводные ops_rejected/ops_overflow

  • операционная автономка (tests_ops_report.py): notify в живом event loop (worker-поток), бюджет шага PIPELINE_STEP_BUDGET, proclock-лок one-shot, дедуп сводки monitor, зависшие вопросы, doctor --quality; tests_media_ingest.py: sweep pending и граница tg_mark_read; tests_db_core.py: user_version-миграции, read-only, deleted/FTS, dirty, каскад проекта

  • проект у тем watch (watch.py --project, подкоманда project) наследуется сущностями и фильтруется в tg_entities/tg_overdue/monitor

  • manage.py (tests_manage.py): создание групп/каналов/форумов на фейк-клиенте с регистрацией в индексе (created_by_me=1, my_role=owner), настройки (title/about/photo/username, тумблеры, дефолтные права), темы форума, участники и инвайт-ссылки; гейт TG_ALLOW_MANAGE (без него все операции заблокированы без запросов); MCP tg_manage_* регистрируются только при гейте и требуют confirm=True

  • announce.py (tests_announce.py): рендер баннера PIL (1080x720), сборка текста из YAML-шаблона, post с кросспостом/реакцией/пином на фейк-клиенте и запись в таблицу posts (миграция v5), readme темы из org.yaml, черновик из CHANGELOG репозитория; гейт TG_ALLOW_MANAGE — без него отправка блокируется без запросов; MCP tg_manage_post требует confirm=True. Живые посты в Telegram не проверены

Не проверено: реальные вызовы к Telegram — здесь нет аккаунта. Сигнатура messages.GetForumTopicsRequest снята с установленного Telethon 1.45, но живой ответ API глазами не видел; iter_dialogs(folder=...) проверен только по сигнатуре. Первый sync.py --dry-run --folder <id> — это и есть проверка.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      A read-only MCP server that lets AI agents read personal Telegram chats from an allowlist of folders, with no send/edit/delete capability.
      41 npm
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      An MCP server that connects to Telegram as your real user account and exposes read-only tools to read and search messages, list chats and folders, inspect group info, and download media.
      16 npm
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Read-only MCP server for Telegram chats and channels that provides digest summaries, message search, and action items.
      11 npm
      MIT
    • F
      license
      Not graded
      quality
      B
      maintenance
      Read-only MCP server that exposes retrieval tools over a Telegram chat archive, including archive overview, full-text search with Russian morphology, aggregates, message retrieval, and local context. It lets LLM agents answer questions from chat history without syncing Telegram, downloading files, or modifying the index.
      -