Files
ObsiGate/docs/features/agent-phase4-166-168-170.md
T
bruno 4ce677902a
CI / lint (push) Successful in 2m53s
CI / security (push) Successful in 2m5s
CI / test (push) Successful in 4m52s
CI / build (push) Successful in 2m5s
CI / e2e (push) Successful in 16m14s
feat: agent IA phase 4 — doublons #166, notifications externes #168 (Discord/Telegram/SMTP/webhook), taches planifiees #170
2026-10-04 13:11:25 -04:00

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 .md max, 200 Ko/fichier, pré-filtre Jaccard avant le score complet, truncated exposé quand le plafond est atteint.
  • Fusion jamais sans filet : backup des 2 fichiers avant écriture, outil merge_duplicate_notes en DANGEROUS (carte « Tout approuver »), route POST /api/duplicates/merge exige {confirm: true}, stratégies append (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 — store notify_secrets.json (0600) ou OBSIGATE_NOTIFY_SECRET_<ID> (même motif que #9 / BUG-026) ; l'API n'expose que *** + has_secret. Le token Telegram peut aussi venir de OBSIGATE_TELEGRAM_BOT_TOKEN.
  • SSRF : validate_webhook_url / is_safe_target ré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_error persisté).
  • CRUD admin (/api/notify/channels), test d'envoi authentifié (POST /api/notify/test), outil notify_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-webhook utilisable tel quel).