Архітектура

aipager - це програма на asyncio з одного процесу, яка працює у фоні. Вона з'єднує три світи: твій чат у Telegram, процеси Claude Code, які ти запускаєш під dtach, і систему hooks Claude Code. Усе проходить через один цикл подій: жодних пулів воркерів, жодної бази даних, жодних черг між процесами.

Джерело на GitHub ↗Цю сторінку перекладено з англійської; якщо вони розходяться, правильною є англійська версія. English

aipager - це програма на asyncio з одного процесу, яка працює у фоні. Вона з'єднує три світи: твій чат у Telegram, процеси Claude Code, які ти запускаєш під dtach, і систему hooks Claude Code. Усе проходить через один цикл подій: жодних пулів воркерів, жодної бази даних, жодних черг між процесами.

Схема частин#

Loading diagram…

Усе на схемі працює на цьому комп'ютері, крім Telegram. aipager говорить ще з кількома серверами: з тунелем Cloudflare для Mini App (і один раз із GitHub, заради cloudflared), з telegram.org заради скрипта Mini App, з перевіркою версій, коли ти просиш оновлення, із завантаженням голосової моделі, коли вперше користуєшся голосовими, і з адресою для звітів про проблеми, коли ти надсилаєш звіт. Усі вони перелічені в розділі Модель безпеки → мережеві з'єднання.

Як улаштований процес#

Один виклик asyncio.run в aipager/cli/daemon.py веде все. Усередині:

  • TelegramBot (aipager/bot/): володіє Application з python-telegram-bot. Отримує оновлення від Telegram, роздає їх обробникам повідомлень, кнопок і голосових і тримає в актуальному вигляді робочі картки, закріплений рядок стану та відповіді.
  • HookReceiver (aipager/dtach/hook_receiver.py): відкриває Unix datagram-сокет у $XDG_RUNTIME_DIR/aipager.sock, читає JSON, який надсилають aipager-hook і aipager-statusline, і розподіляє роботу за його hook_event_name (або type, для власних повідомлень aipager; див. події hooks).
  • SessionMonitor (aipager/session_monitor.py): прокидається кожні 2 с, щоб переглянути /tmp/claude-dtach-*.sock, звірити знайдені сесії зі списком сесій і запустити вартових: робоча картка, що перестала рухатися, повідомлення, яке Claude Code так і не прийняв, запит на дозвіл без новин 5 хвилин, який за збереженою розмовою вже закінчено (AIPAGER_INTERACTIVE_TIMEOUT), завдання, кінець якого пропущено.
  • SessionRegistry (aipager/state.py): об'єкти TrackedSession за назвами. Зберігаються в ~/.claude/aipager-sessions.json (атомарно) під час вимкнення і щоразу, коли змінюється щось, що має пережити перезапуск.
  • Сервер Mini App (aipager/miniapp/server.py): застосунок aiohttp на 127.0.0.1 з портом MINIAPP_PORT (за замовчуванням 8765), доступний для Telegram через керований тунель Cloudflare, поки він увімкнений (за замовчуванням так). Віддає панель /app.

Усе це працює як задачі в одному циклі. Робота, яка б його блокувала, іде в окремих потоках: перетворення голосових на текст, збирання і надсилання звіту про проблему, перевірка оновлень і підготовка перезапуску, завантаження скрипта Mini App і cloudflared та перевірка входу під час запуску.

Порядок запуску#

З aipager/cli/daemon.py:

  1. Один раз перенести стару конфігурацію (config.env, team.yaml) в aipager.yaml і відкласти старі файли вбік.
  2. Відмовитися запускатися, якщо на сокеті відповідає інший aipager, потім узяти блокування aipager (~/.local/share/aipager/daemon.lock).
  3. Перевірити токен бота в Telegram (getMe).
  4. Упорядкувати файли самого Claude Code: додати в ~/.claude/settings.json відсутні hooks aipager і рядок стану та відповісти на питання першого запуску, на які людина в Telegram відповісти не може (див. безпека).
  5. SessionRegistry.load(): читає ~/.claude/aipager-sessions.json. Якщо файлу немає або він не читається, список починається порожнім.
  6. Визначити адресу Mini App (твій власний URL або Tailscale), потім bot.start(): починає опитувати Telegram.
  7. HookReceiver.start(): видаляє старий файл сокета, прив'язується до сокета, слухає.
  8. bot.recover_sessions(): повертає картки й запити сесій, які працювали або чекали, коли aipager зупинився (див. команди → відповіді).
  9. SessionMonitor.start(): починає прохід кожні 2 с.
  10. Запускається сервер Mini App, потім тунель (якщо не задано твій власний URL), і публікуються кнопки 📱 App.
  11. aipager чекає на сигнали: SIGINT або SIGTERM його зупиняють, SIGUSR1 перезавантажує чати й правила (групи → перезавантаження без перезапуску).

