feat: v6.6.0 — Agent phase 5 : API publique agent (/api/v2/agents, run synchrone JSON) + marketplace skills (export/import portable + galerie de 6 presets, palette / du panneau) + webhooks agent.run.started/failed · 764 tests verts
FlowDeck CI / docker (push) Successful in 1m21s
FlowDeck CI / lint (push) Successful in 1m27s
FlowDeck CI / test (push) Successful in 11m5s

This commit is contained in:
2026-09-24 10:16:24 -04:00
parent 6dfd6d718e
commit 9562f30366
16 changed files with 3262 additions and 42 deletions
+63 -13
View File
@@ -500,7 +500,7 @@
allProviders: [], providers: [], providerModels: [], llmProvider: 'offline',
llmModel: '', llmKeys: [], defaultProvider: 'offline',
activeContext: null, ctxPinnedKey: null,
contextChips: [], skills: [],
contextChips: [], skills: [], gallery: [],
mentionOpen: false, mentionItems: [], mentionQuery: '', mentionLoad: false,
mentionFocus: -1, _mentionFrom: null, _mentionTimer: null, _atNode: null,
cmdFocus: -1, slashDismiss: false,
@@ -811,6 +811,42 @@
fetch('/api/agent/skills').then(function(r){return r.json()}).then(function(d){
self.skills = d.skills || [];
}).catch(function(){ self.skills = []; });
// Galerie de skills (marketplace, v6.6.0) — presets installables en 1 clic.
fetch('/api/agent/skills/gallery').then(function(r){return r.json()}).then(function(d){
var installed = {};
(self.skills || []).forEach(function(s){ installed[String(s.name).toLowerCase()] = true; });
self.gallery = (d.gallery || []).filter(function(g){
return !installed[String(g.name).toLowerCase()];
});
}).catch(function(){ self.gallery = []; });
},
// Installe un preset de galerie puis l'épingle au contexte (phase 5).
installGallerySkill(slug, icon, name){
var self = this;
fetch('/api/agent/skills/gallery/' + encodeURIComponent(slug) + '/install', {
method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{}'
}).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) || 'Installation impossible.', true);
return;
}
self.toast('Skill « ' + (res.d.name || name || slug) + ' » installée.');
self.fetchSkills();
self._pinDbSkill(res.d.id, res.d.name || name || slug, res.d.icon || icon || '📦');
}).catch(function(){ self.toast('Installation impossible (réseau).', true); });
},
// Épingle un skill enregistré (id DB) dans les chips de contexte.
_pinDbSkill(id, name, icon){
var exists = this.contextChips.some(function(ch){ return ch.kind === 'skill' && ch.skillId === id; });
if(exists){ this.toast('Ce skill est déjà épinglé au contexte.'); return; }
var pin = {key:'skill-'+id, kind:'skill', icon: icon || '✨',
label: String(name || '').replace(/^\//, ''), token:'skill:'+id, skillId:id};
this.contextChips.push(pin);
this.clearComposer();
this._insertToken({token:pin.token, label:pin.label, icon:pin.icon, kind:'skill'});
this.toast('Skill épinglé — décrivez votre demande, il sera appliqué à l\u2019envoi.');
},
loadConversations(){
var self = this;
@@ -1254,7 +1290,11 @@
items.forEach(function(o){ o._i = seq++; out.push(o); });
}
var skillsPresent = false, admPresent = false;
var built = [], dbs = [], adm = [];
var built = [], dbs = [], adm = [], gal = [];
var installedNames = {};
for(var j=0;j<(this.skills||[]).length;j++){
installedNames[String(this.skills[j].name).toLowerCase()] = true;
}
for(var i=0;i<FD_SKILLS.length;i++){
if(qmatch(FD_SKILLS[i].cmd)){
var s = FD_SKILLS[i];
@@ -1267,6 +1307,15 @@
dbs.push({key:'db-'+sk.id, cmd:'/'+sk.name, desc:(sk.description || 'Skill enregistré'), icon:'✨', _type:'db', skillId:sk.id});
}
}
// Galerie (marketplace) — presets pas encore installés.
for(i=0;i<(this.gallery||[]).length;i++){
var g = this.gallery[i];
if(installedNames[String(g.name).toLowerCase()]) continue;
if(qmatch(g.slug)){
gal.push({key:'gal-'+g.slug, cmd:'/'+g.slug, desc:(g.description || 'Skill galerie'),
icon:(g.icon || '📦'), _type:'gallery', slug:g.slug, skillName:g.name});
}
}
for(i=0;i<FD_ADMIN_CMDS.length;i++){
if(qmatch(FD_ADMIN_CMDS[i].cmd)) adm.push({key:'adm-'+FD_ADMIN_CMDS[i].cmd, cmd:FD_ADMIN_CMDS[i].cmd, desc:FD_ADMIN_CMDS[i].desc});
}
@@ -1276,7 +1325,11 @@
out.push({_sep:'Skills', key:'sep-skills'});
pushItems(built.concat(dbs));
}
if(skillsPresent && admPresent) out.push({_sep:'Commandes', key:'sep-adm'});
if(gal.length){
out.push({_sep:'Galerie', key:'sep-gal'});
pushItems(gal);
}
if((skillsPresent || gal.length) && admPresent) out.push({_sep:'Commandes', key:'sep-adm'});
if(admPresent) pushItems(adm);
this._slashSeq = seq;
return out;
@@ -1302,17 +1355,14 @@
this._focusComposer();
return;
}
if(c._type === 'gallery'){
this.installGallerySkill(c.slug, c.icon, c.skillName);
this.cmdFocus = -1; this.slashDismiss = false;
this._focusComposer();
return;
}
if(c._type === 'db'){
var exists = this.contextChips.some(function(ch){ return ch.kind === 'skill' && ch.skillId === c.skillId; });
if(exists){ this.toast('Ce skill est déjà épinglé au contexte.'); }
else {
var pin = {key:'skill-'+c.skillId, kind:'skill', icon:c.icon||'✨',
label:String(c.cmd).replace(/^\//, ''), token:'skill:'+c.skillId, skillId:c.skillId};
this.contextChips.push(pin);
this.clearComposer();
this._insertToken({token:pin.token, label:pin.label, icon:pin.icon, kind:'skill'});
this.toast('Skill épinglé — décrivez votre demande, il sera appliqué à l\u2019envoi.');
}
this._pinDbSkill(c.skillId, c.cmd, c.icon);
} else {
var slug = String(c.cmd).replace(/^\/+/, '').toLowerCase().replace(/\s+/g, '-');
var builtinExists = this.contextChips.some(function(ch){ return ch.kind === 'skill' && ch.token === 'builtin:' + slug; });