sc.Scan() rend false aussi bien a la fin normale d'un flux qu'a la
premiere erreur de lecture, et sc.Err() n'etait JAMAIS consulte. Une
connexion reinitialisee en plein milieu, ou une ligne plus longue que
le tampon, etait donc indiscernable d'une fin propre : le tour
s'arretait sans un mot pendant que le journal de llama-server affichait
un « stop processing » parfaitement normal. C'est le symptome rapporte
— l'agent enchaine quelques commandes puis rend la main sans avoir
termine ni commente.
Pire : des appels d'outils accumules a moitie auraient ete EXECUTES
avec des arguments tronques. On abandonne desormais le tour en nommant
la cause, le texte deja recu reste affiche, et un stop volontaire reste
silencieux. Trois tests couvrent les trois cas ; celui du flux coupe
echoue bien sur l'ancien code.
Tampon du scanner porte de 1 a 8 Mio au passage : la cause la plus
probable d'une ligne trop longue est un appel d'outil demesure, et
jusqu'ici il partait dans le meme silence.
Sans rapport, trouve en faisant tourner la suite : renameAside tirait
son nom du seul UnixNano, dont la granularite Windows peut rendre deux
fois la meme valeur — deux ecartements se disputaient alors le meme
nom, le second ecrasant le binaire mis de cote par le premier. Un
compteur atomique les departage.
Trois choses ne tenaient pas debout dans la fenetre.
Les options CHANGEAIENT selon le format : le JSON proposait un obscur
« journal d'affichage » a la place des trois cases du Markdown.
Personne ne pouvait deviner ce que ca recouvrait, et cocher une case
n'avait pas le meme sens selon le format choisi juste au-dessus. Les
trois memes options valent desormais pour les deux ; le format ne
decide que du contenant. Cote JSON, le journal est filtre par les
memes criteres.
La taille du fichier annoncee sous le curseur ne servait a rien et
justifiait a elle seule une sonde cote serveur. Supprimee, sonde
comprise : le nombre d'echanges vient maintenant de /api/chat/state,
qui est deja l'instantane leger de la conversation.
La fenetre reprend le vocabulaire visuel de l'editeur de preset
(modal-card, pe-group, pe-row, interrupteurs pe-switch) au lieu de son
balisage bricole, et chaque option porte son sous-titre. Conversation
vide : un simple « Rien a exporter » remplace les reglages, plutot que
d'offrir de tailler un fichier qui serait vide de toute facon.
Le menu de portee proposait « 25 derniers echanges » sur un fil qui en
comptait trois, et ne disait nulle part combien il y en avait. Un
curseur le remplace, borne au nombre REEL d'echanges, butee droite =
toute la conversation. Sous la piste : le compte (« 3 derniers echanges
sur 12 ») et le POIDS du fichier pour les options courantes, sonde
cote serveur (probe=1, rendu puis jete, donc jamais un second calcul de
taille qui divergerait).
Le format passe en controle segmente (.seg), celui du choix de moteur
dans l'editeur de preset : deux cases cote a cote au lieu de deux
boutons radio empiles.
Deux details qui faussaient l'affichage : une sonde encore en vol
revenait ecraser le « … » par la taille de l'etat PRECEDENT (le poids
retardait d'un cran sur le curseur), et fmtSize arrondissait 2585
octets en « 3 KB » dans le seul endroit qui pretend annoncer la taille
reelle.
Deux boutons d'export cote a cote ne laissaient aucun choix : tout ou
rien. Un bouton unique ouvre desormais une fenetre ou l'on choisit le
format (Markdown a lire / JSON a retraiter), ce qu'on emporte
(raisonnements, appels d'outils, sorties d'outils, ou journal
d'affichage cote JSON) et la portee (tout le fil ou les N derniers
echanges). Une ligne dit ce que l'export contiendra, et l'en-tete du
fichier signale un export partiel : relu plus tard, il ne doit pas
passer pour le fil complet.
Memes options en ligne de commande : --last N, --no-reasoning,
--no-tools, --no-results, --messages-only. Sans option, l'export reste
complet, endpoint compris.
« clear chat » descend en derniere position et seul sur sa ligne :
c'est la seule action destructive du panneau, elle n'a rien a faire
sous le doigt qui visait « refresh ».
Au passage, issue #20 : le prompt systeme et la description de l'outil
mem_search annoncaient encore la memoire sous MEMORY/, nom d'avant la
0.8. Le modele lisait donc un chemin qui n'existe plus. Corrige, avec
les deux commentaires restes au meme nom.
Declare dans sys_firewall_windows.go, il n'etait qu'ECRIT par les tests
sur les autres plateformes : staticcheck le signalait mort (U1000) et la
CI cassait. Il vit desormais dans sys_network.go, ou setLANExposure le
lit sur les trois plateformes.
Trois retours d'utilisateurs apres la 0.8.3.
Export de la conversation (regression de la 0.8 : conversation.json a
disparu dans ajean.db, verrouille et binaire). Bouton dans l'UI +
commande 'ajean export [--json] [fichier]', endpoint /api/chat/export.
Ecoute reseau du moteur : interrupteur 'joignable depuis le reseau local'
dans le panneau Acces OpenAI (HOST + regle de pare-feu Windows), commande
'ajean network [on|off|status]'. HOST devient un reglage de machine
preserve au changement de preset.
Modeles GGUF decoupes en tranches -00001-of-000NN : une famille = un
modele. Telechargement de toutes les tranches depuis n'importe quel lien,
sonde d'espace disque sur le total, liste et suppression regroupees,
demarrage refuse avec le nom du fichier manquant.
Bug 1 (chat bloque, remonte par Nathan) : trois causes cumulees.
- runShell naissait d'un contexte independant du tour : stop n'arretait rien.
La commande herite desormais du contexte du tour, et la boucle d'outils
s'interrompt des que l'arret est demande.
- cmd.WaitDelay : une commande laissant un process detache gardait les tubes
ouverts, donc Wait ne revenait JAMAIS (ni delai, ni stop).
- Reset debloque toujours l'etat de generation : « nouvelle conversation » etait
le geste tente quand le chat coince, il ne servait a rien. Un tour perime ne
libere plus le tour courant.
Trouve en testant : le workspace disparu cassait TOUTES les commandes suivantes
(chdir: no such file or directory) jusqu'au redemarrage ; il est recree au besoin.
Bug 2 (raisonnement, remonte par Nathan) : l'UI ecrivait « off » en EFFACANT la
ligne REASONING, or sans drapeau le moteur suit le gabarit du modele, qui
raisonne. L'UI ecrit maintenant on|off et « off » part en --reasoning off, apres
avoir demande au binaire s'il connait le drapeau (ik_llama.cpp ne le connait pas
et refuserait de demarrer). Presets existants inchanges ; l'editeur dit desormais
« non precise : le modele decide » quand la cle est absente.
- Le mode agent redevient une vraie fermeture : une surcharge de requete ne peut
plus RALLUMER l'agent (donc bash/write/edit/MCP) quand la machine l'a coupe.
- Une lecture ratee de la cle de pilotage ne se confond plus avec « aucune cle » :
requireWebAuth ferme (503) au lieu d'ouvrir l'API.
- healthCheck : timeout de 3 s. Sans lui, un moteur qui accepte la connexion sans
repondre laissait /api/chat/send bloque indefiniment.
- Acces distant : la session de tunnel ecoute enfin un contexte. Un redemarrage ne
fait plus cohabiter deux sessions ; le backoff se remet a zero apres une session
qui a tenu.
- Flux SSE : selection des evenements neufs par dichotomie au lieu de relire tout
le journal a chaque token et pour chaque appareil.
- Cache de lecture de la configuration, invalide par ecriture locale, par mtime et
par age : fin des ~100 ouvertures de base par tour agentique.
- Erreurs moteur classees par type (errors.Is / net.Error) et non par sous-chaine
anglaise ; les 3 compactions partagent un seul chemin ; champs morts de chatReq
retires ; ReadHeaderTimeout sur les deux serveurs HTTP.
- start/restart : preflight (BIN, MODEL, .gguf) et fin du faux « [ok] activating »
sur un service qui redemarre en boucle (SubState auto-restart).
- MODEL en chemin absolu accepte hors des dossiers declares (lecture seule) ;
l'allowlist reste sur la suppression de fichiers.
- ajean edit : squelette commente des cles au lieu d'un fichier quasi vide.
- install : les prochaines etapes n'oublient plus « ajean llamacpp install ».
- Presets : un nouveau preset ne recopie plus la config active (issue #17),
seulement BIN/HOST/PORT.
- Presets : plus de guillemet interne mange a la relecture (Go et interface),
et EXTRA_ARGS decoupe en respectant les guillemets.
Trois defauts signales a l'usage.
1. Le binaire 0.8.0 se DECLARAIT 0.7.13 a Windows : j'avais bump la constante Go
sans toucher a cmd/ajean/versioninfo.json ni regenerer les .syso. D'ou le
« version en cours 0.7.13 / ce fichier 0.8.0 » a chaque relance du fichier
telecharge. Ressource remise a 0.8.0, .syso regeneres, et un test garde
desormais les deux en phase.
2. Des fenetres de console apparaissaient : la relance apres installation et
l'ouverture du navigateur creaient un enfant sans masquer sa fenetre, ce qui
se voit maintenant que le binaire est en sous-systeme console. Les deux
passent par hideCmd. Les raccourcis DEJA poses recoivent en plus le demarrage
minimise, qui ne s'appliquait qu'aux nouveaux.
3. Deux builds de meme version n'etaient jamais remplaces : relancer un
telechargement plus recent d'une preversion republiee demarrait simplement
l'ancienne copie. On compare desormais les fichiers (taille puis SHA-256) et
non les seuls numeros.
Au passage, l'icone est epinglee sur le raccourci : Windows garde parfois en
cache celle d'un binaire remplace et affiche un carre blanc.
ShowWindow etait cherchee dans kernel32 alors qu'elle vit dans user32.
LazyProc.Call PANIQUE quand la procedure est introuvable, et setupConsole
s'execute avant tout le reste : le programme mourait sur le champ, sans rien
installer et sans rien afficher. Seul le chemin du double-clic etait touche
(console creee pour nous), d'ou une CI verte et des tests CLI qui passaient.
Tous les appels Win32 de ce fichier passent desormais par un helper qui verifie
la presence de la procedure au lieu de paniquer : aucun reglage de confort ne
justifie d'empecher le programme de demarrer.
Verifie de bout en bout cette fois : lancement facon double-clic -> arborescence
creee, bin/ajean.exe installe, aucune panique, processus vivant ; et les chemins
CLI (ordre d'affichage, redirection, tube, aide sans argument) inchanges.
Dans un terminal, cmd rendait la main avant qu'ajean n'ait rien ecrit : l'invite
se reaffichait puis la sortie arrivait par-dessus. Windows n'attend jamais la fin
d'un programme en sous-systeme graphique. La meme cause empechait les
redirections et les tubes, et rendait 'ajean chat' inutilisable (le shell et
ajean se disputaient l'entree).
Le binaire passe donc en sous-systeme console. La fenetre noire au double-clic,
que le sous-systeme graphique evitait, est traitee autrement : quand aucun autre
processus ne partage notre console, c'est que Windows l'a creee pour nous, donc
que personne ne nous a lances depuis un terminal. On la masque et on la libere.
Les raccourcis demandent en plus un demarrage minimise.
La relance interne (apres mise a jour) passe desormais 'app' explicitement : la
detection ne repond qu'a la question du double-clic, et un processus sans
console du tout ne doit pas avoir a y repondre.
Verifie : ordre d'affichage correct en session cmd, redirection et tube
fonctionnels, 'ajean' sans argument affiche l'aide, et aucune fenetre console
visible au lancement facon double-clic.
'ajean where > fichier.txt' n'ecrivait rien dans le fichier et 'ajean help |
findstr x' ne transmettait rien : tout partait sur la console.
Cause : le binaire etant en sous-systeme GUI, setupConsole reouvrait stdout,
stderr et stdin sur CONOUT$/CONIN$ pour avoir ou ecrire. Il ecrasait ce faisant
la redirection que le shell avait posee. Le test naif ne suffit pas :
AttachConsole REMPLACE lui-meme les handles standard par ceux de la console,
donc les interroger apres le rattachement ne montre jamais qu'une console. On
releve donc leur type AVANT d'attacher, et on ne reouvre que ceux qui sont
reellement inutilisables.
Verifie sur un binaire compile comme celui de la release (sous-systeme GUI) :
redirection vers un fichier, tube vers findstr, et affichage console inchange.
Le double-clic demandait « Installer AJEAN ou juste le lancer ? ». La question
n'avait qu'une reponse utile : rester a l'emplacement du fichier telecharge ne
donne pas une installation utilisable (aucun raccourci, rien dans le PATH, et
une application qui disparait le jour ou l'on vide ses telechargements). Faire
porter a l'utilisateur un choix qui n'existe pas ne l'aide pas.
L'installation se fait donc directement, et le message qui suit dit ce qui a ete
fait : ou le programme s'est installe, et que le fichier telecharge peut etre
supprime. Le repli en cas d'echec (droits sur %ProgramData%) est inchange.
Les deux autres boites de dialogue sont conservees : elles portent de vraies
decisions (fichier plus ancien que la version installee, fermer une application
en cours pour appliquer une mise a jour).
Le moteur tourne dans un process detache, qui survit volontairement a la
fermeture de l'interface pour garder le modele charge entre deux ouvertures.
Mais apres un « Quitter », plus rien ne le pilote et il conserve des dizaines de
Go de RAM sans aucune fenetre pour l'expliquer. La fonction de sortie du systray
appelle desormais svcStop, qui tue l'arbre (taskkill /T) donc llama-server avec.
Windows uniquement : c'est la seule plateforme ou l'app est proprietaire du
moteur. Sous Linux et macOS il appartient a systemd ou launchd, et fermer une
interface n'a pas a arreter un service systeme.
Le redemarrage apres mise a jour ne passe PAS par ce chemin (il a son propre
os.Exit), donc une MAJ ne dechargera pas le modele.
Ils prennent simplement le nom du binaire correspondant. Effet de bord corrige
au passage : tous les assets commencent desormais par « ajean- », donc le motif
« ajean-* *.zip » de SHA256SUMS aurait liste les zips DEUX fois, et un fichier
d'empreintes a doublons est precisement ce que verifie 'ajean update'.
Elle n'est utilisee que par le tray macOS, que staticcheck ne compile pas hors
CGO. Sans cette directive, la CI (qui lance staticcheck sur ubuntu) echouerait
sur une fonction pourtant bien utilisee.
Deux defauts trouves a l'usage :
1. Impossible de saisir la cle de pilotage dans l'UI : le chargement lance une
dizaine d'appels /api/* en parallele, chaque 401 rouvrait la modale, et
celle-ci vide son champ a l'ouverture. Les caracteres disparaissaient au fur
et a mesure de la frappe. Une seule demande de cle est desormais partagee par
tous les appels concurrents. Au passage, une modale ouverte n'est plus
ecrasee en silence (sa promesse restait pendante pour toujours).
2. migrate_07 n'importait pas .authorized_users : les identites appairees pour
le chat chiffre. Symptome trompeur — le tunnel s'etablit, la machine
s'affiche en ligne, mais le portail refuse toute conversation. Importe et
couvert par un test.
brandTemplatePNG n'etait pas du code mort : sys_tray_darwin.go l'utilise pour
l'icone de barre de menus. Ce fichier ne compile qu'avec CGO, donc ni la
compilation croisee depuis Windows ni staticcheck sans CGO ne le voyaient — la
fonction passait pour inutilisee. Restauree.
sys_service_darwin.go affichait encore le chemin de config.env, disparu avec le
passage en base.
Le piege est documente dans doc.go : seul le job macos de la CI attrape ce genre
de regression.
- notes reecrites sans tiret cadratin (convention du projet), avec la marche a
suivre d'installation et la section de ce qui n'a pas ete teste
- migrate_07 archive aussi SKILLS/ (dossier mort depuis la fusion dans memory)
- commentaire d'en-tete de release.yml remis a jour
ajean-linux, ajean-linux-arm, ajean-macos, ajean-macos-arm,
ajean-windows.exe, ajean-windows-arm.exe.
Le schema ne correspond plus a ajean-<GOOS>-<GOARCH> que cherchent les 0.7 :
leur mise a jour automatique ne trouve aucun asset et echoue sans rien
remplacer. C'est voulu — sinon elle installait le binaire 0.8 sans migrer, le
service de lien repartait en boucle d'echec sur « link serve » disparu et le
moteur perdait sa configuration.
assetNameFor est isolee pour etre testee sur les six plateformes, plus un test
qui verifie explicitement l'absence de collision avec le schema 0.7.
Un seul fichier, migrate_07.go, sans dependance entrante : le supprimer et
retirer les deux appels suffira le jour venu. Declenchee UNIQUEMENT par install
(root, delibere) et non au demarrage, contrairement a ce qui existait en 0.7 et
qui pouvait laisser une machine distante sans service.
Deplace configs/->presets/, MEMORY/->memory/, les .gguf->models/, reprend l'etat
en base, range les anciens fichiers dans avant-0.8/ et desactive les unites jean*
(sans quoi un second llama-server demarrait au boot suivant).
Trois tests : le cas nominal, l'installation neuve, et la machine deja migree a
la main. Ils ont trouve deux bugs reels de casse de fichiers (MEMORY/memory se
confondent sur Windows et macOS).
- uiUserSvcCtl lancait encore « ajean link serve », sous-commande supprimee :
l'acces distant etait casse sur macOS et Windows (hors systemd)
- tools/migrate-0.7 : reprise d'une installation 0.7.x, hors du binaire pour ne
pas reintroduire de compat dans le produit ; procedure complete au README
- CI : staticcheck ajoute (le projet est a zero avertissement, il doit le rester)
- .gitignore purge de ses entrees mortes (jean, config.env, .api_key...)
- sys_install_unix.go porte le parcours commun (arborescence, lien du binaire,
/etc/default, chown) ; Linux et macOS ne gardent que leur declaration de
services derriere installServices/uninstallServices (395 -> 339 lignes, et
surtout une seule copie a corriger)
- l'installateur macOS pose desormais les DEUX services (il n'en posait qu'un)
- API bbolt depreciee remplacee, message d'erreur duplique factorise
- staticcheck.conf : ST1005 ecartee, avec la raison (messages francais montres
a l'utilisateur). Le projet est a zero avertissement.
- sysprompt, utilisateurs appairés, codes d'appairage et etat du job de build
passent en base ; ne restent des fichiers que la cle E2E, les certificats et
les journaux (le log de build reste un fichier : des milliers de lignes)
- doc.go remis a jour (run.go, store.go, les deux services, tools/assemble-ui)
- README et notes de release ne pretendent plus que la racine ne contient que
six dossiers : c'etait faux
Le champ 'config' de /api/paths est devenu 'database' : la ligne Configuration
s'affichait vide. Le panneau montre desormais la base, les modeles, les presets,
la memoire, le workspace et les backends.
- ajean.service -> ajean-engine, ajean-link.service -> ajean-ui
- 'ajean web' est desormais l'unique serveur d'interface : il ouvre aussi le
tunnel quand un jeton est enregistre (une seule conversation, plus de conflit
sur :8090 ni de fils divergents)
- 'ajean link' ne gere plus que le compte ; 'ajean ui' pilote le service
- l'installateur Linux pose les deux unites et les deux regles sudoers
- 'app' sort de l'aide
bbolt pose un verrou exclusif sur son fichier : le service de lien, qui tourne
en permanence, empechait toute commande CLI de s'executer a cote. La base est
desormais ouverte pour la duree d'une operation puis refermee, et les buckets
ne sont crees qu'a l'ecriture (une lecture ne coute plus un fsync : 3,8 ms ->
0,55 ms). Test de concurrence a l'appui.
- suppression des fonctions et types inutilises (staticcheck U1000)
- sources UI, workflows, README passes en ajean
- release.yml ne publie plus que les binaires ajean-*
- notes de release redigees
- suppression des alias de commandes herites (skills, machine, tools, mem, upgrade...)
- aide reecrite et regroupee
- helper de test testHome + fermeture de la base (verrou bbolt sous Windows)
- tests des dossiers de modeles alignes sur models/
- provisionDataDir unique pour les trois plateformes (six dossiers, config par defaut)
- plus de modele config.env duplique dans chaque installateur
- plus d'alias jean pose a l'installation
- suppression des migrations d'agencement, d'unites et de dossier de donnees
- store bbolt unique (ajean.db) : config, prefs, conversation, cles, drapeaux
- arborescence backends/ bin/ presets/ memory/ models/ workspace/
- plus aucune variable, unite ou asset nomme jean
- Preset actif : liseré gauche droit, rogné par l'arrondi de la carte, un peu plus large
- Puce de l'actif dessinée en CSS (même taille et même assiette sur Windows et macOS)
- Survol et sélection moins appuyés dans la liste
- Bascule : retour visuel immédiat (barre qui glisse depuis la gauche, atténuée puis
pleine) ; animations rejouées seulement quand la sélection change vraiment
- /api/switch répond sans attendre le redémarrage du service : bascule instantanée
- Bouton envoyer et touche Entrée désactivés tant que le modèle n'est pas chargé
- ecartement du binaire sous un nom unique : un reste verrouille par un ancien
processus ne fait plus echouer le remplacement (message "droits insuffisants,
relance en administrateur" qui etait faux, aucun privilege ne remplace une
image en cours d'execution)
- section Cartes graphiques masquee pour de vrai avec une seule carte :
l'attribut hidden etait sans effet, .pe-group{display:flex} l'emportait
Le remplacement du binaire renommait l'ancien en un nom FIXE (.old). Si un
processus tournait encore depuis ce .old (mise a jour precedente), le fichier
n'etait ni supprimable ni ecrasable : le renommage sortait en "Acces refuse"
(errno 5), que os.IsPermission rapporte comme un defaut de DROITS. L'UI
affichait donc "droits insuffisants, relance en administrateur", conseil faux :
aucun privilege ne remplace l'image d'un executable en cours d'execution.
- renameAside() ecarte sous un nom UNIQUE (.old-<horodatage>) : le renommage
passe meme si un ancien ecartement est verrouille (verifie sur banc d'essai
avec deux processus vivants)
- les trois endroits qui faisaient la manoeuvre l'utilisent (update, install,
premier lancement), et le menage ramasse le nom fixe herite + les uniques
- message d'erreur : Windows renvoie le meme code pour "droits insuffisants" et
"fichier utilise", les deux causes sont donc nommees
Aussi : la section Cartes graphiques restait affichee avec une seule carte.
L'attribut hidden etait sans effet, .pe-group{display:flex} l'emportant sur la
regle par defaut du navigateur.