5.3 KiB
#166 · #168 · #170 — Agent IA phase 4 : doublons, notifications externes, tâches planifiées
Statut : ✅ livré — Effort : ~5 jours | Impacts : 🟢 Références : Roadmap · Changelog Guides : Assistant IA & Forge · API REST · MCP
1. Périmètre
Trois items de la liste v1.2 « Phase 4 » livrés ensemble car ils partagent le
même socle (registre @tool, stores JSON verrouillés, notifications) :
| ID | Fonctionnalité | Entrées |
|---|---|---|
| #166 | Détection & fusion de doublons | backend/services/duplicates.py, backend/tools/duplicates.py, backend/routers/duplicates.py |
| #168 | Notifications externes Discord / Telegram / SMTP / webhook | backend/notify.py, backend/tools/notify.py, backend/routers/notify.py |
| #170 | Tâches planifiées type cron | backend/scheduler.py, backend/tools/scheduled.py, backend/routers/scheduler.py |
Règle transverse respectée : tout nouvel outil = @tool + libellé
labels.py + clés i18n ai.step.* FR/EN + tests (cf. ai-tools-roadmap.md §2).
2. #166 — Doublons
- Score déterministe stdlib (
similarity_score) : Jaccard sur tokens (frontmatter exclu, accents conservés) à 70 % + similarité du titre/first-line (difflib) à 30 %. Pas de dépendance embeddings — l'index sémantique #70 reste un raffinement optionnel, pas un prérequis. - Scan borné : 500 fichiers
.mdmax, 200 Ko/fichier, pré-filtre Jaccard avant le score complet,truncatedexposé quand le plafond est atteint. - Fusion jamais sans filet : backup des 2 fichiers avant écriture,
outil
merge_duplicate_notesenDANGEROUS(carte « Tout approuver »), routePOST /api/duplicates/mergeexige{confirm: true}, stratégiesappend(défaut, avec marqueur d'origine) /prefer_target/prefer_source. - Outils :
find_duplicates(READ),merge_duplicate_notes(DANGEROUS).
3. #168 — Notifications externes
Canaux discord (webhook discord.com), telegram (Bot API + chat_id),
smtp (stdlib, STARTTLS + login) et webhook générique (JSON
{event, title, message, timestamp, source}).
- Secrets : jamais dans
notify_channels.json— storenotify_secrets.json(0600) ouOBSIGATE_NOTIFY_SECRET_<ID>(même motif que #9 / BUG-026) ; l'API n'expose que***+has_secret. Le token Telegram peut aussi venir deOBSIGATE_TELEGRAM_BOT_TOKEN. - SSRF :
validate_webhook_url/is_safe_targetréutilisés pour les webhooks génériques et Telegram ; Discord valide son préfixe d'URL. - Déclencheurs :
manual,schedule_failure,schedule_success,duplicate_found— choisis par canal.broadcast()n'échoue jamais en bloc (résultat par canal,last_errorpersisté). - CRUD admin (
/api/notify/channels), test d'envoi authentifié (POST /api/notify/test), outilnotify_external(WRITE → confirmation).
4. #170 — Tâches planifiées
Store data/scheduled_tasks.json (RLock, écriture atomique tmp+replace).
Actions = outils existants, aucun nouveau chemin d'écriture :
create_file/append_to_file→backend.services.mutations;notify→backend.notify.broadcast.
Planifications interval_hours (≥ 0,25), daily_time (HH:MM) et once_at
(ISO-8601, one-shot désactivé après exécution). tick() exécute les tâches
dues, enregistre last_status/last_error/run_count/next_run_at et émet
schedule_failure via #168 (sauf quand l'action elle-même est notify —
anti-récursion). Boucle de fond dans le lifespan de main.py (tick 60 s via
asyncio.to_thread, désactivable par OBSIGATE_SCHEDULER=0).
Outils : create_scheduled_task / list_scheduled_tasks /
delete_scheduled_task / run_scheduled_task_now (WRITE sauf list).
La création vérifie l'accès au vault et son existence (404 sinon).
5. API REST (toutes avec response_model)
| Route | Rôle |
|---|---|
GET /api/duplicates?vault&threshold&limit&subdir |
paires candidates |
POST /api/duplicates/merge |
fusion (confirm: true obligatoire) |
GET/POST /api/notify/channels, PATCH/DELETE /api/notify/channels/{id} |
CRUD admin |
POST /api/notify/test |
test broadcast ou canal ciblé |
GET/POST /api/scheduler/tasks, PATCH/DELETE /api/scheduler/tasks/{id}, POST …/run |
CRUD + exécution manuelle |
6. Tests
tests/test_duplicates.py (16), tests/test_notify_channels.py (14),
tests/test_scheduler.py (18) : services, routes (fixture client,
auth désactivée), outils (confirmation DANGEROUS vérifiée), stores isolés
en tmp, réseau mocké (jamais d'Internet en CI). Labels couverts par le
garde-fou test_tool_labels.py (tout outil IN_APP doit avoir son libellé).
7. Limites assumées (V1)
- Similarité lexicale (pas d'embeddings) — seuils réglables par l'agent.
- Pas d'UI dédiée (API + agent uniquement) ; les clés i18n des étapes existent déjà pour la section « N étapes ».
- Scheduler in-process (pas de persistance distribuée, tick 60 s) ; Redis/APScheduler resteraient l'option multi-workers.
- SMTP sans OAuth2 (login STARTTLS) ; Slack natif non ciblé (webhook
générique compatible
incoming-webhookutilisable tel quel).