Порядок вимкнення#

  1. SIGINT або SIGTERM виставляє подію stop.
  2. /update, що триває, отримує коротку відстрочку, потім його інсталятор зупиняється (див. команди → оновлення).
  3. registry.save().
  4. Зупиняється тунель, потім сервер Mini App.
  5. session_monitor.stop(), hook_receiver.stop() (закриває сокет і видаляє його файл).
  6. bot.stop(): скасовує анімації та зупиняє Application.
  7. Ще раз registry.save(), для картки, надсилання якої ще тривало.

Перезапуск із Telegram розраховує на те, що весь цей шлях пройде до кінця, перш ніж заміна прив'яжеться до сокета. Що при цьому видно тобі, описано в команди бота → перезапуск.

Де лежать файли та сокети#

ШляхПризначенняХто пише
$XDG_RUNTIME_DIR/aipager.sockUnix datagram-сокет для подій hooks (/tmp/aipager.sock без $XDG_RUNTIME_DIR; $AIPAGER_SOCKET_PATH замінює обидва)aipager (прив'язується)
aipager-reply-<id>.sock (поруч із керувальним сокетом)Короткочасний сокет відповіді для кожного запиту на дозвіл, на який чекає hookaipager-hook
aipager-flood-mute.json, aipager-flood-backoff.json (поруч із керувальним сокетом)Поточний стан лімітів Telegram, для aipager status і doctoraipager
aipager-modelswitch-<name>.json (поруч із керувальним сокетом)Позначає зміну моделі, яку ввів aipager, на 30 с (hooks)aipager (пише), aipager-hook (забирає)
/tmp/claude-dtach-<name>.sockКерувальний сокет dtach для кожної сесіїdtach
/tmp/claude-status-<name>.jsonДані рядка стану кожної сесіїaipager-statusline
/tmp/claude-notes-<name>/По одній примітці про дозволи на кожне ще не забране повідомлення з Telegram і позначка turn-open, яку hook тримає, поки триває завдання (повідомлення, що приєднується до завдання, може лише звузити його правила)aipager (примітки, пише), aipager-hook (витрачає примітки; пише turn-open, а стирають її обидва)
/tmp/claude-policy-<name>.jsonОпорний знімок дозволів для завдання, що триваєaipager-hook (пише, коли забирає повідомлення), читає перевірка PreToolUse
/tmp/claude-policy-.floor-<uid>.jsonНижній поріг безпеки: вбудований список плюс твій розділ safety:, для запасних шляхів hookaipager
/tmp/claude-reply-<name>.txtПовідомлення, на яке ти відповідаєш, коли це старіше повідомленняaipager
/tmp/aipager-files/<chat>/Файли та голосові, надіслані з Telegram, окрема приватна папка для кожного чату (0700, файли 0600)aipager
/tmp/aipager.logВивід перезапуску самим aipager, коли служби немаєaipager
~/.claude/aipager-sessions.jsonДовготривалий стан списку сесійaipager
~/.claude/aipager-audit.jsonlЖурнал натискань «Дозволити» / «Відхилити» і відповідейaipager (лише дописує)
~/.claude/aipager-flood-state.jsonІсторія лімітів Telegram для кожного чату, зберігається між перезапусками (розв'язання проблем)aipager
~/.claude/aipager-tunnel.json, aipager-cloudflared.logТунель Mini App, який тримає aipager: його процес, локальні порти й хеш його адреси (ніколи не сама адреса), щоб наступний aipager міг підхопити його після планового перезапуску (права 600); попередження cloudflaredaipager; запис також видаляють сторож залишеного тунелю і aipager uninstall
~/.claude/aipager-pending-users.jsonЛюди, які звернулися до бота в групі, яку він не обслуговує, або які не є учасниками групи, яку він обслуговує, щоб aipager config міг їх додати (права 0600, повністю перезаписується під час кожного запису, останні 200 людей за 30 днів)aipager (пише), aipager config (читає)
~/aipager-users/<label>/Кореневі папки учасників за замовчуванням (права 0700), де учасники з user і read_only запускають свої сесії; aipager uninstall їх зберігаєaipager (створює під час першого /new учасника або його першого натискання «Нова сесія» в Mini App)
~/.claude/settings.jsonКонфігурація hooks Claude Codeaipager config / aipager setup; під час кожного запуску aipager додає відсутні hooks або рядок стану aipager і ставить skipDangerousModePermissionPrompt (безпека)
~/.claude/settings.json.bak.*Резервні копії перед кожним перезаписомaipager config, aipager setup, aipager uninstall
~/.claude.jsonВласний файл Claude Code: aipager позначає папку, у якій запускається сесія, як довірену просто перед запуском (домашню папку теж, але ніколи / чи папку вище домашньої), щоб питання про довіру Claude Code не зупиняло сесію, запущену з Telegram. Під час запуску aipager нічого не позначається. Ніколи не перезаписується, якщо його не можна прочитати повністю або запущений Claude тим часом його змінивaipager
~/.claude/.credentials.json → .credentials.json.staleФайл входу Claude Code, відсувається вбік, коли сесія запускається із заданим CLAUDE_CODE_OAUTH_TOKEN, а у файлі лише прострочений або порожній вхід (інакше він узяв би гору над токеном); щоб скасувати, поверни його на місцеaipager
~/.config/aipager/aipager.yamlТокен бота, чати, учасники + ролі, налаштування Mini Appaipager config (права 600); aipager переносить запис групи, коли Telegram робить її супергрупою, і зберігає копію aipager.yaml.bak.<time>, коли переносить туди старі налаштування Mini App
~/.config/aipager/policy.yamlПравила для кожної ролі + доповнення до порогу безпекити (перевіряється через aipager policy validate); шаблон із коментарями пишеться один раз під час перенесення старого встановлення
~/.config/aipager/preferences.jsonВибір у /settings для кожного чату і кожної сесіїaipager
~/.config/aipager/daemon.envОблікові дані Claude для сесій, що запускаютьсяти / aipager service install / aipager doctor --fix (права 600)
~/.config/aipager/config.env, team.yaml → *.bak.<time>, *.retired.<time>Конфігурація старого встановлення: копіюється, а потім перейменовується вбік, коли її вже містить aipager.yamlaipager, aipager config, aipager setup
~/.config/aipager/keyboard.jsonНеобов'язкові зміни клавіатурити
~/.local/share/aipager/sessions/По папці на сесію з її SESSION.md (хто може до неї звертатися і правила), створюється заново під час кожного запускуaipager
~/.local/share/aipager/webapp-sdk/, cloudflared/Скрипт Mini App від Telegram і перевірена програма cloudflaredaipager
~/.local/share/aipager/daemon.lock, update.lock, update-restart.jsonЛише один aipager одночасно; лише одне оновлення одночасно; оновлення, про яке треба повідомити після перезапускуaipager
~/.local/share/aipager/install.json, running.jsonКоли aipager уперше запустився тут; чи працює він зараз (щоб відрізнити падіння від звичайної зупинки)aipager
~/.local/state/aipager/reports.jsonЗвіти про проблеми: записані відбитки помилок, їхня кількість і стан пропозицій (права 600); див. звіти про проблемиaipager
~/.local/state/aipager/report-sends.jsonЗвіти про проблеми, надіслані сьогодні (денний ліміт)aipager, aipager report
~/.local/state/aipager/languages.jsonСтан мов (права 600): хто вже користувався aipager до появи мов, остання мова Telegram, помічена в кожної людини, чи пішли разова пропозиція мови та рядок першого контакту, і відбиток кожного опису бота, який поставив aipager; див. Мовиaipager
~/.config/systemd/user/aipager.service (Linux), ~/Library/LaunchAgents/com.aipager.daemon.plist (macOS) і копія .bak.<time> того, що замінює встановлення (на Linux лише якщо воно відрізняється)Фонова службаaipager service install
~/Library/Logs/aipager.log (macOS)Лог служби launchd, його читає aipager logslaunchd, для служби

Крім цього, aipager створює нову папку сесії, коли ти про це просиш (Нова папка в /new або в Mini App), а інсталятори та голосова модель пишуть свої файли, коли ти оновлюєшся або користуєшся голосовими. Усе це лежить у твоїй домашній папці, у /tmp або поруч із керувальним сокетом. aipager ніколи не підвищує своїх прав; див. безпека.

Ізольований екземпляр (для тестів)#

Задай AIPAGER_INSTANCE_DIR як абсолютну папку, що належить тобі, і кожен спільний робочий файл зі списку вище переїде в неї: керувальний сокет (а поруч із ним сокети відповідей, файли сигналів лімітів і позначка про зміну моделі), сокети claude-dtach-*, файли claude-policy-*, claude-notes-*, claude-status-* і claude-reply-*, файл порогу, папка завантажень aipager-files і лог перезапуску самим aipager. Сесії, які запускає цей екземпляр, успадковують налаштування, тому їхні hooks говорять лише з цим екземпляром, а правила безпеки захищають перенесені файли так само. Екземпляр не читає .env робочої копії репозиторію.

Файли у твоїй домашній папці не переїжджають, тому спрямуй і HOME на окрему папку. aipager start відмовляється працювати, якщо задано папку екземпляра, а HOME - це твоя справжня домашня папка, якщо папка не є абсолютним шляхом до папки, що належить тобі, або якщо вона задовга для шляху сокета.

/update і /restart не ізольовані: вони діють на встановлений пакет aipager і службу aipager.service, тому ніколи не надсилай їх тестовому екземпляру.

AIPAGER_TELEGRAM_API_BASE (наприклад, http://127.0.0.1:41234) надсилає кожен запит до Bot API і кожне завантаження файлу на цю адресу замість https://api.telegram.org. Це має бути цей комп'ютер (localhost або адреса loopback), якщо лише не задано AIPAGER_TELEGRAM_API_ALLOW_REMOTE=1, щоб друкарська помилка не могла надіслати токен бота кудись іще.

Обидва налаштування потрібні, щоб наскрізні тести могли запускати другий aipager проти локальної заміни Telegram поруч із твоїм справжнім. Без них кожен шлях і кожна адреса такі, як у таблиці вище.

Чому dtach#

dtach дає кожній сесії Claude Code справжній PTY, не прив'язуючи його до термінала, який має лишатися відкритим. aipager запускає сесію через dtach -n і вводить у неї через dtach -p (повідомлення, Escape, клавіші для відповіді в діалозі); екран він ніколи не читає, бо про те, що відбувається, кажуть hooks та історія розмови. Під'єднатися інтерактивно для прямого доступу можна з будь-якого shell через aipager session <name>. Таке під'єднання стежить за сесією через перезапуск із Telegram (/mode, що вмикає чи вимикає режим «Повний доступ», /restart, Перезапустити): коли сокет сесії зникає, воно чекає до 15 с, поки повернеться той самий сокет, і під'єднується знову само. Сесія, яка справді завершилася, після очікування повертає тебе в shell (Ctrl-C припиняє очікування раніше). Під'єднання працює з вимкненою клавішею від'єднання dtach, тому Ctrl-\ іде в Claude; щоб піти, закрий термінал або панель tmux, і сесія працюватиме далі.

Підсумок: Claude Code працює так само, як під час набору в терміналі, але цей термінал може з'являтися і зникати, не заважаючи сесії, що триває. aipager знаходить сесії, переглядаючи /tmp/claude-dtach-*.sock на кожному проході монітора раз на 2 с. Знайдена сесія належить чату, для якого було створено її назву, а сесія, запущена в терміналі, на встановленні з особистим чатом і групою належить особистому чату власника.

Див. також#