Reprise des commits 91f7f977 (PR #100 de Trombo38) et 1dfac0cf d'AJEAN.
Une vidéo jointe (mp4, webm, mov, mkv, avi) part au modèle en partie
input_video, et l'outil see_video charge une vidéo du disque, mais
seulement si le moteur actif sait la lire : preset llama.cpp local avec
projecteur (MMPROJ) et binaire dont l'aide annonce --video-fps
(videoInputSupported, aide relue quand le binaire change, chemin des
bibliothèques passé à la seule commande pour ne pas figer binHelp).
Strata et les API externes ignoraient la partie en silence : la vidéo
reste alors un fichier, annoncé avec la marche à suivre (ffmpeg puis
see_image), et see_video n'est pas proposé.
llama.cpp a --video-fps depuis la PR #24318 (fin août 2026) ; l'image
server-cuda officielle, base du conteneur, installe ffmpeg. La consigne
de repli dit quoi faire si ffmpeg manque (installation hors conteneur).
Adapté à Loki :
- la vidéo de see_video est toujours éphémère (jamais dans l'historique,
KEEP_TURN_IMAGES compris) ; même ordre de refus que see_image, sonde
/props en dernier, refus sur un poste distant ;
- une vidéo jointe est rangée par référence (loki-vid:, LOKI_HOME/chatvid,
chiffrée comme le fil), parties recopiées et non mutées comme les
images ; retirée d'un fil rouvert (stripImageParts, videoLostMarker) et
effacée par le ménage des médias ;
- marqueur de perte à la compaction, élision dans l'export JSON, relais
par l'API interne des outils (vision_video).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestToolResultsSaveLoadDelete échouait de temps en temps en suite
complète (1 fois sur ~300 en isolé). Vraie course dans le code, pas dans
le test : deleteToolResultsFor retirait les résultats EN ATTENTE de
l'écrivain différé, mais pas ceux qu'il avait déjà pris dans son lot.
Si la transaction de suppression passait avant celle de l'écrivain, le
résultat était écrit juste après avoir été effacé, et renaissait.
En production, la suppression d'une discussion passe d'abord par
forget + flush, ce qui masquait le problème ; mais forget non plus ne
pouvait rien contre un lot déjà parti.
- l'écrivain note les résultats du lot en vol ; dropToolRes (donc
forget et deleteToolResultsFor) les marque annulés
- l'écrivain vérifie l'annulation DANS sa transaction ; la suppression
annule AVANT d'ouvrir la sienne : bbolt sérialisant les deux, soit le
résultat est sauté, soit il est écrit puis effacé
- annulations oubliées à la fin du lot : un nouveau résultat s'écrit
- test déterministe (écrivain retenu en plein lot) qui échouait avant
la correction ; suite complète passée 3 fois, TestToolResults ×400
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Relecture de l'écrivain différé (chat_persist.go) : l'ordre, la suppression
et les bascules tiennent ; trois écarts de comportement corrigés.
- saveToolResult et la télémétrie de generate lisaient la clé brute de la
discussion active ; passés à convEnsureActive, ils pouvaient en CRÉER une
(tâche de fond sur base neuve → discussion vide dans la liste). Nouveau
convActiveID : cache chaud, sinon la clé en base, jamais de création.
- Un résultat d'outil confié après la suppression de sa discussion restait en
mémoire pour toujours (l'écrivain l'écarte, la copie ne partait plus) : il
n'est plus retenu.
- Vidage de l'écrivain avant « verrouiller la mémoire » (sinon l'envoi tout
juste fait ne touchait le disque qu'au déverrouillage) et avant le restart
systemd après MAJ (SIGTERM ne laisse rien finir).
Tests : résultat d'une discussion supprimée ni écrit ni retenu ; base neuve,
saveToolResult reste sous « nosession » sans créer de discussion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StartTurn persistait AVANT de lancer la génération : un marshal, puis deux
commits bbolt (contenu, puis index), soit deux fsync et plusieurs ouvertures
de base — 26-30 ms mesurés sur un NVMe Windows, bien plus sur un /mnt/user
d'Unraid ou un disque de parité, payés à chaque message avant que la requête
ne parte vers llama-server. Même facture au milieu d'un tour (vérification du
mode Code) et pour chaque résultat d'outil coupé (« voir plus »).
- chat_persist.go : un écrivain unique et ordonné. L'instantané reste pris
sous c.mu (images → références, marshal, identifiant de la discussion) ;
seule l'E/S part en différé. Le dernier instantané de chaque discussion
l'emporte (numéro pris sous c.mu), les lots sont fusionnés en UNE
transaction : contenu + index ensemble, résultats d'outils compris.
- Seuls StartTurn, la persistance en cours de tour et les résultats d'outils
sont asynchrones. Fin de tour, reset, bascules, compactage manuel restent
synchrones et attendent tout ce qui précède ; la suppression écarte puis
attend les écritures qui la visent (plus de discussion ressuscitée).
- Discussion active gardée en RAM, créée sous verrou (plus de double
identifiant sur base neuve) et changée sous c.mu avec le contenu : un
instantané ne peut plus écrire l'ancien fil sous le nouvel identifiant.
- « Voir plus » servi depuis la mémoire tant que le résultat n'est pas écrit ;
verrouillage vérifié avant de rendre un id.
- En plein tour, la base reçoit le journal sous sa forme de fin de tour
(compactLog, version pure de compactLogLocked) ; la mémoire n'est pas touchée.
- Vidage de l'écrivain avant redémarrage, « Quitter », exec et (dé)chiffrement.
Le contexte vu par le modèle ne change pas : il lit c.Messages en mémoire, les
résultats complets d'outils et la compaction du journal ne servent qu'à l'UI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>