From f006880394a5fea74aa1fe380f6156a74cd95a4d Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 6 Oct 2026 19:35:22 -0400 Subject: [PATCH] docs: plan par phases du menu + de l'assistant + design V74 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- ROADMAP.md | 129 +++++++++++++++++++++++ docs/V74_Agent_Plus_Menu.md | 202 ++++++++++++++++++++++++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 docs/V74_Agent_Plus_Menu.md diff --git a/ROADMAP.md b/ROADMAP.md index 6c09033..c507e8e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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:`** 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) diff --git a/docs/V74_Agent_Plus_Menu.md b/docs/V74_Agent_Plus_Menu.md new file mode 100644 index 0000000..3c83037 --- /dev/null +++ b/docs/V74_Agent_Plus_Menu.md @@ -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:` | +| 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:` 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).