mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Trois apports repris d'AJEAN 0.12.9 → 0.13.5, adaptés au fork. MÉMOIRE LONGUE DE LA CONVERSATION (chat_recall.go) Le compactage résumait, donc perdait. Chaque gros bloc du torse est désormais ARCHIVÉ verbatim sous un identifiant court (r7…) dans bbolt AVANT d'être résumé ; le résumé cite l'id, et le modèle le rappelle avec recall(id) ou le retrouve par mots-clés avec recall_search. Le contexte reste plat, l'archive grossit sur disque. En mode code, un fichier lu ou un diff produit tôt dans la session n'est plus perdu au compactage suivant. Au passage : garde-fou anti-résumé-dégénéré, budget de résumé indexé sur la fenêtre, queue ramenée à 20 % (compacter plus large ne coûte plus de perte), et fin de l'épinglage du 1er message user — le modèle répondait à l'ancienne demande au lieu de continuer la tâche en cours. PROJETS (projects.go) Un projet cloisonne une mémoire, ses discussions et ses trackers. La couture est memoryDir(), qui pointe sur le projet actif : tout le code mémoire en hérite sans le savoir. Migration automatique au premier démarrage (memory/*.md → memory/generale/, discussions et tâches orphelines rattachées). Une tâche planifiée vise un projet et l'exécution le force, pour qu'une veille n'écrive pas dans la mémoire du chantier affiché à l'écran. TRACKERS (tracker.go) 3e type de mémoire : les données datées qui s'accumulent. On ne les lit jamais en entier — consultation par niveaux (vue d'ensemble → année → mois → événements), et la dernière valeur de chaque tracker est donnée d'emblée au modèle, qui répond sans appeler l'outil. Aussi : index MEMORY.md tenu par le CODE et injecté en tête de conversation avec la description du projet (le modèle ne peut plus le désynchroniser) ; prompt système rattaché au PRESET et non plus global — stocké en base, pas dans le .env, qui ne saurait pas porter un texte multiligne. Deux correctifs du lot précédent voyagent ici, faute de pouvoir séparer les fichiers : msgText signale la présence d'une image au compactage, et le garde-fou « pensé sans agir » passe à deux relances (nudgeCount/maxNudges). Le paquet UI est regénéré dans le commit suivant, qui touche les mêmes sources. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
3.3 KiB
Go
94 lines
3.3 KiB
Go
// web_sysprompt.go — prompt système personnalisé de l'utilisateur, persisté
|
|
// CÔTÉ SERVEUR (en base) et partagé entre appareils, comme la conversation
|
|
// elle-même. Historique : avant la conversation serveur (v0.4.x),
|
|
// l'UI envoyait son prompt système dans chaque requête /api/chat ; depuis,
|
|
// /api/chat/send ne porte que le message → le champ de l'UI n'avait plus aucun
|
|
// effet. Il est maintenant lu ici par la génération (chat_conversation.go), et
|
|
// InjectSkills (llm_client.go) le fusionne avec le préambule agent.
|
|
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// Le prompt était GLOBAL : un seul texte pour tous les modèles. En pratique il ne
|
|
// l'est pas — la consigne qui va bien à un modèle à raisonnement en dessert un
|
|
// petit modèle d'instruction, et changer de preset obligeait à réécrire le champ
|
|
// à la main. Il est donc rattaché AU PRESET, et bascule avec lui.
|
|
//
|
|
// Il vit en base (une clé par preset) et NON dans le fichier .env du preset :
|
|
// un .env est lu ligne à ligne, il ne peut pas porter un texte multiligne — et
|
|
// un prompt système en fait presque toujours plusieurs.
|
|
//
|
|
// La clé globale historique reste le REPLI : une installation qui avait un prompt
|
|
// le garde tant que le preset actif n'en définit pas, plutôt que de se retrouver
|
|
// silencieusement sans consigne après mise à jour.
|
|
const (
|
|
sysPromptGlobalKey = "sysprompt"
|
|
sysPromptKeyPrefix = "sysprompt:"
|
|
)
|
|
|
|
// activePresetID renvoie l'id du preset actif ("" si aucun ne correspond à la
|
|
// configuration courante — config bricolée à la main, ou aucun preset créé).
|
|
func activePresetID() string {
|
|
list, err := ListPresets()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
for _, p := range list {
|
|
if p.Active {
|
|
return p.ID
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// readSysPrompt renvoie le prompt système à appliquer ("" si aucun).
|
|
func readSysPrompt() string {
|
|
if id := activePresetID(); id != "" {
|
|
if s := strings.TrimSpace(getStr(bkState, sysPromptKeyPrefix+id)); s != "" {
|
|
return s
|
|
}
|
|
}
|
|
return getStr(bkState, sysPromptGlobalKey)
|
|
}
|
|
|
|
// saveSysPrompt écrit le prompt système DU PRESET ACTIF. Sans preset identifiable,
|
|
// on écrit le global — c'est le seul emplacement qui aura un effet, et le champ
|
|
// doit rester utilisable sur une installation sans preset.
|
|
func saveSysPrompt(text string) error {
|
|
text = strings.TrimSpace(text)
|
|
if id := activePresetID(); id != "" {
|
|
return putStr(bkState, sysPromptKeyPrefix+id, text)
|
|
}
|
|
return putStr(bkState, sysPromptGlobalKey, text)
|
|
}
|
|
|
|
// handleSysPrompt :
|
|
//
|
|
// GET → {ok, text}
|
|
// POST {text} → enregistre ("" = efface) puis renvoie {ok, text}
|
|
func handleSysPrompt(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
sendJSON(w, 200, map[string]any{"ok": true, "text": readSysPrompt()})
|
|
case http.MethodPost:
|
|
var body struct {
|
|
Text string `json:"text"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
sendJSON(w, 400, map[string]any{"ok": false, "error": err.Error()})
|
|
return
|
|
}
|
|
if err := saveSysPrompt(body.Text); err != nil {
|
|
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
|
|
return
|
|
}
|
|
sendJSON(w, 200, map[string]any{"ok": true, "text": readSysPrompt()})
|
|
default:
|
|
sendJSON(w, 405, map[string]any{"ok": false, "error": "méthode non autorisée"})
|
|
}
|
|
}
|