mirror of
https://gitverse.ru/kpa39l/email-assistant.git
synced 2026-09-29 09:15:09 +00:00
Session close 2026-09-15 (вечер): imap_stream Фаза 1 реализован, backlog urgent закрыт (фильтр актуальности), live-тесты OK
This commit is contained in:
@@ -157,6 +157,30 @@ Hermes cron:
|
|||||||
- [x] **Репозиторий переведён на gitverse (источник истины)**: создан приватный `kpa39l/email-assistant` на gitverse.ru (id 336792) через API; remote `gitverse` добавлен; master запушен; **gitea: pull mirror `estorozhenko/email-assistant` ← gitverse (12h, id 54)** — gitea.nixg.ru остаётся зеркалом, не источником
|
- [x] **Репозиторий переведён на gitverse (источник истины)**: создан приватный `kpa39l/email-assistant` на gitverse.ru (id 336792) через API; remote `gitverse` добавлен; master запушен; **gitea: pull mirror `estorozhenko/email-assistant` ← gitverse (12h, id 54)** — gitea.nixg.ru остаётся зеркалом, не источником
|
||||||
- [x] **AGENT.md создан** — политика репозитория: gitverse = истина, gitea = зеркало, команды пуша, **местоположение токенов** (git-tokens.env, VESTI_BOT_TOKEN, TELEGRAM_CHAT_ID)
|
- [x] **AGENT.md создан** — политика репозитория: gitverse = истина, gitea = зеркало, команды пуша, **местоположение токенов** (git-tokens.env, VESTI_BOT_TOKEN, TELEGRAM_CHAT_ID)
|
||||||
|
|
||||||
|
## Сделано в сессии 2026-09-15 (вечер) — imap_stream.py реализован + backlog urgent закрыт
|
||||||
|
|
||||||
|
### imap_stream.py — Фаза 1 (imap-realtime-sync, задачи 3–5) ✅
|
||||||
|
- [x] **`scripts/imap_stream.py` (новый, ~690 строк)** — постоянный IMAP IDLE-поток на сыром socket поверх `imap_client.imap_connect` (aioimaplib не ставится):
|
||||||
|
- IDLE-цикл: `idle_start` → `idle_wait(25с)` → `idle_done` → перевыпуск (Exchange рвёт IDLE ~60с, поэтому IDLE_TIMEOUT=25с)
|
||||||
|
- Reconnect с паузой `CONNECT_PAUSE=45с` (rate-limit Exchange), цикл переживает обрыв (проверено live: 150с демон → обрыв → reconnect → SELECT)
|
||||||
|
- `reconcile_new` — новые UID (архивация через mail_archive) + `reconcile_full` каждые 5 циклов (флаги/удаления)
|
||||||
|
- SQLite `/opt/hermes/email/state/mailbox.db`: `mailbox_state` (uid, folder, message_id, in_reply_to, refs, flags, has_attachment, archive_path, last_seen, deleted) + `mailbox_events`
|
||||||
|
- CLI: `--check`, `--test-idle`, `--status`, `--metrics`, без аргументов — демон
|
||||||
|
- [x] **Live-тесты Exchange (пауза ≥45с между коннектами) — все прошли**: `--check` (LOGIN 365 писем), `--test-idle` (SELECT → reconcile → IDLE 30с → 0 событий → DONE, EXIT=0), демон 150с (обрыв+reconnect)
|
||||||
|
- [x] **Исправлено live-тестами (питфолы, детали в WALKTHROUGH):** HERMES_REAL_HOME в imap_client (_load_credentials), `_cmd_ok` (хвост \r\n), колонка `references`→`refs` (SQLite), **UID-батчинг `1:1000` пуст** → `UID SEARCH ALL`/`UID <min>:*` (иначе все письма ложно deleted), Exchange не шлёт `+ idling` → `+ IDLE accepted`
|
||||||
|
- [x] **Запушено в gitverse**: `5a81c86` (imap_stream + фикс токена TG + фильтр актуальности + HERMES_REAL_HOME)
|
||||||
|
|
||||||
|
### Backlog urgent (10 писем) — закрыт ✅
|
||||||
|
- [x] **Корень найден**: `email_handlers.py` при наличии python-dotenv шёл в ветку `if load_dotenv` и НЕ грузил `/opt/vesti/.env` → `VESTI_BOT_TOKEN` пуст → все уведомления падали. Фикс: vesti/.env грузится всегда
|
||||||
|
- [x] **5 сентябрьских писем доставлены в ЛС** (msg_id 62–64 и далее), помечены handled_urgent
|
||||||
|
- [x] **Фильтр актуальности (запрос пользователя)**: `URGENT_MAX_AGE_DAYS=3` + `is_urgent_recent()` — письма старше 3 дней НЕ шлются в Telegram (помечаются handled_urgent); 5 старых (июль/авг) пропущены корректно
|
||||||
|
- [x] Идемпотентность сохранена: дубли предотвращены (14229 помечен вручную)
|
||||||
|
|
||||||
|
### Открыто на следующую сессию
|
||||||
|
- [ ] Запуск imap_stream.py как постоянного сервиса (systemd/cron) — код готов, не запущен навсегда
|
||||||
|
- [ ] Баг Radicale: `[task] PUT 400: Bad Request` (VTODO-задача, не относится к urgent) — не разобран
|
||||||
|
- [ ] Полный reconcile по всем подпапкам INBOX (сейчас Фаза 1 — только INBOX)
|
||||||
|
|
||||||
### Задача 1: Веб-интерфейс ассистента ⬜
|
### Задача 1: Веб-интерфейс ассистента ⬜
|
||||||
- [ ] FastAPI + SQLite FTS5: список писем (дата/адресант/тэги/папка)
|
- [ ] FastAPI + SQLite FTS5: список писем (дата/адресант/тэги/папка)
|
||||||
- [ ] Перемещение в папку; тэги
|
- [ ] Перемещение в папку; тэги
|
||||||
|
|||||||
@@ -59,8 +59,10 @@
|
|||||||
| 2026-09-15 | Telegram-уведомления о важных письмах в ЛС (письмо 3216 → ЛС kpa39l, user подтвердил) | ✅ закрыта | `.env` проект (TELEGRAM_CHAT_ID=281328953) |
|
| 2026-09-15 | Telegram-уведомления о важных письмах в ЛС (письмо 3216 → ЛС kpa39l, user подтвердил) | ✅ закрыта | `.env` проект (TELEGRAM_CHAT_ID=281328953) |
|
||||||
| 2026-09-15 | Баг: крон mail-classify-handlers использовал python3 без httpx → уведомления не уходили (10 urgent-писем в backlog). Фикс: /opt/vesti/.venv/bin/python + --limit 2 | ✅ закрыта | scripts/mail-classify-handlers.sh |
|
| 2026-09-15 | Баг: крон mail-classify-handlers использовал python3 без httpx → уведомления не уходили (10 urgent-писем в backlog). Фикс: /opt/vesti/.venv/bin/python + --limit 2 | ✅ закрыта | scripts/mail-classify-handlers.sh |
|
||||||
| 2026-09-15 | Формат даты в уведомлениях: `02.09.2026 10:49` (format_date_for_tg) + снятие YAML-кавычек (_unquote_yaml) | ✅ закрыта | scripts/email_handlers.py |
|
| 2026-09-15 | Формат даты в уведомлениях: `02.09.2026 10:49` (format_date_for_tg) + снятие YAML-кавычек (_unquote_yaml) | ✅ закрыта | scripts/email_handlers.py |
|
||||||
| 2026-09-15 | imap_stream.py (IDLE-цикл) — скелет + выбор библиотеки (aioimaplib не установлен; свой клиент поверх imap_connect) | 🔵 открыта | openspec/changes/imap-realtime-sync/tasks.md |
|
| 2026-09-15 | imap_stream.py (IDLE-цикл) — РЕАЛИЗОВАН (Фаза 1, задачи 3–5): IDLE-цикл с reconnect (45с), reconcile new/full, SQLite mailbox.db (mailbox_state, mailbox_events); --check/--test-idle/--status/демон | ✅ закрыта | @session:default/20260915_174925_ec1ca4; git 5a81c86 |
|
||||||
| 2026-09-15 | Backlog 10 urgent-писем (июль-сентябрь) — отправляются по 2/прогон крона в ЛС; часть просрочена (решить, пропускать ли) | 🔵 открыта | STATUS.md §2026-09-15 |
|
| 2026-09-15 | Backlog urgent: корень найден (vesti/.env не грузился при python-dotenv) → 5 сентябрьских доставлены; фильтр актуальности URGENT_MAX_AGE_DAYS=3 (старые не шлются) | ✅ закрыта | @session:default/20260915_174925_ec1ca4; git 5a81c86 |
|
||||||
|
| 2026-09-15 | Фильтр неактуальных уведомлений (запрос пользователя): is_urgent_recent() — письмо старше 3 дн не шлётся, помечается handled_urgent | ✅ закрыта | scripts/email_handlers.py; git 5a81c86 |
|
||||||
|
| 2026-09-15 | Live-тесты Exchange с паузой ≥45с — все прошли (--check, --test-idle 30с, демон 150с пережил обрыв+reconnect) | ✅ закрыта | @session:default/20260915_174925_ec1ca4 |
|
||||||
| 2026-09-15 | Репозиторий: gitverse = источник истины (создан kpa39l/email-assistant, remote gitverse, master запушен) | ✅ закрыта | AGENT.md, STATUS.md |
|
| 2026-09-15 | Репозиторий: gitverse = источник истины (создан kpa39l/email-assistant, remote gitverse, master запушен) | ✅ закрыта | AGENT.md, STATUS.md |
|
||||||
| 2026-09-15 | Gitea pull mirror: estorozhenko/email-assistant ← gitverse (12h, id 54) | ✅ закрыта | AGENT.md, STATUS.md |
|
| 2026-09-15 | Gitea pull mirror: estorozhenko/email-assistant ← gitverse (12h, id 54) | ✅ закрыта | AGENT.md, STATUS.md |
|
||||||
| 2026-09-15 | AGENT.md: добавить местоположение токенов (git-tokens.env, VESTI_BOT_TOKEN, TELEGRAM_CHAT_ID) | ✅ закрыта | AGENT.md |
|
| 2026-09-15 | AGENT.md: добавить местоположение токенов (git-tokens.env, VESTI_BOT_TOKEN, TELEGRAM_CHAT_ID) | ✅ закрыта | AGENT.md |
|
||||||
|
|||||||
@@ -2,6 +2,83 @@
|
|||||||
|
|
||||||
Воспроизводимость: хронология, команды, решения, ошибки и как чинили.
|
Воспроизводимость: хронология, команды, решения, ошибки и как чинили.
|
||||||
|
|
||||||
|
## 2026-09-15 (вечер, сессия @session:default/20260915_174925_ec1ca4)
|
||||||
|
|
||||||
|
### imap_stream.py — Фаза 1 (IDLE-цикл) реализован
|
||||||
|
|
||||||
|
**Цель:** постоянный IMAP-поток (задачи 3–5 change imap-realtime-sync). aioimaplib
|
||||||
|
не ставится → свой клиент на сыром socket поверх `imap_client.imap_connect`.
|
||||||
|
|
||||||
|
**Архитектура `scripts/imap_stream.py`:**
|
||||||
|
- `ImapStream` — обёртка над сокетом: теги, `_cmd`, `_cmd_ok`, чтение до тега.
|
||||||
|
- IDLE-цикл: `idle_start` → `idle_wait(25с)` → `idle_done` → перевыпуск.
|
||||||
|
- Reconnect: при ошибке `CONNECT_PAUSE=45с` (rate-limit Exchange), повтор.
|
||||||
|
- `reconcile_new(folder)` — поиск UID > last_uid, архивация через mail_archive,
|
||||||
|
запись в `mailbox_state` + событие `mailbox_events`.
|
||||||
|
- `reconcile_full(folder)` — полное сравнение UID/флагов (каждый 5-й цикл).
|
||||||
|
- SQLite: `/opt/hermes/email/state/mailbox.db`, таблицы `mailbox_state` (uid,
|
||||||
|
folder, message_id, in_reply_to, refs, flags, has_attachment, archive_path,
|
||||||
|
last_seen, deleted) и `mailbox_events` (event/ts/folder/uid/detail).
|
||||||
|
- CLI: `--check` (авторизация), `--test-idle` (IDLE 30с), `--status`, `--metrics`,
|
||||||
|
без аргументов — демон.
|
||||||
|
|
||||||
|
**Питфолы Exchange, найденные live-тестами (пауза ≥45с между коннектами):**
|
||||||
|
1. `_load_credentials` в imap_client.py читал `Path.home()` — в сессии Hermes
|
||||||
|
HOME≠реальный. Фикс: `HERMES_REAL_HOME` (env) → `/home/estorozhenko`. Cron
|
||||||
|
уже делает это в mail-archive.sh.
|
||||||
|
2. `_cmd_ok`: `resp.split(b"\r\n")[-1]` давал пустой элемент (хвост `\r\n`) —
|
||||||
|
команда считалась failed даже при OK. Фикс: разбор строк до тега.
|
||||||
|
3. Колонка `references` — зарезервированное слово SQLite → `refs`.
|
||||||
|
4. **UID-батчинг сломан:** `UID SEARCH UID 1:1000` пуст (UID — глобальный номер
|
||||||
|
~14200+, не порядковый); первый пустой батч обрывал цикл, все письма ложно
|
||||||
|
помечались deleted. Фикс: `UID SEARCH ALL` / `UID SEARCH UID <min>:*`
|
||||||
|
(проверено: 365 UID).
|
||||||
|
5. Exchange отвечает на IDLE `+ IDLE accepted, awaiting DONE command.` —
|
||||||
|
НЕ `+ idling`. Фикс: матч `+ IDLE` или `+ idling`.
|
||||||
|
6. **Exchange рвёт IDLE-соединение ~60с** (не 30 мин) — `IDLE_TIMEOUT=25с`
|
||||||
|
(перевыпуск до серверного лимита). Демон переживает обрыв: reconnect 45с.
|
||||||
|
|
||||||
|
**Live-тесты (все с паузой ≥45с):**
|
||||||
|
- `--check`: connect+LOGIN+SELECT 365 писем → OK.
|
||||||
|
- `--test-idle`: SELECT → reconcile (0 новых, deleted=0, flags=18) → IDLE 30с →
|
||||||
|
0 событий → DONE → EXIT=0.
|
||||||
|
- Демон 150с: stream_started → SELECT 365 → обрыв IDLE (~60с) →
|
||||||
|
reconnect_pause 45с → SELECT 365. Цикл переподключения работает.
|
||||||
|
|
||||||
|
**Git:** 5a81c86 запушен в gitverse (defcf53..5a81c86). Файлы: scripts/imap_stream.py
|
||||||
|
(новый, ~690 строк), scripts/email_handlers.py (+фильтр актуальности), scripts/imap_client.py
|
||||||
|
(+HERMES_REAL_HOME).
|
||||||
|
|
||||||
|
### Backlog urgent-pисем: корень найден + фильтр актуальности
|
||||||
|
|
||||||
|
**Проблема:** 10 urgent-писем не уходили в Telegram. Причина: при наличии
|
||||||
|
python-dotenv `email_handlers.py` шёл в ветку `if load_dotenv` — грузил только
|
||||||
|
`BASE_DIR/.env` и `radicale/.env`, НО НЕ `/opt/vesti/.env` → `VESTI_BOT_TOKEN`
|
||||||
|
пуст → все обработки падали. Строка `✗ [urgent] ...: Нет токена Telegram`.
|
||||||
|
|
||||||
|
**Фикс (email_handlers.py):** `/opt/vesti/.env` грузится всегда (до ветвления).
|
||||||
|
|
||||||
|
**Запрос пользователя (mid-turn):** «перестань слать неактуальные срочные
|
||||||
|
уведомления; встроить проверку на актуальность — сравнивать дату письма и
|
||||||
|
текущую». Реализовано:
|
||||||
|
- `URGENT_MAX_AGE_DAYS = 3` — письма старше 3 дней не шлются.
|
||||||
|
- `is_urgent_recent(headers)` — парсит дату из frontmatter, сравнивает с now.
|
||||||
|
- Старое письмо: `✓ [urgent] ...: пропущено (актуальность истекла)`, помечается
|
||||||
|
`handled_urgent` (идемпотентность, не перебирается).
|
||||||
|
|
||||||
|
**Результат backlog:** 5 сентябрьских доставлены в ЛС (msg_id 62–64 и далее),
|
||||||
|
5 старых (июль/авг) — пропущены фильтром корректно. Январьское «Сервер» — на
|
||||||
|
деле `classification: info` (не urgent, ошибка подсчёта). 14229 помечен вручную
|
||||||
|
(уже дважды уведомлён — предотвращён дубль).
|
||||||
|
|
||||||
|
**Известный отдельный баг (не urgent):** `✗ [task] ...: PUT 400: Bad Request`
|
||||||
|
(Radicale VTODO-задача) — не разобран, открыт на следующую сессию.
|
||||||
|
|
||||||
|
### Cron-заметка
|
||||||
|
|
||||||
|
`mail-classify-handlers` (в venv /opt/vesti/.venv/bin/python, --limit 2/прогон)
|
||||||
|
работает; после фикса токена обрабатывает по 2 письма за прогон идемпотентно.
|
||||||
|
|
||||||
## 2026-09-11
|
## 2026-09-11
|
||||||
|
|
||||||
### Фаза 1.7: динамическое обнаружение подпапок INBOX
|
### Фаза 1.7: динамическое обнаружение подпапок INBOX
|
||||||
|
|||||||
Reference in New Issue
Block a user