- Table agent_connectors (migration 32) : colonnes plates name, url,
secret_encrypted, enabled, status, detail — config_json remis (YAGNI, les
scopes OAuth des phases 6-7 ajouteront le leur).
- app/services/connectors.py : 1 fichier au lieu du package connectors/ —
3 natifs (gitea, github, web) servis à la volée avec statut sans réseau,
CRUD des personnels (URL validée par _validate_url = garde SSRF, clé chiffrée
Fernet et jamais renvoyée — seul has_secret), probe() qui persiste
status/detail, connector_fetch() borné à 20 000 car.
- API /api/agent/connectors : GET, POST (400 URL privée), PATCH, DELETE,
POST /connectors/probe — 401 sans session, CSRF global. OpenAPI 519 chemins.
- Outil LLM connector_fetch (26e outil) : un seul outil qui dispatche vers
Gitea/GitHub/web/personnalisé (id ou nom ou kind + path + query) au lieu d'un
outil par connecteur ; désactivé/inconnu = erreur outil, jamais de run cassé.
- Menu + : section « Connecteurs » (catalogue avec badge ✓/✗/⚠/?), fiche par
connecteur (Tester, Activer/Désactiver, Supprimer — masqués pour les natifs),
formulaire « Ajouter un connecteur personnalisé » (clé en type=password).
- Tests : tests/test_v755_connectors.py (13) — 3 natifs, roundtrip Fernet de la
clé, 5 URLs refusées (localhost, 127.0.0.1, metadata cloud, ftp:, file:),
toggle persisté, 401, probe OK/erreur, outil complet, câblage menu ;
test_v751/v753/v754 adaptés (1 seule section « bientôt » = plugins).
Suite complète 1306 verts (-n auto), ruff 0, eslint 0 problème.
- Le moteur n'envoie jamais l'historique (system + objectif courant seulement)
: chaque run repartait de zéro. Nouveau service app/services/agent_memory.py —
une ligne résumé par conversation (table agent_memory, migration 31),
écrite en fin de run et ré-injectée au contexte du run suivant.
- context_for() : bloc « ## Mémoire de la conversation », budget 4000 car.
(troncature gauche + marqueur). remember() : 1 note/échange (180 → 700 car.),
20 notes max, upsert, erreurs journalisées — un run ne peut pas échouer à
cause de la mémoire. Toggle OFF = ni lecture ni écriture.
- Toggle « Mémoire » dans le menu + : entrée racine à l'état réel (🧠/💭 +
libellé), PATCH /api/agent/conversations/{id} avec memory_enabled ajouté au
whitelist, état lu à l'ouverture et à la création, défaut AGENT_MEMORY_DEFAULT
(settings.agent_memory_default) + memory_enabled dans la réponse de création.
- Décision : mémoire d'espace (lignes workspace_id NULL) remise — YAGNI, rien
ne l'écrivait ; noté ponytail: dans le service avec le chemin de montée.
- Tests : tests/test_v754_memory.py (8) dont 2 runs réels du moteur (FakeLLM
capture le prompt : mémoire présente ON / absente OFF) ; test_v751 et
test_v753 adaptés (2 sections « bientôt » restantes). Suite complète 1293
verts (-n auto, 2e run ; 1 échec intermittent sur test_v56_import URL au
1er run, vert isolé + vert au run suivant = flake préexistant), ruff 0,
eslint 0 problème. OpenAPI 516 chemins / 7.54.0.
- Sous-menu « Compétences-skills » : 11 skills builtin + skills enregistrés,
libellés affichés « Deep research » (/research) et « Skill-creator »
(/create-new-skill) via FD_SKILL_ALIAS (slugs intacts) ; le clic épingle le
skill au contexte, même chemin que la palette /.
- « Gérer les compétences » : section du menu + formulaire intégré (nom,
description, prompt) — création, édition, suppression (chip épinglé nettoyé),
export JSON téléchargé, import fichier avec overwrite (pas de doublon).
- « Parcourir les compétences » : section galerie filtrable (nom/description/
slug), installation en 1 clic (installGallerySkill réutilisé).
- PATCH /api/agent/skills/{skill_id} créé : édition partielle, whitelist de
colonnes, 400/401/404/409. OpenAPI régénéré : 515 chemins, version 7.52.0.
- En-tête ← + titre de section dans le menu (navigation à 3 niveaux).
- Fix : épingler un skill enregistré n'efface plus le message déjà tapé
(_pinDbSkill ne vide le composer que s'il est vide).
- Tests : tests/test_v752_skills_menu.py (7) + test_v751 adapté. Suite
complète 1280 verts (-n auto), ruff 0, eslint 0 problème.
- Conversion `async def` → `def` de TOUTES les routes dont le corps ne contient
ni `await`, ni `async with`, ni `async for`, ni `asyncio` (scan automatique
corps par corps sur app/ : 352 converties, 0 dangereuses, vérifié
`asyncio`/`run_coroutine`/`.result()` absents). FastAPI exécute ces handlers
dans son threadpool → tout leur SQLite (`get_conn()` + `conn.execute`) quitte
l'event loop, sans changer une ligne de logique.
- Répartition : api_v2 60, dashboard 40, collections 25, board 23,
workspace 19, wiki 17, permissions 14, api 14, main.py 6, + 35 fichiers.
- Les 4 routers prioritaires de l'audit sont couverts par ce lot :
api_v2 60 + dashboard 40 + collections 25 + board 23 = 148 conversions
(le reste de leurs routes attend la phase 2 : elles ont de vrais `await`).
- Reste (phase 2) : les 311 routes avec de vrais `await` → enrouler les blocs
DB dans `await anyio.to_thread.run_sync(...)` ; pas de wrapper partagé livré
(rien ne l'appellerait — YAGNI jusqu'au premier usage).
suite **1037/1037** (233 s) · `ruff check app tests` OK · docs à jour
- A26 — champ `standalone` mort supprimé (0 lecteur) ; `sqlite:////data/…` ne
produit plus de chemin UNC sous Windows ; `.env.example` ne promet plus
PostgreSQL ; raise au boot si `APP_SECRET_KEY` vaut la valeur par défaut
- A33 — rate limit : préfixes manquants (`/scim/v2/`, `/workspace/`, `/db/` +
non-GET sur `/s/` et `/f/`), limite lue dans `settings.rate_limit_requests`
(60 annoncés / 100 codés en dur), clé `X-Forwarded-For` seulement derrière un
proxy local (anti-spoof), `_store` épuré (mémoire bornée) + test dédié
- A34 — `_spawn()` : les 10 schedulers loggent leur exception et redémarrent
après 10 s au lieu de mourir en silence ; 2 `logger.debug` de scheduler → warning
- A35 — OpenAPI régénéré 439 → 511 chemins (`info.version 7.3.9`), README
(était v6.7.0), compteur API_GUIDE, titre dupliqué retiré du ROADMAP ;
le drift Python 3.12/3.13 reste noté (rebuild d'image à valider)
- A36 — 4 dépendances mortes purgées de requirements.txt (aiosqlite, slowapi,
loguru, packaging = 0 import) ; pyproject reste sans [project] : Docker et la
CI installent requirements.txt, dupliquer les 22 deps créerait 2 sources
- A43 — 15 `datetime.utcnow()` → `now(UTC).replace(tzinfo=None)` (format ISO
naïf identique, warnings de dépréciation divisés : 2374 → 926)
suite **1029/1029** · `ruff check app tests` OK · docs/ROADMAP/CHANGELOG/WORKLOAD à jour
- `_current_user_id` : 401 sans session (24 sites) au lieu de retomber sur
`SELECT id FROM users WHERE login='admin'`
- `_current_admin` : suppression du même fallback — `PATCH /api/agent/providers`
et `POST /api/agent/providers/test` (donc `LLMClient.ping(api_base=…)`)
exigent une session admin : 401 sans session, 403 non-admin
- `_check_api_base()` sur les 2 routes : scheme http(s), pas d'identifiants
dans l'URL (400) ; hôtes privés maintenus — Ollama `localhost:11434` est le
provider par défaut du produit (commentaire `ponytail:` pour la fermeture)
- +1 test de non-régression → suite **1027/1027**, `ruff check app tests` OK
Un api_base stocke (ex. https://api.mistral.ai/v2) ecrasait l'URL par defaut corrigee et faisait echouer le test de connexion. Desormais: normalisation a l'ecriture (une base egale au defaut n'est pas stockee), possibilite de vider le champ (api_base='' vs None), et migration 14 qui efface les bases obsoletes/redondantes dans user_llm_keys et llm_config.
Tests de non-regression ajoutes.
tests/conftest.py: mutate the settings singleton (instead of rebinding) so DB + backup dir are isolated per test -> pytest-xdist safe.
Real backup tests (snapshot/prune/admin API) and OAuth mock tests (Gitea/GitHub/link) replace the previous skips.
init_db() now also creates webhook_subscriptions (full schema without the FastAPI lifespan).
ruff check is clean; .eslintrc.json migrated to eslint.config.mjs (flat config).
CI: lint job (ruff + eslint), parallel tests (-n auto), run on every branch push.
VERSION 5.11.1.
- Nouveau bouton « 🗑 Réinitialiser » dans Réglages → Agent & IA (réservé aux
admins) : bascule le fournisseur par défaut sur « hors-ligne » et efface la
clé API/base/état vérifié stockés (clear_keys dans set_llm_config).
- Confirmation avant suppression des clés API utilisateur (deleteUserKey).
- Correctif : chaîne JS de confirm("…") sur deux lignes provoquait une erreur
de syntaxe → le script alpine:init ne s'exécutait pas et la page paramètres
restait figée sur « Agent & AI » ; la chaîne est désormais sur une ligne.
- /api/agent/mentions : reponse en sections (fichiers de l'espace courant ouvert, collections, pages de collections, autres documents), workspace_id transmis par le frontend (localWsId) et prioritaire
- Navigation clavier up/down : la liste defile pour garder la selection visible (menus @ et /)
- Deduplication des propositions (meme type + meme titre affiches une seule fois)
- Skills : plus d'injection du texte du skill dans la boite d'edition; requete composee en arriere-plan (contexte + skills + texte) a l'envoi; les skills integres injectent leur template, les enregistres partent via skill_ids
- Tests : shape des mentions, dedup, scoping workspace
- Les skills (menu /, integres et enregistres) s'epinglent comme des bulles dans le composer, au meme titre que les mentions @; plusieurs peuvent coexister sur un meme post
- L'API /run accepte skill_ids (tableau) et l'engine injecte tous leurs prompts dans le system prompt (skill_id conserve pour retro-compat)
- Clic sur une bulle de contexte (@ document/page/base) ouvre l'element comme document courant (/pages/<id> ou /db/<id>); le bouton x retire sans ouvrir
- Envoi possible avec des skills seuls sans texte
- test_engine_multiple_skills_applied
- Composeur contenteditable : mentions inline (pastilles icône+nom), barre de contexte groupée documents/skills, sélecteur @ (icône+titre+chemin), skills intégrés + agent_skills + commandes admin via '/'
- Actions sous chaque réponse agent : copier, insérer dans la page, feedback 👍/👎
- Backend : table agent_feedback, GET /api/agent/mentions, POST /api/agent/feedback, contexte document:<id>/page:<id>/collection:<id> résolu en contenu réel
- Inclut le travail v4.14.0 (documents/espace de travail + outils + auto-titre de conversation)