Files
flowdeck/docs/V74_Agent_Plus_Menu.md
T
bruno 18a2d45d46
FlowDeck CI / lint (push) Successful in 2m11s
FlowDeck CI / test (push) Failing after 13m54s
FlowDeck CI / docker (push) Skipped
feat: menu + — Compétences-skills « Gérer » + « Parcourir » — phase 2/8 (v7.52.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.
2026-10-06 21:39:24 -04:00

207 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# V74 — Menu + de l'assistant : hub de contexte (design)
> **Statut** : design — **phases 1 et 2 livrées (v7.51.0, v7.52.0, 2026-10-06)** ;
> phases 3-8 restent à livrer. Adaptations faites en cours de route : le parcours
> utilise `GET /api/nav/menu?parent_id=` (un niveau par appel, contrat
> dossier = `icon === 'folder'`) et la gestion des skills est une **section du menu
> avec formulaire intégré** (pas une modale) — `PATCH /api/agent/skills/{id}` ajouté.
> **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).