feat: Add plugins — catalogue on/off à effet réel (v7.58.0, phase 8/8)
FlowDeck CI / lint (push) Successful in 2m14s
FlowDeck CI / docker (push) Canceled after 0s
FlowDeck CI / test (push) Canceled after 2h7m52s

- app/services/plugins.py + migration 35 : table plugins (slug, name,
  description, enabled) pré-remplie avec 3 modules câblés — web-tools,
  web-clipper, automations ; ligne absente = activé (défaut sûr)
- automations OFF → dépendance FastAPI posée à l'include_router dans main.py
  (aucun router touché) → toutes les routes /workspace/automations* refusées +
  garde de tick du scheduler en arrière-plan
- web-clipper OFF → GET /extensions et tout /api/v2/web-clipper/* refusés
- web-tools OFF → web_search et fetch_url retirés du schéma ET de execute()
  via ToolRegistry._all() : le LLM ne les voit plus
- UI rendue côté serveur : global Jinja plugin_enabled(slug) — nav
  « Extensions » / « Automations » en {% if %} (absentes du DOM), sections
  conditionnées en x-show dans settings.html
- menu + : l'entrée « Add plugins » devient vivante (fini disabled:true) —
  liste des 3 plugins avec bascule, GET/PATCH /api/agent/plugins[/slug]
  (slug inconnu → 404, 401 sans session)
- tests : tests/test_v758_plugins.py (10 tests) — routes refusées (302 hors
  /api, 404 JSON pour /api*), outils retirés, nav disparue, persistance,
  câblage ; assertions disabled:true == 0 dans les tests des phases 1/3/4/5/7
- livraison : VERSION + app/main = 7.58.0, OpenAPI 525 chemins, CHANGELOG,
  ROADMAP phase 8 cochée (menu + complet), avenant phase 8 (docs)
This commit is contained in:
2026-10-07 10:19:25 -04:00
parent f8acb906e5
commit 16f73fe39e
20 changed files with 495 additions and 35 deletions
+41 -2
View File
@@ -44,7 +44,7 @@
{key:'canvases', icon:'🎨', label:'Design System – Canevas',
sub:'Galerie de canevas : créer, insérer, enregistrer', action:'canvases', chev:true},
{key:'plugins', icon:'🧩', label:'Add plugins',
sub:'Modules on/off — bientôt (phase 8)', disabled:true},
sub:'Modules on/off — effet réel : routes, UI, outils', action:'plugins', chev:true},
{key:'memory', icon:'🧠', label:'Mémoire (on/off)',
sub:'Résumés des échanges injectés à chaque run', action:'memory'}
];
@@ -74,7 +74,7 @@
skillForm: {id: null, name: '', description: '', prompt_template: ''},
galleryFilter: '',
canvases: [], canvas: null,
connectors: [], connector: null,
connectors: [], connector: null, plugins: [],
connectorForm: {name: '', url: '', secret: '', kind: 'custom',
auth: 'bearer', hint: ''},
cmdFocus: -1, slashDismiss: false,
@@ -1148,6 +1148,7 @@
if(s === 'connector-new') return 'Ajouter un connecteur';
if(s === 'canvases') return 'Design System – Canevas';
if(s === 'canvas') return this.canvas ? this.canvas.name : 'Canevas';
if(s === 'plugins') return 'Add plugins';
return 'Ajouter au contexte';
},
get plusItems(){
@@ -1163,6 +1164,7 @@
if(s === 'connector-new') return [];
if(s === 'canvases') return this._canvasItems();
if(s === 'canvas') return this._canvasActionItems();
if(s === 'plugins') return this._pluginItems();
if(s === 'root'){
// l'entrée Mémoire reflète l'état réel du toggle de la conversation
var on = this.memoryOn;
@@ -1274,6 +1276,39 @@
var tools = (c.kind === 'mcp' && c.tools_count) ? ' · ' + c.tools_count + ' outil(s)' : '';
return mark + (c.detail || c.status) + tools + (c.enabled ? '' : ' · désactivé');
},
// ── Plugins de l'instance (v7.58.0) ──
fetchPlugins(){
var self = this;
fetch('/api/agent/plugins').then(function(r){ return r.json(); })
.then(function(d){ self.plugins = (d && d.plugins) || []; })
.catch(function(){ self.plugins = []; });
},
_pluginItems(){
var out = (this.plugins || []).map(function(pl){
return {key:'pl-' + pl.slug, icon: pl.enabled ? '✅' : '⛔',
label: pl.name,
sub: (pl.enabled ? 'Activé' : 'Désactivé') + ' — ' + pl.description,
action:'plugin-toggle', slug: pl.slug};
});
if(!out.length) out.push({key:'pl-none', _sep:'Aucun plugin'});
out.push({key:'pl-hint', _sep:
'OFF = routes refusées, UI masquée, outils retirés'});
return out;
},
pluginToggle(slug){
var self = this;
var pl = (this.plugins || []).filter(function(x){ return x.slug === slug; })[0];
if(!pl) return;
fetch('/api/agent/plugins/' + encodeURIComponent(slug), {
method:'PATCH', headers:{'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'},
body: JSON.stringify({enabled: !pl.enabled})
}).then(function(r){ return r.json().then(function(d){ return {ok:r.ok, d:d}; }); })
.then(function(res){
if(!res.ok){ self.toast((res.d && res.d.detail) || 'Bascule impossible.', true); return; }
self.toast(res.d.enabled ? 'Plugin activé.' : 'Plugin désactivé.');
self.fetchPlugins();
}).catch(function(){ self.toast('Bascule impossible (réseau).', true); });
},
_connectorItems(){
var self = this;
var out = (this.connectors || []).map(function(c){
@@ -1579,6 +1614,10 @@
auth:'bearer', hint:''};
this.plusSection = 'connector-new'; this.plusFocus = -1; return;
}
if(it.action === 'plugins'){
this.fetchPlugins(); this.plusSection = 'plugins'; this.plusFocus = 0; return;
}
if(it.action === 'plugin-toggle'){ this.pluginToggle(it.slug); return; }
if(it.action === 'connector-probe'){ this.connectorProbe(); return; }
if(it.action === 'connector-toggle'){ this.connectorToggle(); return; }
if(it.action === 'connector-delete'){ this.connectorDelete(); return; }