mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 23:18:13 +02:00
cleanup: supprime le code mort des skills (fondus dans la memoire)
- supprime skills.go (Skill, ListSkills, SkillContent, SaveSkill, AppendSkill, DeleteSkill, safeSkillDir, skillsSystemPrompt : plus aucun appelant) - retire les handlers/routes /api/skill, /api/skill/save, /api/skill/delete (aucun client : ni UI locale, ni ajean-app, ni jean-relay) - conserve les alias retro-compat /api/skills(/toggle) -> handleAgent, la migration migrateSkillsToMemory et legacySkillsFlag
This commit is contained in:
1 parent
e08020844d
commit
0c4ff12a00
3 files changed
+3
-201
No files matched your search
@@ -255,7 +255,7 @@ func MemContent(name string) string {
|
|||||||
|
|
||||||
// MemSave écrit (crée ou écrase) une page mémoire. Si old est fourni et diffère
|
// MemSave écrit (crée ou écrase) une page mémoire. Si old est fourni et diffère
|
||||||
// du nouveau nom, l'ancienne page est renommée (supprimée). Utilisé par l'éditeur
|
// du nouveau nom, l'ancienne page est renommée (supprimée). Utilisé par l'éditeur
|
||||||
// web — pendant de SaveSkill.
|
// web.
|
||||||
func MemSave(name, old, content string) error {
|
func MemSave(name, old, content string) error {
|
||||||
if old != "" {
|
if old != "" {
|
||||||
if oldFn, e1 := memFileName(old); e1 == nil {
|
if oldFn, e1 := memFileName(old); e1 == nil {
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Skill struct {
|
|
||||||
Name string
|
|
||||||
Desc string // first non-empty line, with leading #'s stripped
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListSkills walks SKILLS/<name>/SKILL.md, returning name+desc+full content.
|
|
||||||
func ListSkills() []Skill {
|
|
||||||
entries, err := os.ReadDir(skillsDir())
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := []Skill{}
|
|
||||||
for _, e := range entries {
|
|
||||||
if !e.IsDir() || strings.HasPrefix(e.Name(), ".") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f := filepath.Join(skillsDir(), e.Name(), "SKILL.md")
|
|
||||||
b, err := os.ReadFile(f)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content := string(b)
|
|
||||||
desc := ""
|
|
||||||
for _, line := range strings.Split(content, "\n") {
|
|
||||||
s := strings.TrimSpace(strings.TrimLeft(strings.TrimSpace(line), "#"))
|
|
||||||
if s != "" {
|
|
||||||
desc = s
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = append(out, Skill{Name: e.Name(), Desc: desc, Content: content})
|
|
||||||
}
|
|
||||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
var skillNameRe = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
|
|
||||||
|
|
||||||
// safeSkillDir validates and returns the on-disk path for a skill directory.
|
|
||||||
func safeSkillDir(name string) (string, error) {
|
|
||||||
if !skillNameRe.MatchString(name) {
|
|
||||||
return "", fmt.Errorf("nom invalide (alphanum, ._-)")
|
|
||||||
}
|
|
||||||
root, err := filepath.Abs(skillsDir())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
p := filepath.Join(root, name)
|
|
||||||
abs, err := filepath.Abs(p)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(abs, root+string(filepath.Separator)) {
|
|
||||||
return "", fmt.Errorf("path invalide")
|
|
||||||
}
|
|
||||||
return abs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkillContent loads SKILL.md for the given name (used by the read_skill tool).
|
|
||||||
func SkillContent(name string) string {
|
|
||||||
d, err := safeSkillDir(name)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
b, err := os.ReadFile(filepath.Join(d, "SKILL.md"))
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveSkill(name, old, content string) error {
|
|
||||||
if old != "" && old != name {
|
|
||||||
if od, err := safeSkillDir(old); err == nil {
|
|
||||||
_ = os.RemoveAll(od)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d, err := safeSkillDir(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(filepath.Join(d, "SKILL.md"), []byte(content), 0o644)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendSkill ajoute du contenu à la fin de SKILL.md (le crée si absent).
|
|
||||||
// C'est le mode "offset" : l'IA peut consigner une solution trouvée sans
|
|
||||||
// réécrire tout le skill.
|
|
||||||
func AppendSkill(name, content string) error {
|
|
||||||
d, err := safeSkillDir(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p := filepath.Join(d, "SKILL.md")
|
|
||||||
existing, _ := os.ReadFile(p)
|
|
||||||
var b strings.Builder
|
|
||||||
if len(existing) > 0 {
|
|
||||||
b.Write(existing)
|
|
||||||
if !strings.HasSuffix(string(existing), "\n") {
|
|
||||||
b.WriteString("\n")
|
|
||||||
}
|
|
||||||
b.WriteString("\n")
|
|
||||||
}
|
|
||||||
b.WriteString(strings.TrimRight(content, "\n"))
|
|
||||||
b.WriteString("\n")
|
|
||||||
return os.WriteFile(p, []byte(b.String()), 0o644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteSkill(name string) error {
|
|
||||||
d, err := safeSkillDir(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(d); err != nil {
|
|
||||||
return fmt.Errorf("introuvable")
|
|
||||||
}
|
|
||||||
return os.RemoveAll(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// skillsSystemPrompt returns the lightweight skills directory message to
|
|
||||||
// prepend to the conversation when skills are enabled.
|
|
||||||
func skillsSystemPrompt(caps Caps) string {
|
|
||||||
if !caps.Agent {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
list := ListSkills()
|
|
||||||
var b strings.Builder
|
|
||||||
b.WriteString("Skill tips: the first content line is a short title (#) used as the description. Only use read when the question concerns a skill listed below — otherwise don't call the tool.\n")
|
|
||||||
if len(list) == 0 {
|
|
||||||
b.WriteString("Skills: none yet.")
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
b.WriteString("Skills:\n")
|
|
||||||
for _, s := range list {
|
|
||||||
fmt.Fprintf(&b, "- %s: %s\n", s.Name, s.Desc)
|
|
||||||
}
|
|
||||||
return strings.TrimRight(b.String(), "\n")
|
|
||||||
}
|
|
||||||
@@ -98,9 +98,6 @@ func newWebMux() *http.ServeMux {
|
|||||||
api("/api/tools/toggle", handleAgentToggle)
|
api("/api/tools/toggle", handleAgentToggle)
|
||||||
api("/api/skills", handleAgent)
|
api("/api/skills", handleAgent)
|
||||||
api("/api/skills/toggle", handleAgentToggle)
|
api("/api/skills/toggle", handleAgentToggle)
|
||||||
api("/api/skill", handleSkill)
|
|
||||||
api("/api/skill/save", handleSkillSave)
|
|
||||||
api("/api/skill/delete", handleSkillDelete)
|
|
||||||
api("/api/mem", handleMem)
|
api("/api/mem", handleMem)
|
||||||
api("/api/mem/save", handleMemSave)
|
api("/api/mem/save", handleMemSave)
|
||||||
api("/api/mem/delete", handleMemDelete)
|
api("/api/mem/delete", handleMemDelete)
|
||||||
@@ -631,49 +628,9 @@ func handleInternet(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSkill(w http.ResponseWriter, r *http.Request) {
|
|
||||||
name := strings.TrimSpace(r.URL.Query().Get("name"))
|
|
||||||
if name == "" {
|
|
||||||
sendJSON(w, 200, map[string]any{"name": "", "content": "# nouveau skill\n\nDécris ici quand et comment l'IA doit utiliser ce skill.\n"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c := SkillContent(name)
|
|
||||||
if c == "" {
|
|
||||||
sendJSON(w, 404, map[string]any{"error": "not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sendJSON(w, 200, map[string]any{"name": name, "content": c})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSkillSave(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req saveReq
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
sendJSON(w, 400, map[string]any{"ok": false, "error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := SaveSkill(req.Name, req.Old, req.Content); err != nil {
|
|
||||||
sendJSON(w, 400, map[string]any{"ok": false, "error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sendJSON(w, 200, map[string]any{"ok": true, "name": req.Name})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSkillDelete(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req saveReq
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
sendJSON(w, 400, map[string]any{"ok": false, "error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := DeleteSkill(req.Name); err != nil {
|
|
||||||
sendJSON(w, 400, map[string]any{"ok": false, "error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sendJSON(w, 200, map[string]any{"ok": true})
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMem / handleMemSave / handleMemDelete : éditeur web des pages mémoire
|
// handleMem / handleMemSave / handleMemDelete : éditeur web des pages mémoire
|
||||||
// (MEMORY/<nom>.md). Mêmes payloads que l'éditeur de skills (name/old/content)
|
// (MEMORY/<nom>.md). Payload partagé saveReq (name/old/content) ; "name" = nom
|
||||||
// pour réutiliser l'UI ; "name" = nom de fichier de la page.
|
// de fichier de la page.
|
||||||
func handleMem(w http.ResponseWriter, r *http.Request) {
|
func handleMem(w http.ResponseWriter, r *http.Request) {
|
||||||
name := strings.TrimSpace(r.URL.Query().Get("name"))
|
name := strings.TrimSpace(r.URL.Query().Get("name"))
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
|||||||
Reference in new issue
Block a user