docs: plan par phases du menu + de l'assistant + design V74
FlowDeck CI / lint (push) Successful in 2m12s
FlowDeck CI / test (push) Failing after 13m39s
FlowDeck CI / docker (push) Skipped

- ROADMAP : section « v7.51.0 → v7.58.0 — Menu + de l'assistant »
  (8 phases : menu hub/fichiers-répertoires, compétences, canevas,
  mémoire, connecteurs socle, Google+M365, Discord/Telegram/MCP, plugins),
  état des lieux lu dans le code, aucun case cochée.
- docs/V74_Agent_Plus_Menu.md : maquette, découpage front/back, modèle de
  données, sécurité (SSRF, secrets Fernet, CSRF), décisions D1-D7,
  pièges repo, critères d'acceptation par phase.
This commit is contained in:
2026-10-06 19:35:22 -04:00
parent 81746d9440
commit f006880394
2 changed files with 331 additions and 0 deletions
+129
View File
@@ -284,6 +284,135 @@ Propriétés custom, AI keywords, sync API, 12 tables DB
---
## v7.51.0 → v7.58.0 — Menu + de l'assistant : hub de contexte (planifié 2026-10-06)
> **Objectif** : faire du bouton **+** du panneau agent (à gauche de la zone d'édition)
> un menu à sections, tel que demandé :
>
> ```
> + ├─ Ajouter des fichiers ou répertoires … → picker actuel + parcours de l'arborescence
> ├─ Compétences-skills … … … … … … … … → Deep research, Skill-creator, … + Gérer + Parcourir
> ├─ Connecteurs … … … … … … … … … … … → Parcourir + Ajouter un connecteur personnalisé
> ├─ Design System – Canevas … … … … … → galerie de canevas (templates de page)
> ├─ Add plugins … … … … … … … … … … … → catalogue de modules on/off
> └─ Mémoire (on/off) … … … … … … … … → mémoire persistante de l'agent
> ```
>
> **Doc de design** : [`docs/V74_Agent_Plus_Menu.md`](docs/V74_Agent_Plus_Menu.md)
> — maquette, découpage front/back, modèle de données, endpoints, décisions, pièges.
**État des lieux lu dans le code le 2026-10-06** (rien n'est livré, tout est `- [ ]`) :
- Le **+** = `toggleAddPicker()` (`static/js/agent_panel_2.js:1074`) → menu **plat** de
mentions (`GET /api/agent/mentions`, `app/routers/agent.py:596`) : documents,
collections, pages. **Pas de dossiers, pas de sous-menu, pas de section.**
- **Compétences** : 11 skills builtin (`FD_SKILLS`, `static/js/agent_panel_2.js:11`) +
table `agent_skills` (CRUD `/api/agent/skills`) + galerie de 17 presets — mais
**aucune UI « Gérer » ni « Parcourir »** : tout passe par la palette `/`.
- **Canevas** : `GET /api/page-templates` (built-ins `app/services/block_templates.py` +
personnels) et `POST /api/page-templates/{id}/use` existent déjà, **jamais appelés
depuis le panneau agent**.
- **Connecteurs / Plugins / Mémoire** : **inexistants** — 0 hit `connector|plugin|memory`
dans `app/` hors « in-memory ».
**Ordre** : phases 1 → 8, chacune indépendante et livrable (version + tests verts +
CHANGELOG). Chiffrage : S < ½ j · M = 1–2 j · L = 3–5 j.
### Phase 1 — v7.51.0 — Menu hub + fichiers/répertoires · effort S–M
- [ ] **Structure du menu** — sections + sous-menus, navigation clavier (↑/↓/Entrée/Échap),
styles réutilisés depuis `.fd-ap-mention-menu` ; les sections pas encore livrées
s'affichent « bientôt » et désactivées (le menu ne promet rien que le code ne fait)
- [ ] **Fichiers & répertoires** — picker actuel (recherche) **+ mode « Parcourir… »** :
arborescence via `GET /api/local-workspace/tree?folder=` + enfants de page
(`parent_id`) ; un répertoire s'épingle comme un fichier
- [ ] **Jeton `folder:<id>`** résolu par `ContextBuilder._mentions_context`
(contenu des pages du dossier, borné en tokens)
- [ ] **Tests** — parcours, résolution du jeton dossier, rendu/états du menu
- [ ] **Livraison** — `ruff` + suite verte, bump `VERSION` + `app/main.py`, CHANGELOG
### Phase 2 — v7.52.0 — Compétences : « Gérer » + « Parcourir » · effort M
- [ ] **Sous-menu Compétences** — builtin + installés (badge « installé »), le clic
insère le template dans le composer (comportement `/` actuel conservé)
- [ ] **Libellés affichés** — alias visibles « Deep research » (`/research`) et
« Skill-creator » (`/create-new-skill`), sans casser les slugs existants
- [ ] **Modale « Gérer les compétences »** — CRUD sur `POST/PATCH/DELETE /api/agent/skills`
(nom, description, prompt, outils autorisés) + export/import
(`/skills/{id}/export`, `/skills/import`)
- [ ] **Modale « Parcourir les compétences »** — galerie `GET /api/agent/skills/gallery`,
installation en 1 clic, filtre
- [ ] **Tests** — CRUD, install/désinstall, pas de doublon à l'import
### Phase 3 — v7.53.0 — Design System – Canevas · effort M
- [ ] **Sous-menu Canevas** — `GET /api/page-templates` (built-ins + personnels)
- [ ] **Deux actions** — « Créer une page à partir du canevas »
(`POST /api/page-templates/{id}/use`) et « Insérer dans le document ouvert »
(blocs poussés dans l'éditeur quand un document est ouvert)
- [ ] **Canevas « design system »** — presets maison dans `block_templates.py`
(callout, TOC, colonnes, grille de composants) réutilisant `design-tokens.css`
- [ ] **Sauver le document ouvert comme canevas** — `POST /api/page-templates` existe
- [ ] **Tests** — création, insertion dans l'éditeur, sauvegarde
### Phase 4 — v7.54.0 — Mémoire de l'agent · effort M
- [ ] **Table `agent_memory`** (`workspace_id`, `kind` summary|fact, `content`,
`updated_at`) + colonne `conversations.memory_enabled`
- [ ] **Service `app/services/agent_memory.py`** — extraction en fin de run (résumé +
faits saillants ; LLM sans outils, repli déterministe hors-ligne)
- [ ] **Injection dans `ContextBuilder` si le toggle est ON** ; OFF = contexte strict
de la conversation courante
- [ ] **Toggle « Mémoire »** dans le menu + : état visible, persisté par conversation,
défaut `AGENT_MEMORY_DEFAULT`
- [ ] **Tests** — injection ON / absence OFF / persistance / budget de tokens borné
### Phase 5 — v7.55.0 — Connecteurs : socle · effort L
- [ ] **Table `agent_connectors`** (`kind`, `name`, `config`, credentials chiffrés,
`enabled`, `status`)
- [ ] **`app/services/connectors/`** — interface commune `probe() / list_sources() / fetch()`
+ 3 adapters **natifs déjà présents** : Gitea, GitHub, Web (`web_search` / `fetch_url`, v7.46)
- [ ] **Menu « Connecteurs »** — « Parcourir les connecteurs » (catalogue + statut
connecté/déconnecté) et « Ajouter un connecteur personnalisé » (URL + clé + type
HTTP / MCP)
- [ ] **Tools agent** — un tool par connecteur exposé au LLM via `tool_registry`
- [ ] **Sécurité** — garde SSRF `_is_public_host` sur toute URL, credentials chiffrés,
session requise, CSRF sur les écritures
- [ ] **Tests** — CRUD, statut, vecteurs SSRF, schéma des tools
### Phase 6 — v7.56.0 — Connecteurs : Google + Microsoft 365 · effort L
- [ ] **OAuth2 PKCE Google** — Drive, Gmail, Calendar : connexion, refresh, déconnexion,
lecture limitée aux sources choisies
- [ ] **OAuth2 Microsoft (Graph)** — Outlook / OneDrive / SharePoint
- [ ] **Écran connecteur** — état, scopes, dernière synchro, bouton déconnecter
- [ ] **Tests** — callbacks, refresh, échec, **aucun appel réseau réel** (transport injecté)
### Phase 7 — v7.57.0 — Connecteurs : Discord, Telegram, Teams, MCP · effort L
- [ ] **Discord** (bot token : lecture canaux/DMs), **Telegram** (Bot API),
**Teams** (via Graph de la phase 6)
- [ ] **MCP complet** — handshake `initialize`, `tools/list`, `tools/call`
(streamable HTTP) → outils **dynamiques** ajoutés au registre à la connexion
- [ ] **Tests** — schémas MCP, mapping d'outils, échecs d'auth
### Phase 8 — v7.58.0 — Add plugins · effort M
- [ ] **Registre `plugins`** (`slug`, `name`, `description`, `enabled`) — état persisté
- [ ] **Catalogue dans le menu +** — bascule on/off à effet **réel** : un plugin
désactivé retire vraiment sa route/UI (au minimum 3 plugins câblés : web tools de
l'agent, web clipper, automations)
- [ ] **Tests** — plugin OFF = route refusée + UI absente (pas seulement un drapeau lu)
---
*Plan produit le 2026-10-06 à partir du code réel (aucune phase commencée) — à valider
phase par phase avant « go ». Chaque phase se clôt par tests verts, bump de version,
CHANGELOG et ROADMAP à jour.*
---
## 🔜 Prochaines versions
### v4.0.1 — Onboarding & Polish ✅ (2026-07-18)
+202
View File
@@ -0,0 +1,202 @@
# V74 — Menu + de l'assistant : hub de contexte (design)
> **Statut** : design — aucune phase n'est commencée.
> **Plan phasé** : `ROADMAP.md` → section « v7.51.0 → v7.58.0 — Menu + de l'assistant ».
> **Date** : 2026-10-06 · Version cible de départ : **v7.51.0** (phase 1).
---
## 1. Objectif
Le bouton **+** du panneau agent (`app/templates/agent_panel.html:409`) ouvre
aujourd'hui une liste plate de fichiers/pages/bases à épingler en contexte. On le
transforme en **menu à sections à deux niveaux** :
```
+ ┌──────────────────────────────────────────────────────┐
│ 📎 Ajouter des fichiers ou répertoires › │ → picker actuel + « Parcourir… »
│ ✨ Compétences-skills › │ → Deep research, Skill-creator, …
│ │ ────────────────
│ │ Gérer les compétences
│ │ Parcourir les compétences
│ 🔌 Connecteurs › │ → Parcourir les connecteurs
│ │ Ajouter un connecteur personnalisé
│ 🎨 Design System – Canevas › │ → liste des canevas
│ 🧩 Add plugins │ → catalogue on/off (modale)
│ 🧠 Mémoire [◉/○] │ → toggle persisté
└──────────────────────────────────────────────────────┘
```
Règle d'or : **chaque entrée vit dans la même phase qui la rend réelle**. Tant qu'une
section n'est pas livrée, elle s'affiche grisée « Bientôt (phase N) » — le menu ne
promet rien que le code ne fait.
## 2. État des lieux (lu dans le code, 2026-10-06)
| Section | Existe | Manque |
|---|---|---|
| Fichiers / répertoires | `GET /api/agent/mentions` (`app/routers/agent.py:596`) → documents, collections, pages ; jetons `document:id`, `collection:id`, `page:id` résolus par `ContextBuilder._mentions_context` (`app/services/context_builder.py:120`) | Sous-menu, mode parcours d'arborescence, jeton `folder:<id>` |
| Compétences | `agent_skills` CRUD (`/api/agent/skills` : GET/POST/DELETE, apply, import/export), galerie de 17 presets (`app/services/skill_gallery.py`), 11 skills builtin (`FD_SKILLS`, `static/js/agent_panel_2.js:11`) | UI « Gérer » (CRUD complet : **`PATCH /skills/{id}` à créer**), UI « Parcourir » (la galerie n'est accessible que via la palette `/`), libellés affichés type « Deep research » |
| Canevas | `GET /api/page-templates` (built-ins `app/services/block_templates.py` + personnels), `POST /api/page-templates/{id}/use` (`app/routers/board/page_api.py:75,135`) | Menu dans le panneau agent, action « insérer dans le document ouvert », canevas « design system » |
| Connecteurs | Rien (0 hit `connector` dans `app/`). Natifs déjà branchés ailleurs : Gitea (`gitea_client.py`), GitHub (`github_adapter.py`), Web (`web_search.py` + tool `fetch_url` v7.46) | Table, service d'adapters, catalogue UI, tools agent, OAuth Google/M365, Discord/Telegram, MCP |
| Plugins | Rien | Registre, catalogue UI, **câblage réel** (désactivation effective) |
| Mémoire | Rien (l'historique de la conversation est déjà envoyé, ce n'est pas de la mémoire) | Table, service d'extraction, injection conditionnelle, toggle persisté |
## 3. Découpage front (Alpine)
Tout vit dans `static/js/agent_panel_2.js` (composant `agentPanel()`) + un bloc de
markup dans `app/templates/agent_panel.html`, sans nouveau fichier JS.
**État ajouté**
```js
plusOpen: false, // menu ouvert
plusSection: 'root', // 'root' | 'files' | 'skills' | 'connectors' | 'canvases'
plusFocus: -1, // index de l'item focalisé
plusItems: [], // items de la section courante (fetch si besoin)
memoryOn: true, // état du toggle (phase 4)
```
**Items = données, pas du markup en dur** : un tableau `FD_PLUS_MENU`
`{key, icon, label, sub, action, phase}` → le rendu et la navigation clavier
(↑/↓/Entrée/Échap) sont une seule boucle. Les sections `phase > livrée` sont
`disabled: true`.
**Réemploi** : le conteneur et les styles `.fd-ap-mention-menu` /
`.fd-mention-item` (`agent_panel.html:171-181`) servent déjà exactement ce
comportement — on duplique le CSS minimal (`.fd-plus-*`) et on garde la logique de
focus/scroll (`moveMentionFocus`, `_scrollMentionItem`) comme modèle.
**Entrées existantes préservées** : la touche `@` dans le composer et le mode
recherche du `+` continuent de fonctionner à l'identique (régression zéro) ; le `+`
devient seulement *l'entrée en menu* au lieu d'ouvrir directement la liste.
## 4. Backend par phase
| Phase | Existant réutilisé | À créer |
|---|---|---|
| 1 — menu + fichiers/répertoires | `/api/agent/mentions`, `/api/local-workspace/tree?folder=`, enfants de page (`parent_id`, `local_workspace.py:243`) | Route(s) de parcours adaptée(s) au panneau (shape items compatible mention), jeton `folder:<id>` dans `ContextBuilder` |
| 2 — compétences | `/api/agent/skills` (GET/POST/DELETE/apply/import/export), `/api/agent/skills/gallery` + install | `PATCH /api/agent/skills/{id}` (édition), 2 modales |
| 3 — canevas | `/api/page-templates` (GET/POST/use) | Insersion dans l'éditeur ouvert (API d'insertion de blocs de l'éditeur), presets « design system » dans `block_templates.py` |
| 4 — mémoire | `ContextBuilder`, `PATCH /conversations/{id}` (pattern du toggle) | Table `agent_memory`, `app/services/agent_memory.py`, colonne `conversations.memory_enabled`, config `AGENT_MEMORY_DEFAULT` |
| 5 — connecteurs (socle) | `_is_public_host` (SSRF), `encrypt_secret` (`sso_provisioning.py:51`), `http_client.shared_client`, `tool_registry` | Table `agent_connectors`, `app/services/connectors/`, 3 adapters natifs (gitea/github/web), catalogue UI |
| 6 — Google + M365 | `auth/oauth.py` (client OAuth2), `encrypt_secret`, `shared_client` | Flows PKCE Google + Microsoft Graph, table de tokens (motif `calendar_sync.py:38`), écran connecteur |
| 7 — Discord / Telegram / Teams / MCP | Graph (phase 6), pattern de tools dynamiques du registre | 3 adapters + client MCP (`initialize`, `tools/list`, `tools/call`) |
| 8 — plugins | — | Table `plugins`, catalogue UI, désactivation réelle (routes + UI) |
## 5. Modèle de données
```sql
-- Phase 4 — mémoire
ALTER TABLE conversations ADD COLUMN memory_enabled INTEGER NOT NULL DEFAULT 1;
CREATE TABLE agent_memory (
id INTEGER PRIMARY KEY,
workspace_id INTEGER NOT NULL,
conversation_id INTEGER, -- NULL = mémoire d'espace (partagée)
kind TEXT NOT NULL DEFAULT 'summary', -- summary | fact
content TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX idx_agent_memory_ws ON agent_memory(workspace_id, conversation_id);
-- Phase 5 — connecteurs
CREATE TABLE agent_connectors (
id INTEGER PRIMARY KEY,
workspace_id INTEGER,
kind TEXT NOT NULL, -- gitea | github | web | google | ms365 | discord | telegram | mcp | custom
name TEXT NOT NULL,
config_json TEXT NOT NULL DEFAULT '{}', -- URL, scopes, channels…
secret_encrypted TEXT, -- Fernet (encrypt_secret, sso_provisioning.py:51)
enabled INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'unknown', -- ok | error | unknown
last_probe_at TEXT,
created_by INTEGER
);
-- Phase 8 — plugins
CREATE TABLE plugins (
slug TEXT PRIMARY KEY, -- web-tools | web-clipper | automations | …
enabled INTEGER NOT NULL DEFAULT 1,
updated_at TEXT NOT NULL
);
```
Migrations ajoutées dans `app/migrations.py` (motif `_migration_*` existant,
transaction par migration — A31).
## 6. Sécurité (non négociable)
- **SSRF** : toute URL de connecteur passée par `_is_public_host`
(`app/services/importers/url_fetch.py`), re-vérifiée à chaque saut de redirection.
- **Secrets** : jamais en clair, jamais renvoyés par l'API (champ retiré des
sérialiseurs) — `encrypt_secret()` existant, dérivé de `app_secret_key`.
- **Auth + CSRF** : session requise sur tout CUD ; `/api/agent` est sur la liste des
préfixes CSRF cookie → header `X-CSRF-Token` sur les nouveaux `fetch` (helper
`getCsrf()`).
- **Borne de tokens** : la mémoire et les sources de connecteurs injectées dans le
contexte sont tronquées (budget explicite) — sinon la phase 4 fait exploser le
coût de chaque run.
- **Plugins OFF = effet réel** : un simple drapeau affiché serait un menu qui ment
(règle du projet).
## 7. Décisions techniques
- **D1 — Un seul menu, deux niveaux** (pas de modale racine) : ouverture instantanée,
clavier, et le `+` reste un geste unique. Les modales ne servent qu'aux écrans
lourds (Gérer / Parcourir / Connecteurs / Plugins).
- **D2 — Pas de nouveau fichier JS** : tout dans `agent_panel_2.js` + markup du
template, conforme à l'extraction A27 (un fichier par bloc).
- **D3 — États « Bientôt » visibles** plutôt que sections masquées : le menu sert de
spec vivante et évite d'implémenter6 features d'un coup.
- **D4 — La mémoire est un résumé structuré, pas l'historique brut** : réinvoquer
tout l'historique à chaque run coûterait plus cher que la réponse ; un résumé borné
(~1–2 k tokens) + les faits saillants suffisent, avec repli déterministe hors-ligne
(motif `ai_writing.py`).
- **D5 — Connecteurs = registry + adapters** (interface `probe/list_sources/fetch`),
pas un if par provider : la phase 7 (MCP) ajoute des adapters sans toucher au socle.
- **D6 — Canevas : « créer » par défaut, « insérer » seulement si un document est
ouvert** — l'insertion dans l'éditeur est le point le plus fragile (blocs +
sélection), donc livrée en second dans la phase 3.
- **D7 — Pas de dépendance ajoutée** : OAuth via `httpx` (`shared_client`), chiffrement
via `cryptography` déjà présent, MCP en HTTP direct.
## 8. Pièges du repo à respecter
- `ROADMAP.md` / `CHANGELOG.md` sont en **CRLF** : `patch` exige des lignes complètes.
- **Routes statiques avant les wildcards** (`/skills/gallery` avant `/skills/{id}`),
sinon 422 sur un id non numérique.
- **Events `AgentEngine.run()` plats** (`{"type": …, "content": …}`), pas enveloppés
dans `data`.
- Bumper **`VERSION` + `app/main.py`** à chaque phase, régénérer
`docs/openapi-v2.json` (CRLF, sans newline final) si de nouvelles routes sortent.
- Le panneau agent est rendu sur **toutes les pages** (`base.html:2555`) : le JS doit
rester défensif (`window.appState` absent, page non authentifiée).
## 9. Critères d'acceptation par phase
1. **Phase 1** : le `+` ouvre le menu, « Parcourir… » montre l'arborescence, un
dossier épinglé apparaît en chip et son contenu est bien dans le contexte du run.
2. **Phase 2** : créer/éditer/supprimer un skill depuis la modale, l'installer depuis
la galerie, il apparaît dans la palette `/` et le sous-menu.
3. **Phase 3** : choisir un canevas crée la page (et l'insertion dans le document
ouvert fonctionne quand un doc est édité).
4. **Phase 4** : ON → la mémoire est injectée (test qui le prouve) ; OFF → absente ;
l'état survit au rechargement et au changement de conversation.
5. **Phase 5** : connecteur créé/testé/supprimé, URL privée refusée (SSRF), tool
exposé au LLM avec le bon schéma.
6. **Phase 6** : connexion Google et Microsoft, refresh, déconnexion, aucun appel
réseau réel dans les tests.
7. **Phase 7** : Discord/Telegram branchés, MCP `tools/list` → outils visibles dans le
registre.
8. **Phase 8** : plugin OFF = route refusée + UI disparue (test de non-réintroduction).
Chaque phase se clôt par : `ruff` + suite verte, bump de version, CHANGELOG, ROADMAP
à jour, tag.
## 10. Hors périmètre
- Écriture dans les sources distantes (Google Docs, Slack…) — lecture seule au départ.
- Synchronisation d'arborescence locale ↔ connecteur.
- Marketplace de plugins tiers (le registre est interne à l'instance).
- Refonte du composer / de la palette `/` (compatibilité maintenue à l'identique).