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
Le flux n'envoie plus tout le résultat d'un outil : un aperçu de 1600
caractères, sa taille RÉELLE et l'id de l'appel. Le bouton « voir plus »
charge le reste à la demande (/api/chat/tool-result), et déplie vraiment le
bloc (plafond de 280 px levé) au lieu de le laisser scroller. « voir plus »
et « copier » vivent dans la même barre, ancrée en bas à droite d'un
bloc-parent non scrollant : elle reste au coin quand on scrolle le résultat.
Le compteur « ~N tok » de la bulle disait la taille de ce que l'UI avait
reçu, donc toujours le plafond sur un long résultat. Il lit maintenant
result_chars, la taille réelle.
MCP : plus de troncature à 12000 caractères avant le modèle (même règle que
la lecture mémoire). Un serveur MCP est configuré exprès pour ce qu'il rend ;
le couper au milieu rendait la réponse inutilisable. L'UI, elle, n'en affiche
que l'aperçu.
Reprises d'AJEAN 0.15.1, 0.15.2 et 0.15.4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
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.
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>
Deux manques dans la présentation d'un tour :
1. Rien ne disait sur QUELLE discussion un agent était en train de
travailler. La ligne concernée porte maintenant un anneau qui tourne,
à gauche du titre — et l'en-tête aussi, puisque la barre latérale est
escamotée sur téléphone. L'état vient de deux sources : le flux SSE
(instantané) et /api/conversations, qui expose désormais `busy` pour
que la page ouverte en cours de génération s'anime sans attendre le
rejeu du journal. L'indicateur est posé et retiré SANS redessiner la
liste : un redessin relancerait l'animation à chaque événement.
2. Le pied des réponses ne montrait que la vitesse du moteur, jamais le
temps que le tour avait pris. Il porte maintenant « travail 1 min 04 s »
— de la question envoyée à la fin du tour, raisonnement, appels
d'outils et attentes compris. La durée avance à la seconde pendant le
tour et se fige au turn_done ; elle apparaît aussi sur l'indicateur
« … », là où il n'y a pas encore de réponse sous laquelle écrire.
La bulle de raisonnement gagne au passage sa propre durée.
La mesure est prise sur les HORODATAGES SERVEUR (événements `user` et
`turn_done`) : elle est donc juste en direct comme au rejeu, où tout
arrive d'un bloc côté client. Le compteur vivant se recale sur l'écart
entre l'horloge du navigateur et celle du serveur, réévalué à chaque
événement reçu en direct — un téléphone n'est pas à la même heure que la
machine.
« Masquer la vitesse de génération » ne masque plus que la vitesse : la
durée n'est pas une mesure de moteur, c'est ce que la réponse a coûté.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5UndZ9DedRXPuAoRXbmDb
L'amont ne connaît qu'un fil unique (bkChat/conversation) que « clear chat »
effaçait définitivement. On garde toute la machinerie (un seul conv en
mémoire, mêmes flux SSE, même compactage) mais rangée par discussion :
bkChat/index liste des discussions (métadonnées seules)
bkChat/active discussion ouverte, partagée par tous les appareils
bkChat/conv:<id> état complet d'une discussion
Basculer réutilise le mécanisme d'epoch du reset : les abonnés SSE reçoivent
{reset:true} et rejouent le nouveau fil — aucun code de rendu à toucher. Le
fil unique existant est repris comme première discussion au premier
démarrage, et sa clé d'origine est laissée intacte.
- routes /api/conversations (liste, new, switch, rename, delete)
- barre latérale : liste (titre déduit du 1er message, date, nb d'échanges),
bouton +, renommer, supprimer ; lignes construites en DOM et non en
innerHTML, les titres venant de messages utilisateur
- suppression de la dernière discussion : convCreate et non convNew, qui
aurait réenregistré celle qu'on vient d'effacer
Le bouton « Vérifier les mises à jour » répondait « GitHub a répondu 404 » :
il interrogeait les releases du dépôt du fork, qui n'en publie aucune. En
conteneur, remplacer le binaire n'a de toute façon pas de sens — l'UI, l'API
et la CLI renvoient désormais « docker compose pull ».
Vérifié en conteneur : création, bascule, renommage (titre accentué avec
< > &), suppression de l'active puis de la dernière ; /api/update et
loki update renvoient la note Docker.
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki)
- LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ;
units loki-engine / loki-ui ; binaire et aide CLI
- updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les
binaires AJEAN amont)
Conservé à l'identique : le domaine ajean.link (service de tunnel amont),
les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et
LICENSE (historique et licence de l'amont).
go build/vet/test : verts.