mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
c1d3757f23b4c1d1eb45017c46fb76f2627244d1
60
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c1d3757f23 |
Cache : KEEP_TURN_IMAGES, relecture — budget plein vérifié avant de ranger l'image
Le relais rangeait l'image sur disque avant de regarder si le budget des images gardées était déjà plein : un fichier écrit pour rien, élagué 24 h plus tard. - budget vérifié d'abord ; plein, l'image reste éphémère sans être rangée - README : sur un modèle hybride, le gain dépend des points de reprise du moteur après une image, à vérifier dans la télémétrie avant d'adopter la clé - test : budget plein, rien d'écrit dans chatimg Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f74f64ed90 |
Cache : KEEP_TURN_IMAGES garde les images d'outils et NUDGE_IN_TOOL range le rappel de budget, en opt-in
Une capture ou une image vue par see_image ne vivait que le temps du tour : au tour suivant elle manquait à l'historique, le préfixe en cache divergeait à elle et toute la boucle d'outils qui suivait était recalculée. Sur Qwen3.5, le rappel de budget en message user faisait de même pour le tour en cours : le nouveau message devient la « dernière question » et tout le tour est rendu autrement. Deux clés, off par défaut et étiquetées zone grise ; sans elles, requêtes identiques à l'octet près (testé). - KEEP_TURN_IMAGES : image gardée par référence (chatimg, chiffrée si la mémoire l'est), relue à l'identique avant d'être gardée, vision active revérifiée à chaque tour ; preset externe seulement s'il déclare la vision - coût mesuré par le moteur (écart de prompt_tokens moins le texte ajouté) ; image non mesurable = éphémère comme avant ; total sous 10 % de la fenêtre, la première qui dépasse et les suivantes du tour restent éphémères - compaction, réduction forcée et début ou milieu de tour au seuil retirent ces images d'abord (légende et imageLostMarker gardés), le résumé ne suit que s'il reste nécessaire ; clé retirée ou vision perdue : retirées au tour - relais marqué ImgRelay (persisté, retiré à l'envoi) : ni demande réinjectée ni preuve de demande servie, fil rouvert compris ; une image non gardée n'entre plus dans l'historique par une compaction en cours de tour - discussion seulement (ni tâche, ni sous-agent, ni vérification) ; fichier d'image rafraîchi à chaque usage, l'élagage de 24 h ne le prend pas - NUDGE_IN_TOOL : rappel de budget au bout du dernier résultat d'outil, même message persisté ; moteur local, sonde de gabarit « préfixe instable » pour ce modèle ; sinon, ou forme inattendue, message à part comme avant Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b0c53e32af |
Compaction : COMPACT_CONTINUATION, relecture — un refus mémorisé ne vaut que pour le même fil
Le refus mémorisé ne regardait que la taille du contexte. Il n'est évalué qu'au-dessus du seuil : une discussion vidée, éditée ou régénérée qui remontait dans la même plage de jetons voyait sa compaction sautée sur la foi d'un refus qui concernait un autre fil. - le refus garde l'empreinte de l'historique (celle de perfPrefix) ; seul un historique qui prolonge celui d'alors en profite, tout autre retente - testé : historique modifié ou raccourci, refus noté par une vraie compaction, filet réactif jamais bloqué Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2097c9f915 |
Compaction : COMPACT_CONTINUATION résume dans le prolongement du prompt en cache, en opt-in
Le résumé d'une compaction part aujourd'hui dans une requête à part — prompt du résumeur et transcription des anciens tours — que le moteur local calcule à froid : des dizaines de secondes sur un 27B, des minutes sur un MoE, à chaque compaction. Nouvelle clé COMPACT_CONTINUATION (off par défaut) : quand le slot porte encore le prompt de la discussion, la requête de résumé est celle du tour telle qu'elle est partie, suivie d'une seule demande de résumé ; le moteur ne calcule qu'elle. Sans la clé, requêtes de compaction identiques à l'octet près (prompt du résumeur comparé à une copie figée, testé). - vue MODÈLE pour tout ce qui est rangé : bornes, archives recall, demande réinjectée, garantie de réduction et sortie ne changent pas ; la vue d'envoi (turnViewDry, wireMessages, buildChatPayload) ne sert qu'à la requête, rien d'injecté n'entre dans l'historique (testé) - frontière recalée sur la vue envoyée (messages non système un pour un, vérifiés rôle par rôle), désignée par le nombre de messages gardés et le début du premier ; tout résumer avant, dater l'avancement à la frontière - mêmes règles de résumé (+ mode Code), même budget, température 0.2 sans l'échantillonnage du preset, enable_thinking=false ajouté aux arguments du tour, reasoning_effort du tour, tool_choice « none », sans flux - messages utilisateur de fin hors de la vue : pas en cache, et pas deux `user` d'affilée pour les gabarits stricts - slot vérifié : tampon posé par une étape de tour acceptée (llm_slots.go, sous le verrou du compteur en vol), perdu dès qu'une autre requête part — vérification, sous-agent, tâche, préchauffage, résumé, bench, /v1 — ou que le modèle ou la fenêtre changent ; relu à l'envoi - marge en jetons réels (dernier compte + non vu + demande + budget + 5 %) - repli sur la transcription au moindre écart : refus du moteur, réseau, appel d'outil émis (tool_calls décodés) ou écrit en texte, raisonnement seul, résumé vide ; refus du gabarit ou appel d'outil = suspendue pour le modèle jusqu'au redémarrage, deux échecs de suite aussi - avec la clé : pas de résumé quand même un vide ne réduirait pas de 20 % (borne exacte, avant tout archivage) ; après un refus faute de réduction, pas de nouvel essai avant +10 % de contexte, jamais à 90 % de la fenêtre, oublié quand le contexte baisse - réactif, fenêtre pleine, bouton manuel, tâches, sous-agents, terminal, preset externe : chemin d'avant ; télémétrie kind=compact avec la discussion Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
630e84cc08 |
Cache : PREWARM prépare le prochain tour pendant que l'utilisateur lit, en opt-in
Une compaction, un dernier message rendu autrement au tour suivant ou une tâche planifiée qui a pris le slot laissent au message suivant un recalcul inévitable : des secondes sur un 27B, des dizaines sur un MoE aux experts en RAM, avant le premier mot. Nouvelle clé PREWARM (off par défaut) : dès que le moteur local est libre, Loki lui envoie la requête du prochain tour suivie d'un message utilisateur « . », max_tokens 1, sans flux. Le moteur calcule le préfixe et pose un point de reprise au début de ce message ; le vrai tour ne calcule plus que le sien. Sans la clé, aucune requête de plus (testé). - assemblage partagé, contenu vivant : turnViewDry (même code que turnView, sans rien toucher à la discussion, décision PROJ_SNAPSHOT via projSnapDecide), wireMessages, turnReasoning et buildChatPayload, extraits de runChat ; corps identique à l'octet près à celui d'avant (matrice d'options et de modes, testée contre une copie de l'ancien code) - jamais devant un vrai travail : toute requête de Loki l'annule au départ (compteur de llm_slots.go, sous son verrou), sauf un tour de chat dont les messages sérialisés, outils et arguments du gabarit prolongent exactement le préfixe préchauffé - moteur local, un seul slot (/props total_slots), pas pendant un tour, une tâche, un bench, un travail annexe ni une requête en vol ; un seul à la fois ; pas si le prochain tour compactera, ni à moins de 10 min de minuit - requête brute : ni compaction, ni relance, ni effort appris, ni CtxUsed, ni stats ; erreurs lâchées (une ligne de journal par statut) ; rien persisté - déclencheurs : fin de tour sans file d'attente, fin de tâche (projet forcé levé, slot effacé) ; full = aussi changement de discussion après 3 s - capacités du dernier tour gardées en mémoire par discussion ; mode Code = rôle d'un message ordinaire, une demande de plan diverge et annule - télémétrie kind=prewarm ; la vérification du cache RAM après une tâche se fait sur lui Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4932999d5c |
Contexte : PROJ_SNAPSHOT fige le bloc projet par discussion, ses changements arrivent en mise à jour, en opt-in
Le contexte du projet (description, index mémoire, trackers, AGENTS.md) part en tête du premier message utilisateur, reconstruit à chaque tour : une page créée ou une valeur de tracker notée faisait recalculer toute la conversation derrière lui, 10 à 45k tokens. Nouvelle clé PROJ_SNAPSHOT (off par défaut) : avec on, chaque discussion garde une copie datée du bloc, renvoyée à l'octet près, et les changements arrivent en tête du message suivant dans un bloc <context_update from="loki">, rangé dans l'historique. Sans la clé, la requête est identique à l'octet près (testé contre l'ancien assemblage). - en-têtes figés « as of <date> » pour l'index mémoire et les trackers, sans « answer straight from this » ; ligne fixe du système, seulement avec la clé - mises à jour par type : +/~/- par page (clé « ](fichier) »), une ligne complète par tracker (clé = slug), texte COMPLET pour la description, AGENTS.md, ou un index dont la prose a changé — jamais de diff de prose - état annoncé structuré et persisté (ProjSnap, omitempty), instantané pris sur marqueur explicite : la première page d'un projet vide arrive en mise à jour, le premier message ne bouge pas - rafraîchi (blocs retirés de tout l'historique) quand le prompt change de toute façon : compaction début/fin/manuelle, compaction ou réduction en cours de tour (bloc vivant aussitôt, rangé comme instantané : pas de recalcul de plus), système ou outils modifiés, redémarrage de Loki, réglage moteur ou modèle, projet, nom, mode mémoire, mode Code, dépôt, textes des en-têtes ; et au-delà d'un seuil de mises à jour accumulées - mise à jour posée sous c.mu avec l'epoch, après la compaction de début de tour ; texte ou multimodal ; retirée du titre, de l'export JSON, de l'entrée du résumeur et de la tâche du vérificateur - loadFrom, Reset et le chargement remettent l'instantané à zéro ; clé retirée = nettoyage ; agent off = rien du projet ; tâches et presets externes inchangés ; un sous-agent ne voit pas le bloc du tour - ordre des trackers déjà déterministe (lot 1, test existant) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7c9aa04fd9 |
Raisonnement : REASONING_ECHO renvoie au moteur local la réflexion du modèle, en opt-in
Les gabarits Qwen3.5/3.6 rendent <think>…</think> pour chaque message assistant après la dernière question : c'est leur format entraîné. Loki ne gardait que le texte et les appels, donc à chaque étape d'une boucle d'outils le modèle relisait ses étapes précédentes avec des blocs vides, et le moteur recalculait le dernier message. Nouvelle clé REASONING_ECHO (off par défaut) : avec on, le raisonnement séparé par le serveur est gardé avec chaque message et renvoyé au même modèle. Sans la clé, rien n'est capturé ni envoyé : la requête est identique à l'octet près (testé). - Message.ReasoningContent (reasoning_content) + ReasoningModel, persistés ; seulement depuis reasoning_content du flux, jamais depuis le découpage « </think> » fait chez nous (rendu en double) - un seul point de sortie (echoMessages) : étiquette toujours retirée, raisonnement retiré pour une API externe, un autre modèle, un message sans texte ni appel, ou si la sonde dit que le gabarit ne le rend jamais - comptage : estimateTokens/compactBounds comptent ce qui part et que le gabarit rend (passé seulement si la sonde dit qu'il le garde, ou l'ignore) ; ctxAfter ne retire plus un raisonnement renvoyé - débordement : relance d'abord sans raisonnement, avant toute compaction ; shrinkToFit retire le raisonnement le plus ancien avant tout le reste ; le torse compacté le perd comme le résumé - erreur de gabarit (raise_exception, thinking, Failed to parse messages) : relance une fois sans raisonnement avant tool_choice none / outils coupés ; retenu pour le modèle si la relance passe - runBuilderTurn applique enfin NewHistory comme generate (compaction perdue pendant une passe de correction) ; forwardStream affiche la bannière - sonde de gabarit : nouveau verdict renders_reasoning - REASONING_PRESERVE (lot 1) inchangée, son interaction documentée Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9f6bd725e6 |
Stockage : corrections de relecture — le remède Unraid décrit tel qu'il existe
« Exclusive access » n'est pas un réglage qu'on passe à Yes sur la page du partage : c'est un état, que Unraid 6.12 accorde quand « Permit exclusive shares » est activé et que le partage vit tout entier sur un pool, sans stockage secondaire (/mnt/user/<partage> devient un lien vers le pool). La consigne précédente envoyait chercher une option introuvable. - conseil, compose et README : le vrai chemin (Global Share Settings, mover vers le pool puis Secondary = None, « Exclusive access : Yes »), et redémarrer le conteneur, Docker ne résolvant le lien qu'au démarrage ; - un seul chemin par montage fuse.shfs : /data/models répétait /data ; - l'encart de l'UI, permanent tant que la cause dure, devient masquable (localStorage, pour ce texte seulement — un autre conseil réapparaît). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
056f307983 |
Stockage : Loki signale un /data ou /models servi par le FUSE d'Unraid
Sur Unraid, /mnt/user/… n'est pas un disque mais shfs, un démon FUSE : chaque fsync de loki.db et chaque page d'un gros MoE mmappé relue depuis le disque (modèle plus gros que la RAM) le traverse, à chaque token. Rien n'est perdu ni altéré, mais le décodage ralentit — sans que rien ne le dise. Loki ne déplace rien : changer le chemin hôte d'une installation donnerait un /data vide. Il le voit et le dit, sur un ton d'information : - sys_storagefuse.go lit /proc/self/mountinfo (type lu après le séparateur « - », montage le plus long qui contient le chemin) pour LOKI_HOME et chaque dossier de modèles ; Linux en conteneur seulement, mis en cache une minute puisque /api/status est interrogé en boucle ; - conseil en jaune au démarrage et champ « hint » de /api/status, affiché en encart discret, distinct du bandeau rouge de perte de données ; - compose Unraid et README : « Exclusive access » recommandé (même chemin, FUSE court-circuité), /mnt/<pool> en option avancée avec migration manuelle et mise en garde sur un pool inexistant (RAM). La détection de thrash du cache de pages est laissée de côté : elle exige un signal majflt calibré par modèle sur le serveur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
51c791268d |
Fil long : retour au replay complet avant la reprise du fenêtrage par échanges
Annule
|
||
|
|
b893d1a81f |
API : un site tiers ne peut plus piloter Loki en douce
Repris d'AJEAN 0.15.5. Sans clé de pilotage (le défaut), l'API /api était ouverte à tout ce qui savait joindre le port — y compris une page web quelconque ouverte dans un navigateur du réseau local. Un POST « simple » (text/plain) ne déclenche aucune pré-vérification CORS : la page pouvait changer des réglages et, mode agent actif, faire exécuter des commandes. requireWebAuth passe désormais par crossSiteReject, AVANT le test de clé : - Sec-Fetch-Site: cross-site → 403 ; - Origin présent et différent de l'hôte appelé (ou « null ») → 403. curl, les scripts et les apps n'envoient pas d'Origin : non concernés ; - sans clé seulement, l'hôte appelé doit être local (IP, localhost, nom sans point, .local/.lan/.home…, nom du conteneur) : c'est ce qui coupe le DNS rebinding, où un domaine malveillant se fait résoudre en IP locale. DEUX ÉCARTS AVEC L'AMONT, DUS AU CONTENEUR - LOKI_TRUSTED_HOSTS : derrière un reverse proxy, le nom public n'est ni local ni celui du conteneur. Plutôt qu'imposer une clé, on peut lister ce nom. Documenté dans le README. - Le trafic du tunnel (marqué par withLocalAuth, authentifié par le relais) est dispensé du contrôle d'hôte : son Host est celui du relais. À SAVOIR : un accès existant par nom de domaine SANS clé ni LOKI_TRUSTED_HOSTS est désormais refusé (403, message explicite). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c4e399e101 |
Mode code : sous-agents (explorer, code-reviewer, planner)
Reprise de l'idée des sous-agents d'OpenFox, sur la mécanique déjà en place pour la passe de vérification : un runChat isolé, non persisté. L'outil `subagent` délègue une question bornée à un rôle qui travaille dans SON propre contexte et ne rend que sa réponse. Sur un modèle local, c'est la fenêtre de contexte qu'on sauve : « trouve où est géré le cache » coûte dix lectures de fichiers qui restaient ensuite dans l'historique jusqu'à la compaction, alors que seule la réponse comptait. Les rôles explorer et code-reviewer, jusqu'ici définis mais jamais appelés, deviennent utilisables. Tous les rôles délégués sont en LECTURE SEULE : pas de write/edit (ce qui modifie le dépôt reste dans le fil principal, sous les yeux de l'utilisateur), pas de subagent (aucune récursion), pas de mémoire ni de web. Seul le planner pose des critères — son prompt le lui demande — et marquer un critère « passé » reste le privilège de la passe de vérification. Le rôle verifier n'est PAS délégable : le builder se décernerait son propre satisfecit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY |
||
|
|
c32fd40f1a |
Longues discussions : ne rejouer que la fin, charger le début d'un clic
Inspiré du fenêtrage du fil d'OpenFox (2.0.151+). À chaque ouverture d'onglet, le serveur rejouait TOUT le journal d'affichage : sur une discussion de plusieurs centaines de tours, des dizaines de milliers d'événements traversaient le flux, puis autant de bulles s'installaient dans le DOM que le navigateur devait traîner à chaque rendu. Le replay initial est désormais borné à ses 4000 derniers événements. Rien n'est tronqué sur le disque : le serveur annonce combien d'événements sont restés en arrière, l'interface l'affiche en tête du fil et le bouton « charger le début » se réabonne en demandant le journal entier. Jamais borné sur une reprise de flux (from > 0) : là, le client a déjà le début à l'écran et attend la suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY |
||
|
|
382c5dbdce |
Interface en anglais, par-dessus une source française
L'amont tient une table de clés FR/EN et marque chaque texte d'un data-i18n. Reproduire ça ici demanderait de réécrire tous les écrans d'un coup, avec le risque d'en casser un pour une clé oubliée — et un « settings.memory.title » affiché en production. Chemin additif : la source RESTE le français, et « English » applique un dictionnaire sur le texte affiché (correspondance exacte, nœud par nœud). Une chaîne absente du dictionnaire reste en français ; le dictionnaire s'enrichit sans toucher au reste de l'interface. Jamais traduits : le fil de discussion (#chat), le code, les zones de saisie, et tout ce qui porte data-no-i18n. Un observateur couvre les panneaux rendus en JS après le chargement. Revenir au français recharge la page — le texte d'origine a été remplacé dans le DOM, c'est le moyen sûr de le retrouver intact. Couverture de départ : navigation des réglages, intitulés de sections, libellés de lignes, boutons et interrupteurs. Sélecteur dans Apparence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY |
||
|
|
fa69dac77f |
Chiffrement de la mémoire, snapshots, sauvegarde chiffrée en fichier
Reprise d'AJEAN (mem_crypto/mem_vault/mem_store/mem_io/mem_migrate/ mem_snapshots/mem_health/mem_fsutil + backup_bundle), adaptée à Loki. Chiffrement à enveloppe : une DEK tirée une fois chiffre les données en AES-256-GCM ; elle est enfermée dans un coffre par une KEK dérivée en Argon2id. Ce qui ouvre le coffre : la clé de pilotage de l'appareil (le serveur n'en a que l'empreinte, il ne peut pas ouvrir seul) ou la clé de récupération donnée une fois. La DEK ne vit qu'en RAM. Périmètre chiffré, choisi pour Loki : les pages mémoire, les discussions (journal ET index, qui porte les titres), les blocs archivés au compactage — du verbatim de conversation — et les trackers. Pas les buckets de réglages : le coffre lui-même y vit, les chiffrer serait une boucle. Verrouillé, rien n'est écrasé : putStoreBytes REFUSE d'écrire du clair par-dessus du chiffré, la liste des discussions se lit vide et les pages s'affichent « 🔒 chiffré ». Le déverrouillage recharge la discussion et rattrape ce qui serait resté en clair. Une migration interrompue reprend au démarrage, un snapshot est pris avant chaque bascule, et aucune donnée n'est supprimée avant relecture vérifiée de son remplaçant. Piège corrigé au passage : chiffrer À L'INTÉRIEUR d'une transaction bbolt se bloquait sur le verrou de la base (memEncActive relit la config, donc la base). L'état du chiffrement est désormais résolu AVANT la transaction (memEncoderNow), qui ne reçoit plus qu'un encodeur pur. Sauvegarde : le paquet chiffré {mémoire, presets, réglages} s'exporte et s'importe en FICHIER (/api/backup/export, /api/backup/import). La sauvegarde vers le relais ajean.link n'est pas reprise — c'est le service de l'auteur amont ; ici le fichier reste chez soi. Le dossier mémoire n'était déjà plus joignable qu'aux outils mem_* : c'était la condition de ce chiffrement (un `cat memory/…` aurait rendu du binaire). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY |
||
|
|
e9ec8ae3a8 |
Notifications Web Push (+ manifeste PWA)
Reprise d'AJEAN : le serveur pousse une notification vers les navigateurs abonnés, directement via leur service de push — donc app fermée et téléphone verrouillé, là où une notification côté page ne peut rien (un onglet caché relâche son flux SSE). Deux déclencheurs : la fin d'un tour utilisateur (sauf interruption par le bouton stop : celui qui a coupé est devant l'écran) et — ajout propre à Loki — la fin d'une TÂCHE PLANIFIÉE, succès comme échec. C'est le cas qui compte le plus : une tâche tourne justement quand personne ne regarde. Clés VAPID générées à la première demande et rangées dans la base ; abonnements persistés et purgés quand le service de push répond 404/410. Corps de notification générique, sans extrait de réponse : elle transite par Apple ou Google. /sw.js et /manifest.webmanifest sont servis à la racine (un service worker doit venir de l'origine) ; le worker ne fait QUE recevoir les push, sans cache — mettre l'UI en cache servirait une interface périmée après une mise à jour de l'image. Interrupteur dans Réglages → Mode agent, à armer sur chaque appareil. L'UI dit ce qui manque plutôt que d'échouer : HTTPS requis, notifications bloquées, ou iPhone à ajouter d'abord à l'écran d'accueil. Nouvelle dépendance : github.com/SherClockHolmes/webpush-go (RFC 8291). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY |
||
|
|
74fb1fc781 |
Tâches : scripts planifiés sans modèle, et l'IA planifie la sienne
Deux reprises d'AJEAN autour des tâches planifiées. Dossier de scripts (/data/scripts) : un dossier durable À CÔTÉ de memory, hors du workspace. Le workspace est jetable — supprimer une discussion emporte ses fichiers — donc un script qu'on veut garder n'y avait pas sa place. Le briefing machine l'annonce à l'IA, qui y écrit et y lance ses scripts normalement. Tâche « script seul » (Task.Kind/Script) : le planificateur lance le script sans charger le modèle ni consommer un token, sa sortie devient le compte-rendu, et l'UI l'affiche comme n'importe quelle tâche. Elle tourne hors du verrou de génération — d'où un registre à part pour l'afficher « en cours » et l'arrêter (/api/tasks/stop), et un « tester » qui n'attend ni le verrou ni le moteur. Sélecteur Consigne IA / Script seul dans la modale. Outils task_list/create/update/delete : l'IA se donne elle-même rappels et veilles récurrentes, cloisonnés par projet (dans un projet, elle ne voit et ne pilote que ses tâches). Le budget du préambule passe de 8600 à 11000 caractères, avec le palier documenté dans le test. Dossier mémoire réservé à ses outils : bash, write et edit ne le touchent plus (guardToolOnly*). Un `cat memory/…` contournait l'index MEMORY.md, et c'est la condition d'un chiffrement de la mémoire à venir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY |
||
|
|
00b2350fb9 |
Navigateur piloté, images redressées : reprises d'AJEAN
L'amont (AJEAN 0.15) donne à l'IA le contrôle d'un navigateur ; Loki embarquait déjà un Chromium pour `web_screenshot` sans jamais le piloter. Contrôle du navigateur (computer_use.go, computer_cdp.go, browser_grid.go, portés d'AJEAN) : l'IA ouvre une page en CDP, en reçoit les éléments interactifs NUMÉROTÉS et agit par numéro (browser_open/snapshot/find/click/ type/key/scroll). Aucune vision requise — un petit modèle texte s'en sort. Avec un projecteur chargé s'ajoutent browser_screenshot (image quadrillée) et browser_click_xy. Interrupteur dédié (Réglages, `loki computer`, /api/computer), sous le mode agent : cliquer et taper dans une page sont des actions réelles, même niveau de confiance que bash. Adaptation au conteneur : chromePath cherche D'ABORD le Chromium de Playwright (PLAYWRIGHT_BROWSERS_PATH, /opt/pw-browsers), le seul navigateur de l'image — sinon la fonctionnalité se déclarait absente là où le navigateur est présent. LOKI_CHROME force un chemin, LOKI_CU_HEADFUL ouvre une vraie fenêtre. Préparation des images (web_upload_orient.go, porté d'AJEAN) : l'orientation EXIF est cuite dans les pixels — le projecteur l'ignore et voyait les photos de téléphone couchées — et le grand côté ramené sous 1568 px. Appliquée aux pièces jointes, à see_image et aux captures. Dédup des appels d'outils : bash, bash_bg, bash_tail, see_image et les browser_* ne sont plus court-circuités sur un appel identique. Relancer la même commande après avoir modifié un fichier est légitime, et un outil qui porte une image dans un message à part renvoyait « [déjà fait] » SANS l'image — le modèle tournait en boucle. Mode code : le builder ne publie plus de lui-même (pas de commit/push/reset/ rebase ni de redémarrage de service sans demande explicite), reprise de la leçon d'AJEAN 0.15.4 ; l'inspection en lecture seule reste encouragée. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY |
||
|
|
009b585e75 |
VRAM : un bouton pour décharger le modèle et rendre la carte
Le modèle occupe la mémoire vidéo tant que le moteur tourne : une autre
application qui réclame la carte (jeu, encodage, autre serveur d'inférence)
ne trouvait plus rien à prendre. Le geste existait — « arrêter » le service,
au fond des réglages — mais son nom ne disait pas qu'il libérait la VRAM, et
il laissait tourner le serveur de dictée, qui garde la carte lui aussi.
- POST /api/vram/unload : arrête le moteur ET la dictée, attend que le pilote
rende la mémoire (une lecture immédiate rapporte « 0 Mo libérés » après un
déchargement pourtant réussi) et renvoie le bilan chiffré. Refuse pendant une
génération, sauf {force:true} — la couper perdrait la réponse en cours.
- POST /api/vram/reload : relance le moteur, préflight compris (BIN/MODEL
absents = la vraie raison tout de suite, pas un « chargement… » sans fin).
- Bouton sur les jauges du moniteur, là où l'on regarde la VRAM ; il devient
« Recharger le modèle » dès que le moteur est arrêté, d'après /api/status et
non d'un drapeau local (un second onglet afficherait sinon un bouton qui ment).
- Même commande dans Réglages → Moteur → Service du moteur.
- Carte de saisie : « Modèle déchargé — Recharger le modèle » au lieu de
« Le modèle charge », qui promettait un chargement qui ne viendrait jamais.
La lecture nvidia-smi de /api/vram passe dans web_vram.go (gpuStats), partagée
avec le bilan du déchargement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8n9tDn5sZudAcUT8U6uGg
|
||
|
|
ad107ed284 |
Dictée vocale : micro dans la carte de saisie, whisper.cpp local
Un bouton micro dans la carte de saisie : un clic enregistre, un second
arrête, transcrit et pose le texte dans le champ sans écraser ce qui s'y
trouve. Anneau rouge pulsé pendant l'enregistrement, garde-fou à 90 s.
La transcription est 100 % locale : POST /api/transcribe → whisper-cli
(whisper.cpp, compilé CPU en statique dans une étape dédiée du Dockerfile).
Le modèle ggml-small-q5_1 (~190 Mo, multilingue) n'est pas dans l'image :
téléchargé au premier usage dans /data/whisper/ avec progression (503
{downloading, pct} en attendant), il survit aux recréations du conteneur.
L'audio est encodé en WAV 16 kHz mono côté navigateur (whisper.cpp ne lit
que du PCM) — pas de ffmpeg dans l'image. Le micro n'existe qu'en contexte
sécurisé (HTTPS ou localhost) : le bouton l'explique au lieu d'échouer en
silence.
Vérifié bout en bout avec le binaire whisper.cpp officiel : téléchargement
du modèle par le handler, puis une sinusoïde 440 Hz transcrite « (beeping) ».
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
43489769f4 |
Cartes raisonnement bornées et fluides ; version 0.11.0
Un long raisonnement (des milliers de tokens) faisait grandir la page de plusieurs écrans et finissait par figer l'affichage : le bloc entier était re-parsé en Markdown à chaque tick, en O(n²). Deux mesures : - Les cartes raisonnement/outils ont une hauteur bornée (280 px) avec défilement interne, collé en bas pendant la génération (un défilement manuel vers le haut est respecté). - En direct, un bloc de raisonnement géant n'est re-parsé que sur sa fin (REASON_TAIL) ; le texte complet est posé au rendu de fin de bloc. La finalité voyage avec le rendu en attente (renderPending.final) : le timer déjà armé passait final=false et consommait le rendu final en ne posant que la queue — le début du raisonnement n'était jamais rendu. Version 0.11.0 (const, versioninfo, .syso régénérés) ; README et RELEASE_NOTES à jour sur les nouveautés du fork. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7f5765160d |
Jeton Hugging Face réglable dans l'interface
Le jeton ne vivait que dans la variable d'environnement HF_TOKEN : découvrir depuis l'interface qu'un dépôt est verrouillé, c'était devoir éditer un docker-compose et recréer le conteneur pour y répondre. - Éditeur de preset → Modèle → « Jeton Hugging Face » : ligne repliée comme « Dossiers de modèles », qui affiche l'état (aucun / masqué / fourni par l'environnement). Le bandeau d'un dépôt verrouillé y mène d'un clic. - Le jeton est VÉRIFIÉ auprès de /api/whoami-v2 avant d'être enregistré (le compte s'affiche) : un jeton mal collé accepté en silence rendrait le 401 qu'on cherchait à expliquer. Il est rangé avec les secrets en base d'état, pas dans config.env que le changement de preset réécrit en bloc, et n'est jamais renvoyé en clair — seulement masqué. - Priorité : jeton enregistré, puis HF_TOKEN. Rien d'enregistré = comportement d'avant à l'identique. L'enregistrement vide le cache des réponses obtenues sans jeton. - Le jeton n'est envoyé qu'aux adresses Hugging Face : un lien collé vers un autre hébergeur n'a aucune raison de recevoir un secret. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hz1QWmvZqW3t53YC5SJBKC |
||
|
|
b339cced0a |
Dépôts Hugging Face verrouillés : dit lesquels, et pourquoi le 401
Un dépôt « gated » (orcarouter/Qwen3.8-27B-Uncensored-GGUF, vécu) laisse lire son arborescence sans rien : Loki listait donc ses seize quantifications avec leur verdict mémoire, puis échouait sur « HTTP 401 depuis la source » au premier octet. Le message ne disait ni que le dépôt était verrouillé, ni qu'il fallait accepter ses conditions, ni où poser un jeton. - Le refus est maintenant traduit à partir de X-Error-Code (GatedRepo, RepoNotFound, EntryNotFound…) et nomme le dépôt, l'action à faire et l'état du jeton : absent (il en faut un) ou présent mais sans accès. 404, 416, 429 et les pannes de la source y gagnent aussi une phrase utile. - Le verrou se voit AVANT de choisir une quantification : la recherche demande `expand[]=gated` et la fiche du dépôt est lue à l'ouverture, d'où une pastille « accès restreint » dans la liste et un avertissement en toutes lettres au-dessus des fichiers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hz1QWmvZqW3t53YC5SJBKC |
||
|
|
0f58ad7b49 |
Tâches planifiées : l'IA travaille toute seule (repris de l'amont AJEAN)
Portage des v0.9.9 → v0.10.2 de l'amont, la dernière vraie fonctionnalité qui nous manquait. Une consigne, une fréquence (« @every 2h », « tous les jours à 9h », ou une expression cron à 5 champs, dans le fuseau du navigateur), et l'IA l'exécute seule en arrière-plan. Preset épinglé par tâche (bascule de modèle avant l'exécution, attente du rechargement), accès mémoire et web réglables, interrupteur maître pour tout suspendre, bouton « tester maintenant ». Le planificateur est une goroutine, un tic par minute, dans le process qui détient la conversation et parle au moteur. Une seule tâche part par tic : de toute façon une seule inférence tourne à la fois, et étaler les départs évite qu'une rafale monopolise le modèle. Occupé ou modèle en cours de chargement n'est pas un échec — la tâche repasse au tic suivant. Une tâche ne partage QU'UN point avec le chat : le verrou de génération. Ni messages, ni journal d'affichage, ni epoch — elle construit son fil éphémère et le jette, ne gardant que son texte final comme compte-rendu (borné à 4000 caractères, réinjecté au passage suivant pour la continuité). Deux adaptations, parce que loki n'est pas l'amont : — Dossier de travail. Ici il appartient à la DISCUSSION ouverte : une tâche y aurait déposé ses fichiers, et en aurait changé en cours de route si l'utilisateur changeait de discussion — pour disparaître avec elle à la suppression. Chaque tâche a donc le sien (workspace/tasks/<id>/), stable d'un passage à l'autre. La bascule ne touche que les points d'entrée des OUTILS (agentCwd) : le panneau Fichiers, les dépôts et les liens des messages continuent de suivre la discussion de l'utilisateur. — Capacités. Mem est posé explicitement à MemOff quand l'agent est coupé : le zéro de MemMode est la chaîne vide, qu'EnabledTools ne reconnaît pas comme « coupée » — une tâche sans agent se serait vu offrir les outils mem_*. Le mode code reste off : rôles, critères et passe de vérification n'ont pas de sens sans personne en face. Le refus d'un message pendant qu'une tâche tourne dit maintenant LAQUELLE occupe le modèle : « génération en cours » sur un fil vide et immobile n'expliquait rien. Interface : section repliable dans les réglages (liste, état, prochain passage, pastille), modale d'édition bâtie sur le gabarit de l'éditeur de preset, panneau « dernier résultat » en markdown. Vérifié dans un vrai navigateur — création, rendu, réouverture en édition, bascule intervalle/cron, interrupteur maître, suppression — sans une seule erreur JS. |
||
|
|
03ae361ade |
Synchronisation avec l'amont AJEAN (v0.9.5 → v0.10.7)
Le fork est parti de la v0.9.4 ; l'amont en est à la v0.10.7. Reprise de ce qui manque VRAIMENT ici, en laissant de côté ce que loki a déjà résolu à sa façon (contexte MTP via --parallel 1, jauge de contexte, chrono de tour, vignettes d'images, ligne d'état de génération). Rendu du chat cadencé puis lissé (amont v0.9.5 issue #24, v0.10.5). Chaque token re-parsait le Markdown du bloc ENTIER : du O(n²) qui faisait ramer l'interface sur un long raisonnement — le moteur débitait toujours autant, mais les tokens semblaient arriver au ralenti et un simple rafraîchissement « réparait » tout. Le texte s'accumule désormais et n'est re-rendu qu'à intervalle adaptatif (16 ms sur un petit bloc, jusqu'à 500 ms sur un énorme), soldé à chaque frontière (outil, bascule de rôle, fin de tour, erreur, rejeu). Par-dessus, un lissage d'apparition découple l'arrivée de l'affichage : le décodage spéculatif rend les tokens par rafales, le texte sautait par paquets ; il s'écoule maintenant à cadence régulière. Rejeu exclu — relire un fil ne doit pas être une lente réécriture. Échantillonnage réglable par preset (amont v0.9.5/v0.9.6) : TEMP, TOP_P, TOP_K, MIN_P, PRESENCE_PENALTY, REPEAT_PENALTY, injectés dans chaque requête (donc sans redémarrage du moteur), vide = défaut du serveur. Sans ça seule la température voyageait et le reste retombait sur les défauts de llama.cpp, rarement ceux que recommande le modèle. Différence avec l'amont : REASONING_EFFORT n'est PAS traité là — loki lui réserve un chemin plus riche, et l'écrire ici écraserait `chat_template_kwargs`, donc la consigne « aucune ». Un seul message système, en tête, à l'envoi (amont v0.9.8, issue #26). steerSystem ne couvrait que les consignes de loki ; un historique venu d'ailleurs peut encore en porter deux, et Qwen3.x en --jinja répond alors « System message must be at the beginning ». Copie normalisée : l'historique affiché et persisté garde sa forme. Détection Vulkan multi-distro (amont issues #28, #29) : le chemin Debian codé en dur est invisible sur Fedora/RHEL/Atomic, où le plan de build retombait sur le CPU. ldconfig d'abord, puis les chemins connus. Dossier de travail (amont v0.10.2) : la consigne dit maintenant ce que le dossier EST — l'endroit par défaut de tout ce que le modèle produit — et nomme les dossiers système à ne pas toucher, au lieu d'interdire vaguement d'en sortir. Non repris : les tâches planifiées (~1200 lignes + interface, à décider), et le quoting cmd.exe par .bat temporaire (loki tourne en conteneur Linux). |
||
|
|
f3b0f78b64 |
Raisonnement : un gabarit qui refuse le niveau ne tue plus le tour
Qwen3.8-27B valide `reasoning_effort` au lieu de l'ignorer : il connaît xhigh/medium/low, pas « high » — le niveau que l'interface enregistre par défaut. Chaque message partait donc en 500, avec une trace jinja affichée en guise d'erreur, et le 500 tombait dans la branche « prompt trop long » de runChat : loki compactait l'historique pour rien avant d'abandonner. Le refus dit lui-même ce que le gabarit accepte. On le lit (llm_effort.go), on traduit le niveau demandé vers le plus proche sur l'échelle none/minimal/low/medium/high/xhigh — à égalité, le plus fort, dégrader en silence étant pire que générer un peu plus longtemps — et on rejoue le tour, historique intact. Sans liste annoncée, le champ est simplement retiré. « aucune » n'est jamais traduite : c'est une coupure, portée par `enable_thinking` que tous les gabarits comprennent. La traduction est retenue par modèle : les messages suivants ne repaient pas l'aller-retour. Le repli est tracé sur stderr, sinon l'intensité choisie dans l'interface n'est pas celle qui part au moteur sans que rien ne le dise. « maximale » (xhigh) rejoint la liste des niveaux proposés : aucun gabarit ne les connaît toutes, et sans elle le maximum d'un Qwen3.8 restait hors d'atteinte. Le repli couvre les gabarits qui la refusent. |
||
|
|
53d23418e9 |
CI : gofmt + gopls sous Go 1.25 ; MCP : premier lancement npx/uvx patient
- gofmt sur trois fichiers oubliés par le dernier commit (ci/test rouge). - gopls@latest exige Go ≥ 1.26 alors que l'image de build est en 1.25 avec GOTOOLCHAIN=local : l'étape Docker échouait net. GOTOOLCHAIN=auto télécharge le toolchain requis, borné à l'étape de build (build-push GHCR rouge). - Serveurs MCP : npx/uvx téléchargent leur paquet au premier lancement, et le délai de connexion de 20 s tombait dessus (« enregistré mais connexion échouée : context deadline exceeded » depuis le catalogue). Délai à froid de 3 min pour ces deux lanceurs, et erreur de délai réécrite pour dire quoi faire (réessayer : le paquet reste en cache). - README : note sur ce premier lancement, ligne mode Code dans le tableau des différences. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
901d518f70 |
Mode Code : agent de code avec critères, vérification et LSP
Sélecteur Chat|Code par discussion dans le pied du composeur ; en mode chat, une détection serveur suggère la bascule (puce ignorable, jamais automatique). Conception reprise d'OpenFox (MIT), réécrite en Go — voir NOTICE.md. - Outils fichiers : read (lignes numérotées, borné), grep, glob, et le tracker « lu avant d'écrire » qui refuse write/edit sur un fichier non lu ou modifié depuis la lecture ; edit préserve les fins de ligne CRLF. - Politique d'exécution : commandes catastrophiques refusées (rm -rf /, mkfs, reboot…), chemins bornés au dossier de la discussion en mode code, mutex par fichier. - Critères d'acceptation : contrat posé via l'outil criteria (éditable dans l'UI), passe de vérification indépendante sur contexte isolé — seule habilitée à marquer « passed » — puis corrections plafonnées. - Rôles embarqués (agents/*.md) : builder, planner, verifier, explorer, code-reviewer ; badge de rôle dans le fil. - LSP : gopls / typescript-language-server / pyright (inclus dans l'image), diagnostics injectés dans le retour de write/edit. - Git natif : git_status, git_diff, git_clone (borné à la discussion). - Jobs d'arrière-plan bash_bg/bash_tail (serveur de dev, build long). - Auto-retry : un appel d'outil écrit en texte (default_api:…, <tool_call>…) relance le tour une fois avec consigne corrective. - Outil ask : question à choix rendue en carte à boutons. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
69e89e5c2f |
Compteur de vitesse par bulle, et budget souple d'appels d'outils
Deux défauts révélés par un tour d'agent d'une heure (~50 appels d'outils) sur un modèle très quantifié. 1. La vitesse affichée sous les réponses tombait de 17 tok/s à 0,9 au fil du tour, ce qui donnait à croire que le moteur s'effondrait. Il n'en était rien : un tour d'agent ouvre une bulle NEUVE après chaque appel d'outil (le flux repasse contentEl à null), mais les compteurs n'étaient jamais remis à zéro. Chaque bulle affichait donc le CUMUL de tout le tour divisé par le temps écoulé depuis le tout premier token — exécution des outils, pages web et prefill compris. La vitesse convergeait mécaniquement vers « tokens générés ÷ durée totale du tour ». Les compteurs sont maintenant remis à zéro à la CRÉATION de la bulle, ce qui couvre tout chemin qui en ouvre une neuve, aujourd'hui comme demain. Rejoué sur un tour synthétique où le moteur décode à 20 tok/s constants entre deux outils de deux minutes : 20,5 / 0,6 / 0,5 tok/s avant, 20,5 sur les trois bulles après. La durée « travail », elle, reste bien celle du tour entier — c'est sa définition. 2. Rien n'exerçait de pression sur un tour qui tourne en rond. Le plafond d'itérations avait été retiré en v0.6.3 (il coupait des recherches légitimes) et la déduplication d'appels ne rattrape pas ce cas : sa clé est « nom + arguments bruts », or relire le même fichier par tranches (`sed -n '1,80p'` puis `sed -n '80,160p'`) produit des clés différentes. D'où un budget SOUPLE : au-delà de 24 appels d'outils sur un tour, on rappelle au modèle combien il en a déjà faits et on lui demande de conclure. Le rappel revient à chaque palier en durcissant le ton, et ne coupe jamais le tour. Il est ajouté EN FIN d'historique, ce qui laisse intact le préfixe déjà en cache côté llama-server, et n'est pas persisté. `AGENT_BUDGET` dans config.env règle le palier, `off` le désactive. Élargir plutôt la clé de déduplication à la CIBLE de l'appel a été écarté : deux tranches d'un même fichier renvoient un contenu différent, les confondre casserait toute lecture paginée légitime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5UndZ9DedRXPuAoRXbmDb |
||
|
|
0a3af60715 |
Intensité du raisonnement dans la barre de saisie, angles moins arrondis
Le niveau de raisonnement n'existait que dans l'éditeur de preset : le changer demandait d'ouvrir les réglages, éditer, enregistrer. Or ça se décide au moment d'écrire le message. - Nouvelle route /api/reasoning-effort (GET/POST), calquée sur /api/memory : elle écrit REASONING_EFFORT dans la config vivante, relue à chaque requête au moteur. Aucun redémarrage — la valeur voyage dans le corps de la requête. - Liste blanche côté serveur : cette clé finit dans une requête au moteur, on n'y laisse pas passer une chaîne arbitraire venue du navigateur. - La liste est grisée quand le raisonnement est coupé pour ce modèle, plutôt que masquée : le réglage reste trouvable, et l'infobulle dit où le rallumer. - L'éditeur de preset garde le même réglage comme DÉFAUT du modèle. Appliquer un preset réécrit toute la config, donc il reprend la main sur le choix fait à la volée — les deux sous-titres le disent, sinon la double présence intrigue. Angles : les rayons allaient de 5 à 26px selon les composants, ce qui donnait des cartes très rondes. Tout est ramené à 4px (cartes, boutons, modales) et 3px (petits éléments), y compris les formes multi-valeurs comme la barre de saisie (26px 26px 0 0 → 3px 3px 0 0). Les pastilles (999px) et les ronds (50%) sont laissés intacts : ce sont des interrupteurs et des avatars, pas des cartes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
847aad217b |
MCP : catalogue embarqué de serveurs connus
Ajouter un serveur MCP demandait d'écrire soi-même `npx -y @scope/paquet …` dans la modale, en devinant le nom du paquet. Le catalogue liste une vingtaine de serveurs connus, commande déjà renseignée, classés par catégorie. - internal/loki/mcp_catalog.json est EMBARQUÉ dans le binaire (go:embed), pas interrogé sur le réseau : Loki tourne hors ligne, et rien de ce qui s'exécute sur la machine ne vient d'un annuaire distant. Pour proposer un serveur de plus : éditer le fichier et recompiler. - Choisir une entrée n'installe rien. Ça préremplit la modale d'ajout existante et l'utilisateur relit la commande avant d'enregistrer — un serveur stdio exécute un process arbitraire, au même titre que l'outil bash du mode agent. Aucune ligne de mcp_client.go n'est touchée : le catalogue s'arrête à l'affichage, tout le reste passe par l'API MCP déjà en place. - Une entrée dont le runtime manque (npx ou uvx absent) le signale dans la liste, plutôt que de laisser l'utilisateur découvrir l'échec au démarrage du serveur. - Les entrées qui réclament une clé d'API la rappellent avant l'enregistrement, au lieu d'enregistrer un serveur qui ne peut pas se connecter. - Les entrées Python publiées avant le SDK MCP 2.0 sont épinglées avec `uvx --with "mcp<2"` : sans cela elles plantent sur ImportError: McpError. - Le Dockerfile gagne uv/uvx (~35 Mo). Sans lui, 9 des 21 entrées s'affichent sans pouvoir démarrer dans le conteneur. Intensité du raisonnement (REASONING_EFFORT) L'interrupteur Raisonnement était binaire. llama-server accepte `reasoning_effort` dans /v1/chat/completions : `none` coupe le raisonnement, toute autre valeur est passée au gabarit jinja du modèle. - Nouvelle clé de preset REASONING_EFFORT — donc réglée par modèle, comme le reste du preset. Vide = on n'envoie rien et le gabarit garde son comportement. - Pas de repli à prévoir : un gabarit qui ne lit pas la valeur l'ignore sans erreur. En pratique seuls gpt-oss et apparentés changent de comportement, et le sous-titre du réglage le dit — promettre un effet universel serait faux. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b378311873 |
Moteur : mettre à jour llama.cpp sans reconstruire l'image
llama.cpp publie plusieurs versions par jour ; l'image de Loki ne se reconstruit qu'à une mise à jour de Loki. Le moteur y était donc figé à la date du dernier build, et le rattraper imposait un rebuild complet de 2,6 Go pour un composant qui en pèse 170 Mo. Le panneau « Moteur » ne le disait même pas : il annonçait « rien à mettre à jour ici ». Réglages → Moteur affiche maintenant la version qui tourne (bXXXXX et son commit, lus dans la bannière du binaire — jusqu'ici invisibles ailleurs que dans le journal du moteur) et la met à jour en un clic. Où le moteur est pris. Pas dans les releases GitHub de llama.cpp : elles ne contiennent AUCUN binaire CUDA pour Linux, et le mode « précompilé » retomberait sur Vulkan, donc sur une régression pour une carte NVIDIA. La seule distribution CUDA/Linux officielle et précompilée est l'image de conteneur — celle-là même dont l'image de Loki hérite. On lit son manifeste OCI et on ne télécharge que les couches qui portent /app, en descendant du sommet : le runtime CUDA (2 Go) et la base système sont déjà là. S'arrêter au binaire ne suffit pas — llama.cpp le range dans une couche et ses .so dans la précédente — d'où une règle d'arrêt sur « binaire + libggml-base + libllama », et un garde-fou de taille qui interdit de descendre jusqu'au runtime. Le moteur atterrit dans /data/engine/<version>/, donc sur le volume de données : il survit à un docker compose pull. La variante (CUDA, Vulkan, SYCL, MUSA, CPU) est déduite des backends ggml posés à côté du moteur courant — le conteneur ne sait pas de quelle image il vient, et faire retenir « server-cuda » à l'utilisateur serait un piège. Le risque, et ce qui le couvre. La mise à jour apporte llama.cpp, pas le runtime CUDA, qui reste celui de l'image : un llama.cpp compilé pour un CUDA plus récent ne chargerait pas son backend GPU. Le symptôme serait silencieux — tout marche, mais sur le processeur. Le nouveau moteur est donc lancé à blanc avant toute bascule ; il est refusé s'il ne démarre pas, ET s'il ne voit plus aucune carte alors que le moteur courant en voyait. Dans les deux cas le moteur courant n'est pas touché, et celui de l'image reste intact : « revenir au moteur de l'image » y ramène en un clic, sans réseau. Vérifié de bout en bout contre le vrai ghcr.io (166 Mo, 7 s, toutes les bibliothèques et leurs liens de version présents) et, pour les chemins d'échec, contre un faux registre. LOKI_OCI_REGISTRY permet de viser un miroir quand ghcr.io n'est pas joignable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd |
||
|
|
b518e98b43 |
Accès OpenAI : servi par Loki, par domaine ou par IP
L'endpoint compatible OpenAI n'était pas servi par Loki : le panneau annonçait l'adresse de llama-server lui-même, http://<ip>:8080/v1. Dans le déploiement de référence de ce fork, cette adresse ne peut joindre personne — le port 8080 n'est pas publié par le conteneur, l'entrypoint sème HOST=127.0.0.1, et l'IP annoncée est celle du bridge Docker. L'autre voie proposée, « exposer en public (ajean.link) », exigeait un jeton de relais que ce fork ne permet plus d'obtenir : l'interrupteur ne pouvait que renvoyer vers un panneau supprimé. Désormais, Loki sert /v1/* SUR SON PROPRE PORT et relaie vers le moteur. L'API est donc joignable partout où l'interface l'est — IP du réseau local, nom de domaine, reverse proxy — sans publier de second port ni ouvrir le moteur. Serveur - mountOAI (llm_oai.go) monte /v1/ sur le mux, et RIEN d'autre : ni /metrics, ni /props, ni /slots, qui divulgueraient le modèle chargé et l'état des slots. Le filtre interne d'oaiHandler reste en seconde barrière. - requireCompletionKey (web_auth.go) garde cette surface avec la clé des COMPLÉTIONS, pas celle de pilotage : un client OpenAI n'a qu'un en-tête Authorization, et on veut pouvoir lui donner l'accès au modèle sans le droit de redémarrer la machine. Erreurs au format d'OpenAI (body.error.message), que les SDK savent présenter. Le préflight CORS passe sans clé — il n'en porte jamais, et le refuser casserait tout client tiers de navigateur. - effectiveAPIKeyErr (backend_config.go) devient la source unique de la clé exigée : base d'abord, config.env en repli, exactement comme le moteur. Sans ce miroir, un API_KEY résiduel donnait un endpoint « ouvert » côté Loki et un 401 côté moteur, sans rien pour l'expliquer. Lecture ratée = refus, jamais ouverture (même raisonnement que readWebKeyErr). - oaiHandler passe à ReverseProxy.Rewrite : le port du moteur est relu à chaque requête au lieu d'être figé à la construction — il visait l'ancien port dès qu'on changeait PORT, jusqu'au redémarrage de Loki. - withLocalAuth (relay_link.go) n'injecte plus la clé de pilotage sur /v1 : elle aurait été refusée par la garde, et surtout relayée au moteur. Le trafic du tunnel est marqué (en-tête effacé avant d'être posé, sinon un client le forge) et la surface y reste fermée tant que oai_public est faux — la promesse du tunnel est tenue. Adresse affichée - web_public_url.go : normalisation d'une adresse publique saisie à la main (schéma ajouté, /v1 recopié toléré, chemin refusé), origine de la requête via Host + X-Forwarded-Proto, et la règle de priorité entre les deux. - Le calcul quitte le navigateur pour le serveur : c'est la concaténation côté client qui produisait l'adresse fantôme. Interface - Le panneau perd l'interrupteur ajean.link et l'interrupteur d'écoute LAN — ce dernier n'a plus d'objet, et deux interrupteurs pour « rendre l'IA joignable » était la confusion à lever. La route /api/network et `loki network` restent pour qui veut exposer le moteur en direct. - Il gagne un champ « adresse publique » (facultatif, pour le reverse proxy) et un avertissement rouge tant qu'aucune clé n'est définie — l'endpoint est maintenant ouvert PARTOUT où l'interface l'est, ça ne se dit pas à voix basse. Le démarrage de `loki web` le crie aussi. Vérifié bout en bout sur le serveur réel : liste des modèles à travers Loki avec la clé (200), sans la clé (401), et complétion en streaming dont les tokens arrivent espacés de 120 ms — le flux traverse bien le double proxy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd |
||
|
|
f99e5b59b8 |
Refonte de l'interface : direction « Sober Tech »
Reprise du design d'après la maquette fournie, sans changer d'architecture : l'UI reste du HTML/CSS/JS assemblé dans le binaire (voir plus bas). Charte - Palette ardoise + sauge en remplacement des bruns « Terre ». Deux variantes : claire par défaut (celle de la maquette) et « Deep Dark » (fond #0F172A, cartes #1E293B), à un clic depuis l'en-tête. Une variable --on-accent porte ce qui s'écrit sur les aplats de sauge, pour que le contraste tienne dans les deux variantes. - Typographie Inter (interface) + JetBrains Mono (code, chiffres, chemins), en sous-ensemble latin, embarquées comme l'étaient Bricolage et IBM Plex Mono — toujours aucune requête vers un service de polices. - Boutons sans contour, fond au survol, léger enfoncement au clic. - Coque à plat : plus de cartes flottantes séparées par une gouttière, la barre latérale est collée au bord et le fil occupe tout le reste. Coque - Barre d'en-tête : titre de la discussion + sélecteur de modèle. Changer de preset demandait d'ouvrir la barre latérale et de descendre jusqu'aux presets ; c'est désormais une liste déroulante, toujours visible. - Barre latérale en trois zones — marque, contenu défilant, moniteur machine — et escamotable d'un clic sur grand écran. Les discussions y passent au premier plan, avec une recherche textuelle (filtrage côté client : la liste est déjà chargée, sans les messages) ; les réglages descendent sous un repli unique, d'où openDetails(), qui déplie aussi les parents. - Moniteur machine en pied de colonne : une jauge par ressource (charge GPU, puis VRAM et température en détail, mémoire vive), sauge jusqu'à 75 %, ambre puis rouille quand la machine sature. Il remplace la section « Machine », qui disait la même chose en plus verbeux et en plus loin. - L'option « barre latérale escamotable » disparaît d'Apparence : elle faisait de la barre un tiroir posé SUR la conversation, là où le bouton d'en-tête l'escamote pour de bon. Deux mécanismes concurrents pour la même intention. Fil et saisie - La réponse de l'IA redevient une carte, la question un aplat de sauge : sur un fond de fil coloré, du texte nu flottait sans ancrage. - Saisie en pilule, ses outils (joindre, fichiers, envoyer) dans la barre elle-même. L'envoi est une pastille ronde à flèche ; le mot « Envoyer » reste porté par aria-label et l'infobulle. Corrections croisées - L'heure d'une discussion prenait la moitié de la ligne (flex:1 hérité de `.preset>span`), le titre était tronqué au premier mot. - Une liste déroulante posée directement dans une ligne de modale débordait sous son intitulé quand sa valeur était longue (chemin de destination). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd |
||
|
|
001d633750 |
Un dossier de fichiers par discussion
Les fichiers du chat vivaient dans un pot commun : on changeait de discussion et on revoyait les mêmes pièces jointes, et supprimer une discussion laissait derrière elle tout ce qu'on y avait déposé ou fait écrire à l'agent (seules ses captures, déjà rangées par identifiant, partaient avec). Chaque discussion a désormais son dossier, <workspace>/discussions/<id>/ : - les dépôts (uploads/), les captures (captures/) et ce que l'agent écrit y atterrissent ; le shell et les chemins relatifs du modèle y sont résolus ; - le panneau Fichiers s'ouvre sur ce dossier et n'en sort pas, et se redessine quand la discussion change (bascule ou vidage, signalés par le flux SSE) ; - supprimer une discussion — ou la vider — emporte ses fichiers. Les deux gestes le disent maintenant avant de demander confirmation ; « clear chat » en demandait aucune. La racine du dossier de travail reste la borne de sécurité : les liens des anciens messages (uploads/x.pdf, captures/<id>/y.jpg) continuent d'ouvrir leur fichier par un chemin de repli. Au démarrage, une migration range les captures dans le dossier de leur discussion et rend chaque dépôt à la discussion qui le mentionne dans son journal ; ce que personne ne réclame reste à la racine, atteignable par le bouton « hors discussion » du panneau, qui disparaît une fois le ménage fait. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd |
||
|
|
00b9cc2aee |
Panneau Fichiers : accéder à ce que l'agent écrit
L'agent produit des rapports, des scripts, des exports et des captures dans son dossier de travail. On ne pouvait en récupérer un que si le modèle avait pensé à en mettre le lien dans sa réponse : tout ce qu'il écrivait sans le dire restait invisible, et faire le ménage demandait un shell. Un panneau de droite, ouvert et fermé par le bouton dossier du pied de carte, liste ce dossier avec navigation dans les sous-dossiers, téléchargement et suppression. Il pousse la conversation au lieu de la recouvrir — la largeur de lecture étant pilotée par --measure, le fil se resserre et se recentre tout seul. Sur téléphone c'est un tiroir, comme les réglages à gauche. Le socle existait : /api/chat/file téléchargeait déjà, workspaceRel bornait déjà les chemins, downloadWorkspaceFile gérait déjà le blob. Manquaient les deux verbes qui comptent, /api/chat/files pour lister et /api/chat/file/delete pour supprimer. Trois choix qui méritent d'être dits : - Un dossier affiche la taille de TOUT son contenu, pas celle de son inode : c'est ce qu'on libère en le supprimant, donc c'est le chiffre qui aide à décider. Le parcours est borné à 20 000 entrées pour que la liste reste instantanée si l'agent y dézippe quelque chose d'énorme. - La racine du dossier de travail est refusée à la suppression : l'agent y perd le dossier dans lequel il écrit, et « tout effacer » ne doit pas être à un clic. La suppression d'un dossier annonce le nombre de fichiers et le poids emportés avant de demander confirmation. - Le panneau ne se remplit qu'à l'ouverture. Un ReadDir récursif à chaque chargement de page pour un panneau fermé serait payé par tout le monde. Ouvrir le panneau rétrécit la conversation SANS déclencher de `resize` : la gouttière de barre de défilement mesurée dans --sbw resterait celle de l'ancienne largeur et la carte de saisie serait décalée du fil. On resynchronise donc explicitement ; la hauteur, elle, est déjà suivie par un ResizeObserver. Vérifié. Six tests Go sur le bornage, qui est la partie sensible — ces routes suppriment sur disque à partir d'un chemin venu du navigateur : « .. », chemins absolus, racine, et un lien symbolique posé dans le dossier de travail sont tous refusés, et le fichier voisin survit. Puis au navigateur, en clair et en sombre : listing trié dossiers d'abord, taille récursive, descente et fil d'Ariane, téléchargement réel (l'événement download porte bien rapport.md), suppression confirmée puis disparition de la ligne, persistance de l'état ouvert, tiroir mobile avec voile, et la saisie reste alignée au pixel sur le fil (0 px des deux côtés) une fois le panneau ouvert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VueWA9xcYadaYq65tBisix |
||
|
|
133b30421a |
Postes distants : retirer le bouton oublié du composeur
Le retrait de l'accès distant s'était arrêté à mi-chemin. La section « Accès distant » de la barre latérale et son module 13-remote.js ont bien disparu au commit |
||
|
|
e7863d732c |
README : remettre la documentation en accord avec l'application
Le README décrivait encore l'état d'AJEAN sur plusieurs points devenus faux, et passait sous silence ce que ce fork a ajouté. Corrections de fond : - L'accès distant chiffré ajean.link était annoncé en tête et dans les fonctionnalités. Sa section d'interface et son module JS ont été retirés ; le code serveur reste en place mais inerte, et c'est ce que le README dit maintenant plutôt que de promettre une fonctionnalité absente. - Le catalogue de modèles distant est documenté comme retiré, avec la raison. - Le titre de discussion vient du premier message, pas d'un modèle : dire « généré automatiquement » aurait laissé croire à un appel d'inférence. - L'outil de capture d'écran est toujours proposé au modèle ; c'est sa DESCRIPTION qui suit la vision réelle du moteur. La première rédaction disait que l'outil était retiré — c'est faux. - Les captures vivent sous workspace/captures/<id>/, pas directement sous /data. Ajouts : - Section « Installer un modèle » : recherche Hugging Face, verdict mémoire et son caractère estimatif assumé, projecteur vision du même dépôt, repères sur les quants Dynamic d'unsloth et sur ggml-org, mode expert par lien direct. - Section « Données et persistance » : ce que contient chaque chemin sous /data, la commande docker inspect pour vérifier le montage, et les deux pièges rencontrés sur Unraid — /mnt/user contre /mnt/cache, et les conteneurs relancés avant que le pilote Nvidia soit chargé (ERROR init result=11). - Discussions multiples, identité (prénom + avatars), regroupement des Paramètres, HF_TOKEN dans le tableau des variables. - Le tableau des différences avec l'amont gagne quatre lignes : panneau Moteur, choix du modèle, historique de tchat, accès distant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VueWA9xcYadaYq65tBisix |
||
|
|
26e93f43ef |
Chercher et installer un modèle depuis Loki
Installer un modèle demandait d'aller sur huggingface.co, de naviguer dans l'arborescence d'un dépôt et de coller un lien à la main. Rien ne disait si le fichier tiendrait en mémoire, et surtout rien ne reliait un modèle à SON projecteur vision : c'est ainsi qu'un mmproj-Qwen3VL-8B s'est retrouvé configuré pour un Qwen3.8-27B — deux modèles sans rapport, moteur qui démarre et ne voit rien. La recherche Hugging Face arrive dans l'éditeur de preset. Elle ne remonte que les dépôts GGUF ; déplier un dépôt montre ses quantifications avec leur taille et un verdict mémoire, et propose le projecteur vision DU MÊME DÉPÔT — le seul qui corresponde. Quand le dépôt n'en publie pas, Loki le dit au lieu d'aller en chercher un ailleurs. Le nouveau code ne télécharge rien : il produit des URL que le chemin existant consomme tel quel (normalizeHFURL, shardURLSet, sonde d'espace disque, reprise et annulation). Une file d'attente enchaîne modèle puis projecteur — le serveur ne mène qu'un transfert à la fois, et le champ Vision ne se remplit que si le modèle est déjà sélectionné. Trois familles de .gguf cohabitent dans un dépôt et ne veulent pas dire la même chose : mmproj-* (projecteur), mtp-* (poids de décodage spéculatif) et le modèle. Les deux premières ressemblent à un modèle ; les proposer en vrac, ce serait offrir de lancer llama-server sur un encodeur d'images. Les tranches d'une famille sont repliées en une entrée de taille TOTALE : annoncer 15 Go pour un modèle qui en occupe 45 promet une place qui n'existe pas. Le verdict mémoire s'appuie enfin sur le GPU. detectHardware() ne renvoyait que la RAM système alors que detectGPUs() existait déjà : sur un serveur à carte NVIDIA, le verdict se prononçait sur la mauvaise grandeur. Le coût du cache KV reste une estimation assumée — l'exact demanderait de parser l'en-tête GGUF — et l'interface l'annonce comme telle plutôt que d'afficher un chiffre faussement sûr. Le catalogue ajean.link disparaît. Sa route n'avait aucun consommateur (l'écran d'accueil qu'elle attendait n'a jamais existé), son repli embarqué proposait du Qwen2.5 de 2024, et un fork qui laisse le serveur de l'amont décider de ce qu'il propose n'est pas vraiment un fork. Une piste écartée en cours de route : marquer les dépôts « vision » d'après les tags Hugging Face. Ils mentent — des deux dépôts GGUF de Qwen3.8-27B qui publient tous deux un mmproj, seul ggml-org est taggé image-text-to-text. Une pastille sur l'un et pas sur l'autre aurait été pire que rien. La vision est donc déduite de la seule source qui ne se trompe pas : la présence d'un mmproj-*.gguf dans l'arborescence. Vérifié : 8 tests unitaires sur les arborescences réelles des deux dépôts, puis au navigateur contre le vrai Hugging Face — 25 dépôts trouvés, 3 quants listés sans aucun mtp ni mmproj, projecteur Q8_0 proposé et coché, verdicts affichés avec leur explication, modale dans l'écran. L'ordre de la file (modèle puis projecteur) est testé en interceptant les appels, sans transfert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VueWA9xcYadaYq65tBisix |
||
|
|
1e232a7ff3 |
Simplification radicale : moteur pris de l'image officielle llama.cpp
Plus aucune compilation CUDA : le runtime part de ghcr.io/ggml-org/llama.cpp:server-cuda (llama-server précompilé et maintenu par l'équipe amont, backends .so chargés dynamiquement, archs GPU courantes, repli CPU fonctionnel). Le build complet passe de ~40 min à ~4 min et tous les pièges du build CUDA sans GPU (stubs libcuda, espace disque du runner, choix des architectures) disparaissent. - Dockerfile : 2 étapes (Go + image officielle), build-arg LLAMACPP_IMAGE pour épingler une version ou passer en CPU/Vulkan - entrypoint : BIN=/app/llama-server - workflow GHCR : purge disque et CUDA_ARCHS supprimés, cache max - compose/.env/README à l'avenant Validé : build 3 min 55, UI HTTP 200, healthcheck healthy, superviseur PID OK, llama-server démarre (repli CPU) et n'échoue que sur un modèle factice. |
||
|
|
bb9aa9f559 |
Attribution du fork, README, logo UI et workflow GHCR
- NOTICE.md : Loki est un fork d'AJEAN (nathaninline, MIT), liste des modifications ; LICENSE amont conservée à l'identique. - README réécrit : bandeau fork, architecture conteneur, démarrage Docker, procédure Unraid (plugin Nvidia Driver), différences avec l'amont. - UI : le logo pixel-art épelle désormais LOKI (il épelait encore AJEAN), infobulle d'attribution sur la marque ; index.html régénéré. - Workflow GHCR : libération d'espace disque du runner (l'étape CUDA devel ne tient pas dans les ~14 Go libres), build-args CUDA_ARCHS/LOKI_VERSION, cache GHA en mode min (plafond 10 Go). |
||
|
|
a85341da32 |
docs: projets par session
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
71f8ba0d03 |
feat: toggle skills UI + Node.js dans l'image + docs MCP/skills
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cb2872c78a |
perf: fix model reload thrash and cut time-to-first-token
Ollama reloaded the chat model mid-message because plan/summary/router calls sent divergent runner options (no num_ctx/num_batch) and omitted keep_alive — the main cause of perceived slowness. - share one keep-alive httpx.AsyncClient for all Ollama calls - unify runner options (runner_options) + keep_alive on every model call, embeddings included - drop the blocking LLM routing fallback (pure lexical heuristic) - run RAG recall + plan + code-model pick in parallel inside the SSE stream, after the start event - RAG cosine scoring off the event loop; cache /api/tags 30s and nvidia-smi 5s; frontend polls 2s->5s, warm poll backoff, dedup config fetch feat: working session menu in TopBar (switch/create/rename/delete) feat: workspace file deletion (DELETE /api/files + UI trash buttons) docs: recommended Ollama env vars (KEEP_ALIVE, MAX_LOADED_MODELS...) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
552548e4b4 |
Niveau supérieur (inspiré de Jean) : modes Plan/Build/Yolo + panneau Git & Diff
Modes d'exécution (composer) : - routes/chat : champ mode ; _apply_mode adapte la config — plan = outils lecture seule + plan forcé ; yolo = confirm_shell off ; build = comportement normal. - Frontend : ModeSelector dans le composer, mode dans le store + requête chat. Panneau Git & Diff (le workspace est un dépôt git, Aider commite) : - routes/git : /api/git/log (commits), /api/git/diff (commit ou working), /api/git/revert (git revert --no-edit, confiné au workspace). - Frontend : onglet Git dans le PreviewPanel — liste des commits, diff colorisé, bouton Annuler (revert) avec rafraîchissement de l'arbo. Bonus : le panneau Matériel explique que « GPU déclaré 12 Go » vient de GPU_VRAM_MB (valeur manuelle) — à corriger en 16000 pour une 16 Go. Tests : git log/diff/revert, modes plan/yolo/build via la route chat, builds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N |
||
|
|
d9169a1d27 |
Préchargement des modèles : warm + keep_alive + indicateur d'état
Évite le rechargement lent quand Ollama a déchargé le modèle de la VRAM : - ollama_client.warm() : précharge un modèle (/api/generate sans prompt) ; chat() accepte keep_alive (durée de rétention en VRAM). - agent.run_agent transmet keep_alive ; route chat le passe depuis la config. - config : champ keep_alive (défaut 30m) par profil de modèle. - routes/models : POST /api/models/warm, GET /api/models/loaded (placement GPU/CPU via /api/ps). - main : préchargement du modèle par défaut au démarrage (arrière-plan, best-effort — n'empêche pas le démarrage si Ollama est absent). - Frontend : préchargement automatique à la sélection d'un modèle, poll des modèles chargés (8s), pastille verte (GPU) / orange (CPU) / blanche (à charger) dans le sélecteur, réglage 'Maintien en VRAM' dans Configuration. Tests : warm/loaded routes, keep_alive transmis, démarrage résilient sans Ollama, build front. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N |
||
|
|
d9be1c4dda |
Les 5 évolutions : plan, auto-critique, RAG, vérif HTML, benchmark
1. Plan-puis-exécute (enhance.make_plan) : les demandes complexes sont décomposées en 3-5 étapes (event SSE 'plan', carte PLAN dans le fil, meta.plan persisté) ; le plan guide l'agent et le moteur code. 2. Auto-critique « Qualité + » (enhance.self_review, toggle Intelligence) : critique éclair puis révision de la réponse (event 'revision'). 3. Mémoire long-terme RAG (rag.py) : échanges vectorisés via /api/embed (modèle d'embedding auto-détecté), rappel cosinus top-3 inter-sessions injecté en contexte, indexation en arrière-plan, élagage à 2000 souvenirs. 4. Vérification HTML (tools.check_html) : références locales cassées et balises déséquilibrées ; branchée sur l'auto-vérification des outils ET sur le moteur code avec une passe d'auto-correction Aider. 5. Benchmark intégré (bench.py + /api/bench) : 5 épreuves notées /100 (appel d'outil, code exécuté en sous-processus isolé, consignes, JSON, format), streaming SSE, scores stockés ; carte BENCHMARK dans l'UI. Config : plan_mode / self_review / rag_enabled / embed_model + carte Intelligence (3 toggles). Client SSE : events plan/revision ; PlanCard. Tests : heuristique+parsing du plan, révision, index/rappel RAG (exclusion de la session courante), html_check, bench 100/100 sur modèle simulé, intégration chat HTTP (event plan + meta persisté), build front. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N |
||
|
|
cf1444f7c0 |
Niveau supérieur : mémoire compressée, outils chirurgicaux, modèle code auto
Faire d'un petit modèle un bon agent : - memory.py : contexte = invite + résumé des anciens tours + 10 derniers messages ; résumé régénéré en arrière-plan après chaque réponse (colonne summary sur sessions, migration douce). Contexte court = modèle concentré et qui tient sur le GPU. - tools.py : edit_file (recherche/remplacement exact, erreurs pédagogiques, unicité exigée), grep_search (regex bornée, dossiers ignorés), et vérification syntaxique auto (.py/.json) après chaque écriture — l'erreur revient au modèle qui se corrige dans le même tour. - coder.pick_code_model : les tâches de code vont au meilleur modèle code installé (qwen-coder, deepseek-coder…) via config code_model=auto. - Branché dans la route chat (mémoire + résolution du modèle code) et la boucle agent (code_task) ; UI : descriptions et glyphes des nouveaux outils. Tests : edit/grep/vérification, compression mémoire (19 msgs -> 12 dont résumé), pick auto/explicite, chat HTTP de bout en bout, build front. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N |
||
|
|
ac801aa71b |
Moteur code façon Claude Code : Aider intégré avec routage automatique
- coder.py : enveloppe Aider (version figée 0.86.2), commits git auto dans le workspace, verrou d'exécution, dégradation propre si absent - router.py : classification automatique de chaque message (heuristique lexicale + micro-classification LLM sur les cas ambigus) — invisible pour l'utilisateur - routes/chat.py : routage code/agent, chemin code avec keepalive SSE, cartes fichier + commit dans le fil, meta.engine persisté - agent.py : outil code_task exécuté en thread — l'agent peut coder lui-même - tools/agent_config : code_task enregistré (actif par défaut), invite système mise à jour - main.py : workspace auto-initialisé en dépôt git au démarrage - Dockerfile : git ; requirements : aider-chat==0.86.2 - UI : glyphe code_task, description outil, aperçu d'instruction Tests : heuristiques 7/7, routage HTTP code+agent de bout en bout (SSE, meta persistée), agent appelant code_task, build front. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N |
||
|
|
e24d397d31 | Add per-model GPU and context profiles |