mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Les deux reprises d'AJEAN arrivées en parallèle du chiffrement écrivaient en clair à côté de discussions chiffrées. - Résultats complets des outils (« voir plus ») : bucket toolres ajouté à encryptedBuckets, écrits et relus par putStoreBytes / getStoreBytesErr. Mémoire verrouillée : rien n'est écrit, le flux porte le résultat entier. Le nettoyage des orphelins ne tourne plus quand la mémoire est verrouillée : l'index des discussions revenait vide et TOUT passait pour orphelin. - Images du fil (chatimg/) : même enveloppe que les pages mémoire ; verrouillée, l'image reste en base64 dans le message. Elles n'étaient jamais effacées : comme un fil rechargé perd ses images (stripImageParts), celles de plus de 24 h partent au démarrage et à chaque bascule de discussion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
3.6 KiB
Go
122 lines
3.6 KiB
Go
package loki
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
// tool_results.go — résultats COMPLETS des outils, pour le bouton « voir plus ».
|
|
// Repris d'AJEAN 0.15.5 / 0.15.7. Chiffré comme les discussions quand le
|
|
// chiffrement mémoire est actif (bkToolRes figure dans encryptedBuckets).
|
|
//
|
|
// Le flux n'envoie à l'UI qu'un aperçu (toolPreviewChars) ; le reste se charge
|
|
// au clic. Il ne peut pas être cherché dans conv.Messages : pendant un tour
|
|
// d'agent, les messages tool n'y sont versés qu'à la FIN du tour, et après un
|
|
// compactage les anciens ont disparu de la vue modèle. On garde donc chaque
|
|
// résultat coupé dans un bucket dédié, sous un id PROPRE (pas le tool_call_id,
|
|
// que certains parseurs réutilisent d'un tour à l'autre).
|
|
//
|
|
// Rangement PAR DISCUSSION : la clé est « <id de discussion>.<aléa> ». Les
|
|
// résultats vivent aussi longtemps que leur discussion (supprimés avec elle,
|
|
// voir deleteToolResultsFor) ; les orphelins sont nettoyés de temps en temps.
|
|
|
|
const (
|
|
bkToolRes = "toolres"
|
|
toolResPruneGap = 200 // nettoyage tenté toutes les N écritures
|
|
)
|
|
|
|
var toolResWrites atomic.Int64
|
|
|
|
func toolResID(sid string) string {
|
|
var b [12]byte
|
|
if _, err := rand.Read(b[:]); err != nil {
|
|
return ""
|
|
}
|
|
if sid = strings.TrimSpace(sid); sid == "" {
|
|
sid = "nosession"
|
|
}
|
|
return sid + "." + hex.EncodeToString(b[:])
|
|
}
|
|
|
|
// saveToolResult enregistre un résultat complet de la discussion ACTIVE et
|
|
// renvoie son id ("" en cas d'échec : l'appelant envoie alors le résultat
|
|
// entier dans le flux).
|
|
func saveToolResult(result string) string {
|
|
id := toolResID(getStr(bkChat, ckActive))
|
|
// Chiffrement actif mais mémoire verrouillée : putStoreBytes refuse d'écrire
|
|
// en clair, et l'appelant envoie alors le résultat entier dans le flux.
|
|
if id == "" || putStoreBytes(bkToolRes, id, []byte(result)) != nil {
|
|
return ""
|
|
}
|
|
if toolResWrites.Add(1)%toolResPruneGap == 0 {
|
|
go pruneToolResults()
|
|
}
|
|
return id
|
|
}
|
|
|
|
// loadToolResult relit un résultat enregistré.
|
|
func loadToolResult(id string) (string, bool) {
|
|
if strings.ContainsAny(id, "/\\") {
|
|
return "", false
|
|
}
|
|
b, err := getStoreBytesErr(bkToolRes, id)
|
|
if err != nil || b == nil {
|
|
return "", false
|
|
}
|
|
return string(b), true
|
|
}
|
|
|
|
// deleteToolResultsFor supprime les résultats d'une discussion supprimée.
|
|
func deleteToolResultsFor(sid string) {
|
|
if sid == "" {
|
|
return
|
|
}
|
|
prefix := sid + "."
|
|
_ = update(bkToolRes, func(b *bolt.Bucket) error {
|
|
c := b.Cursor()
|
|
var keys [][]byte
|
|
for k, _ := c.Seek([]byte(prefix)); k != nil && strings.HasPrefix(string(k), prefix); k, _ = c.Next() {
|
|
keys = append(keys, append([]byte(nil), k...))
|
|
}
|
|
for _, k := range keys {
|
|
if err := b.Delete(k); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// pruneToolResults supprime les résultats de discussions qui n'existent plus.
|
|
func pruneToolResults() {
|
|
// Mémoire verrouillée : l'index des discussions est illisible et reviendrait
|
|
// vide — tout passerait pour orphelin. On attend le déverrouillage.
|
|
if memEncActive() && !memUnlocked() {
|
|
return
|
|
}
|
|
alive := map[string]bool{getStr(bkChat, ckActive): true}
|
|
for _, m := range convIndex() {
|
|
alive[m.ID] = true
|
|
}
|
|
_ = update(bkToolRes, func(b *bolt.Bucket) error {
|
|
var orphans []string
|
|
_ = b.ForEach(func(k, _ []byte) error {
|
|
key := string(k)
|
|
if i := strings.LastIndexByte(key, '.'); i > 0 && key[:i] != "nosession" && !alive[key[:i]] {
|
|
orphans = append(orphans, key)
|
|
}
|
|
return nil
|
|
})
|
|
for _, k := range orphans {
|
|
if err := b.Delete([]byte(k)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|