Releases: nathaninline/ajean
Release list
v0.8.6
Une seule correction, mais elle touche l'accès distant.
Changer la clé de pilotage ne coupe plus ajean.link
Si vous changiez la clé de pilotage, depuis l'interface ou avec ajean set-web-key, l'accès distant tombait aussitôt et ne revenait qu'au redémarrage du service.
La cause : le tunnel lisait la clé une seule fois, à son ouverture, puis l'injectait dans chaque requête venue du relais. Après un changement, il continuait donc à présenter l'ancienne, et tout passait en refus d'authentification.
Le symptôme, lui, ne disait rien du problème. Le chat n'affiche pas le code d'erreur d'un flux qui n'arrive jamais : on voyait « chargement de la conversation » tourner à l'infini, sans un mot d'explication, alors que le serveur allait très bien.
La clé est maintenant relue à chaque requête. Si l'accès distant vous a lâché après un changement de clé, cette version suffit à le rétablir.
Mise à jour
ajean updatePuis redémarrez le service d'interface pour rouvrir le tunnel :
ajean ui restartSi vous utilisez le portail, rechargez la page ensuite.
v0.8.5
Une correction qui compte, et la fenêtre d'export refaite d'après vos retours.
Le chat qui s'arrête sans un mot
Symptôme rapporté : l'agent réfléchit, lance quelques commandes, puis s'arrête au milieu du travail et rend la main sans avoir terminé ni commenté. Aucune erreur nulle part, et dans le journal du moteur un « stop processing » parfaitement normal.
La cause est dans AJEAN, pas dans le moteur. La lecture du flux de réponse s'arrête aussi bien à la fin normale qu'à la première erreur, et AJEAN ne regardait jamais laquelle des deux venait de se produire. Une connexion coupée en plein milieu était donc traitée comme une réponse terminée : le tour se refermait en silence.
Plus ennuyeux, un cas que personne n'avait encore vu passer : quand la coupure tombait pendant que le modèle demandait un outil, l'appel à moitié reçu était exécuté quand même, avec des arguments tronqués.
Désormais, un flux coupé est annoncé, avec la cause exacte dans le message, et le tour est abandonné plutôt que de lancer un appel incomplet. Le texte déjà reçu reste affiché. Un arrêt que vous demandez vous même reste silencieux, comme avant.
À noter : cette correction rend la coupure VISIBLE, elle ne l'empêche pas. Si le message apparaît chez vous, l'erreur qu'il nomme (« unexpected EOF », « connection reset », « ligne trop longue »…) est exactement ce qu'il nous faut pour trouver la suite. Signalez la.
La taille maximale d'une ligne du flux passe de 1 à 8 Mio au passage : la seule chose qui puisse la dépasser est un appel d'outil démesuré, et jusqu'ici il partait dans le même silence.
La fenêtre d'export
Elle est arrivée en 0.8.4 sous forme de deux boutons qui ne laissaient aucun choix. C'est maintenant une vraie fenêtre.
Le format, Markdown à lire ou JSON à retraiter, ne décide plus que du contenant : les trois mêmes options de contenu valent pour les deux, raisonnements, appels d'outils, sorties des outils. La version précédente proposait des options différentes selon le format, dont un obscur « journal d'affichage » côté JSON que personne ne pouvait comprendre.
La portée se règle à présent avec un curseur qui va de 1 au nombre réel d'échanges de votre conversation, butée à droite pour tout prendre, au lieu de valeurs prédéfinies qui proposaient « 25 derniers échanges » sur un fil qui en comptait trois.
Conversation vide, la fenêtre le dit simplement au lieu d'offrir des réglages pour un fichier qui serait vide.
Enfin, « clear chat » descend en dernière position du panneau Actions et se retrouve seul sur sa ligne : c'est la seule action destructive de ce panneau, elle n'a rien à faire sous le doigt qui visait « refresh ».
Corrections plus discrètes
Le prompt système et la description de l'outil de recherche en mémoire annonçaient encore au modèle une mémoire rangée dans MEMORY/, nom d'avant la 0.8. Il pouvait donc aller chercher un dossier qui n'existe plus. Merci à qui l'a remarqué.
Le mécanisme qui met de côté l'ancien binaire pendant une mise à jour tirait son nom de l'horloge seule. Sous Windows, deux appels rapprochés pouvaient obtenir la même valeur, et le second écrasait le binaire mis de côté par le premier.
Mise à jour
ajean updatePuis, si vous utilisez l'accès distant, rechargez la page du portail pour prendre la nouvelle interface.
Ce qui n'a pas été vérifié sur machine réelle : le téléchargement d'un modèle découpé en plusieurs fichiers de bout en bout (arrivé en 0.8.4, couvert par des tests mais pas par un vrai transfert), et le comportement sur macOS, toujours pas testé sur un Mac.
v0.8.4
Trois retours d'utilisateurs après la 0.8.3, trois vrais trous dans le logiciel. Ils sont bouchés.
Récupérer sa conversation
Jusqu'à la 0.7, l'historique vivait dans un fichier conversation.json qu'on pouvait ouvrir, relire et copier pour archive. La 0.8 l'a rangé dans ajean.db, une base binaire, verrouillée tant que le service tourne : impossible ne serait ce que de la copier. Le fil et les raisonnements étaient toujours là, mais plus personne ne pouvait les sortir. C'était une régression, et elle n'aurait pas dû passer.
Deux façons de récupérer le fil, désormais.
Depuis l'interface, dans le panneau « Actions » : un bouton exporte la conversation en Markdown (les raisonnements sont repliés sous un « Raisonnement », les appels d'outils apparaissent avec leur résultat), un autre en JSON fidèle si vous voulez retraiter les données.
Depuis le terminal :
ajean export # ajean-conversation-<date>.md
ajean export --json # même chose en JSON
ajean export mon-fil.md # nom imposé, l'extension choisit le format
ajean export - # sur la sortie standard, pour enchaîner un tubeLa commande fonctionne pendant que le service tourne.
Le moteur joignable depuis le réseau
Symptôme rapporté : « à part le chat dans le navigateur, impossible d'utiliser ton URL dans les logiciels en local ». L'adresse http://<machine>:8080/v1 s'affichait bien dans l'interface, mais rien ne répondait depuis un autre ordinateur.
Deux causes, cumulées, et aucune des deux n'était visible.
Sous Windows, l'installation posait HOST=127.0.0.1 : le moteur n'écoutait que sur la machine elle même. Le chat du navigateur marchait, puisqu'il passe par le serveur web d'AJEAN, sur place. Tout le reste était invisible. Et ce réglage n'était modifiable nulle part dans l'interface, il fallait connaître ajean edit et savoir quoi y écrire.
Deuxième cause : même ouvert sur toutes les interfaces, le pare feu Windows bloque les connexions entrantes tant qu'aucune règle n'autorise le port. AJEAN n'en posait aucune.
Il y a maintenant un interrupteur « joignable depuis le réseau local », dans le panneau « Accès OpenAI », juste sous l'adresse qu'il conditionne. Il règle l'adresse d'écoute et pose la règle de pare feu dans le même geste. Poser une règle exige les droits administrateur, que l'installation d'AJEAN ne réclame pas : quand ça échoue, l'interface le dit et donne la commande exacte à coller dans un terminal administrateur, au lieu de laisser croire que c'est fait.
En ligne de commande : ajean network, ajean network on, ajean network off. Le moteur doit redémarrer pour appliquer, l'interface le propose.
L'adresse d'écoute est aussi devenue un réglage de machine et non de modèle : basculer sur un preset écrit avant cette version ne la remet plus à zéro. Une machine volontairement fermée reste fermée.
Les modèles en plusieurs fichiers
Au delà d'une certaine taille, un dépôt Hugging Face publie son GGUF en tranches : ...-00001-of-00003.gguf, et ainsi de suite. llama.cpp n'a besoin que de la première, il ouvre les suivantes tout seul. AJEAN, lui, traitait chaque tranche comme un modèle indépendant, avec trois conséquences.
Le lien collé ne rapatriait qu'un fichier sur trois, et le moteur mourait ensuite sur un tenseur introuvable. Le sélecteur affichait trois entrées pour un seul modèle, dont deux qui ne démarrent pas. Et supprimer « le modèle » n'effaçait que sa première tranche, laissant des dizaines de Go que plus rien ne référençait et que l'interface ne savait plus montrer.
Une famille de tranches est maintenant un seul modèle. Coller le lien de n'importe laquelle télécharge la famille entière, avec une barre de progression unique et un compteur de fichiers ; la vérification d'espace disque porte sur le total, plus sur une tranche. La liste n'affiche que la première, avec la taille de l'ensemble et une mention « 3 fichiers ». S'il en manque une, elle est signalée dans la liste, et le démarrage du moteur s'arrête sur un message qui nomme le fichier absent au lieu de laisser le service boucler. La suppression emporte toutes les tranches.
Un téléchargement interrompu à la deuxième tranche peut être relancé : les fichiers déjà complets sont conservés.
Mise à jour
ajean updatePuis, si vous utilisez l'accès distant, rechargez la page du portail pour prendre la nouvelle interface.
Ce qui n'a pas été vérifié sur machine réelle : le téléchargement d'un vrai modèle découpé de bout en bout (la logique est couverte par des tests, mais pas un transfert complet depuis Hugging Face), et le comportement sur macOS, toujours pas testé sur un Mac.
v0.8.3
Deux bugs signalés par l'usage, tous les deux confirmés, tous les deux réparés. Le premier pouvait bloquer le chat jusqu'au redémarrage du service. Le second faisait mentir un interrupteur.
Le bouton stop arrête vraiment, et le chat ne se bloque plus
Le scénario, tel qu'il était vécu : le modèle lance une commande, elle dure, on clique sur stop, rien ne se passe. On redémarre alors le moteur, et là le chat reste figé avec son bouton stop, sans plus rien accepter. Vider la conversation ne suffit pas, rafraîchir la page non plus ; il faut redémarrer le service d'interface.
Trois défauts se cumulaient, et ils sont corrigés ensemble.
La commande ignorait l'arrêt. Elle démarrait avec un contexte à elle, indépendant du tour. Annuler la génération n'annulait donc rien du tout : le tour restait suspendu jusqu'au bout du délai, cinq minutes au maximum. La commande hérite désormais du contexte du tour, et stop la tue pour de bon. Dans la foulée, un arrêt demandé au milieu d'une série d'appels d'outils interrompt la série au lieu de la dérouler jusqu'au bout.
Une commande qui laisse un processus en arrière-plan bloquait le tour pour toujours. Quelque chose comme ./serveur & rend la main tout de suite, mais le processus détaché garde les tubes de sortie ouverts, et l'attente de fin de commande attendait leur fermeture, c'est à dire jamais. Ni le délai ni le stop n'en venaient à bout. L'attente est maintenant bornée après la fin ou la mise à mort du processus.
Vider la conversation ne débloquait pas. C'est pourtant le geste qu'on tente quand le chat est coincé. « Nouvelle conversation » libère désormais toujours l'état de génération, quel que soit le sort du tour abandonné. Plus besoin de redémarrer quoi que ce soit.
Au passage, un tour abandonné qui se termine après le démarrage du suivant ne vient plus déclarer ce dernier terminé.
Un troisième défaut, trouvé en écrivant les tests
Le dossier de travail de l'agent est résolu une fois pour toutes au démarrage. S'il disparaissait ensuite, parce que vous avez fait le ménage ou parce que le modèle l'a supprimé lui-même, toutes les commandes suivantes échouaient sur un « chdir : no such file or directory » incompréhensible, et ce jusqu'au redémarrage. Il est maintenant recréé au besoin.
Raisonnement désactivé veut enfin dire désactivé
Couper le raisonnement dans l'éditeur de preset effaçait la ligne REASONING au lieu d'écrire off. Ce n'est pas la même chose : sans consigne, le moteur suit le gabarit du modèle, et un modèle à raisonnement raisonne. L'interrupteur affichait donc « désactivé » pendant que le modèle réfléchissait tranquillement.
L'interface écrit maintenant on ou off, explicitement, et off est transmis au moteur comme une interdiction.
Avec une précaution : le drapeau qui désactive le raisonnement est récent, et certains moteurs, notamment le fork ik_llama.cpp, ne le connaissent pas. Le leur passer les ferait refuser de démarrer, donc boucler. AJEAN demande au binaire ce qu'il sait faire avant de le lui passer, et le dit dans le journal quand le moteur choisi ne permet pas de couper la réflexion.
Vos presets existants ne sont pas modifiés. Ceux dont la ligne est absente gardent le comportement d'avant, et l'éditeur ne prétend plus que le raisonnement y est coupé : il indique que rien n'est précisé et que le modèle décide. Basculez l'interrupteur une fois pour trancher.
Mise à jour
ajean updatePuis, si vous utilisez l'accès distant, rechargez la page du portail pour prendre la nouvelle interface.
v0.8.2
Une version d'audit. Aucune fonctionnalité nouvelle : une relecture du cœur du code, et la correction de ce qu'elle a mis au jour. Deux de ces défauts pouvaient vous mordre pour de bon.
Le mode agent redevient un vrai interrupteur
L'API de chat acceptait, dans le corps de la requête, une surcharge qui rallumait le mode agent. Autrement dit : agent éteint sur la machine, mais un client qui envoyait le bon drapeau récupérait quand même le shell, l'écriture de fichiers et les outils MCP. Comme l'API de pilotage n'est pas protégée par défaut et écoute sur toutes les interfaces, l'interrupteur ne garantissait rien sur un réseau local partagé.
Désormais une surcharge ne peut que restreindre. Ce qui est éteint sur la machine ne peut plus être rallumé par une requête, et couper l'agent coupe aussi, du même geste, l'accès web qui en dépend. Deux tests verrouillent la règle dans les deux sens.
Le champ hérité qui portait cette surcharge venait du temps où l'ancien portail gérait ses propres interrupteurs. Il reste accepté, mais borné.
Une base illisible ne désarme plus l'authentification
Quand aucune clé de pilotage n'est enregistrée, l'API est ouverte : c'est le confort du local. Mais la lecture de cette clé traitait « je n'ai pas pu lire la base » exactement comme « il n'y a pas de clé ». Une base momentanément verrouillée par une commande lancée à côté suffisait donc, en théorie, à ouvrir l'API le temps de la contention.
La lecture des secrets distingue maintenant les deux cas, et l'authentification ferme en cas de doute (503) au lieu d'ouvrir.
Le bouton d'envoi ne peut plus rester bloqué
Avant chaque tour, AJEAN vérifie que le moteur répond. Cet appel n'avait aucun délai maximum. Un moteur qui accepte la connexion sans jamais répondre, ce qui est exactement ce que fait un très gros modèle pendant son chargement, laissait donc l'envoi du message suspendu indéfiniment, sans erreur et sans retour. Il abandonne maintenant au bout de trois secondes et vous dit que le modèle n'est pas prêt.
L'accès distant ne peut plus se dédoubler
Redémarrer le lien depuis l'interface arrêtait la boucle de connexion, mais la session en cours, elle, continuait de vivre : elle n'écoutait aucun signal d'arrêt et attendait la mort naturelle du WebSocket, qui n'arrive pas tant que le relais répond. Le nouveau tunnel s'ouvrait donc pendant que l'ancien tenait encore, et le relais voyait deux agents pour une seule machine.
L'arrêt est maintenant immédiat et attendu : une session se ferme quand on le lui demande, et la suivante ne démarre qu'après.
Dans la foulée, le délai entre deux tentatives de reconnexion se remet à zéro après une session qui a tenu. Il grimpait sans jamais redescendre et finissait collé à trente secondes, y compris pour rattraper un lien qui venait de fonctionner des heures.
Le flux de chat ne relit plus tout le fil à chaque mot
Pour envoyer les nouveaux événements à votre navigateur, le serveur reparcourait la totalité du journal de conversation, à chaque token généré, et pour chaque appareil connecté, en tenant le verrou que la génération elle-même attend. Sur une longue conversation, c'est un coût qui grandit avec l'historique et qui ralentit ce qu'il est censé diffuser.
Le journal étant trié, la recherche du premier événement neuf se fait par dichotomie. Le coût ne dépend plus de la longueur de la conversation.
La configuration n'est plus relue depuis le disque cent fois par tour
La base de données est délibérément fermée entre deux opérations : c'est ce qui permet aux commandes du terminal de fonctionner pendant que le service tourne. Mais la boucle d'inférence relisait le port, la clé et le seuil de compactage à chaque itération, et le compactage se re-testait après chaque appel d'outil. Un tour agentique un peu fourni rouvrait le fichier une centaine de fois.
Un cache de lecture s'interpose, invalidé par toute écriture locale, par une écriture venue d'un autre processus (date et taille du fichier) et, en dernier filet, par l'âge. Le comportement ne change pas d'un iota, le travail inutile disparaît.
Le reste
Les erreurs du moteur sont classées sur leur type plutôt que sur le texte du message : « connexion refusée » d'un Windows en français ne ressemblait à aucun des motifs anglais reconnus, et l'utilisateur recevait alors l'erreur brute au lieu de l'explication.
Les trois compactions (début de tour, fin de tour, bouton manuel) déroulaient la même douzaine de lignes recopiées ; elles partagent maintenant un seul chemin, ce qui ferme la porte aux corrections qui n'atterrissaient que dans une des trois copies. La ligne de journal de la compaction de secours comparait le résultat à lui-même, elle compare enfin l'avant et l'après. Deux champs de l'API de chat que plus personne ne lisait depuis que la conversation vit côté serveur ont été retirés. Les serveurs HTTP posent un délai de lecture des en-têtes, pour qu'une connexion qui n'envoie jamais rien ne retienne pas de ressources.
Mise à jour
ajean updateRien à migrer : presets, configuration et mémoire sont inchangés.
v0.8.1
Une version de corrections, née de vos retours sur la 0.8.0 : un moteur qui refusait de démarrer sans jamais le dire, et des presets qui se mélangeaient.
« ajean start » ne ment plus
Le cas le plus pénible remonté cette semaine : ajean start répondait [ok] ajean-engine: activating, puis ajean test répondait « /health ne répond pas ». Rien dans les deux messages ne permettait de comprendre.
L'explication tenait à une nuance de systemd. Un service qui meurt au lancement est relancé toutes les trois secondes, et pendant ces trois secondes il est en activating, exactement comme un moteur en train de charger un modèle. AJEAN prenait donc un échec en boucle pour un démarrage en cours. Il distingue désormais les deux : un vrai chargement reste annoncé comme tel (un gros .gguf prend des minutes, c'est normal), une boucle d'échec affiche l'erreur, les vingt dernières lignes du journal et quoi corriger.
Mieux : ajean start et ajean restart vérifient d'abord ce sans quoi le moteur ne peut pas démarrer, et refusent de lancer un service condamné. Moteur absent, modèle non renseigné, fichier .gguf introuvable : c'est dit tout de suite, avec la commande qui répare.
Un modèle peut vivre où vous voulez
MODEL=/home/moi/modeles/mon-modele.gguf était refusé si le dossier n'avait pas été déclaré au préalable dans l'interface web. Le moteur mourait alors en boucle sur un « dossier non autorisé » que personne ne voyait passer, et le seul remède connu consistait à ouvrir l'interface pour y ajouter le dossier. Un utilisateur l'a trouvé tout seul, à tâtons ; ce n'était pas une découverte à lui imposer.
Ce garde-fou existe pour empêcher l'interface web de supprimer un fichier n'importe où sur la machine. Il n'avait aucune raison de s'appliquer à « ouvrir ce modèle en lecture ». Un chemin absolu vers un .gguf qui existe est maintenant accepté tel quel, quel que soit le disque. La protection reste entière là où elle sert.
La configuration s'explique enfin
Depuis que la configuration vit en base, ajean edit déroulait dans votre éditeur les seules clés déjà définies. Sur une installation neuve, cela donnait un fichier quasiment vide : impossible de deviner quoi écrire.
L'éditeur propose désormais un squelette commenté. Chaque réglage utile y figure avec son rôle et son défaut (BIN, MODEL, CTX, NGL, batch, threads, cache KV, raisonnement, mémoire, EXTRA_ARGS). Les valeurs que vous avez déjà réglées sont en clair, les autres restent commentées, donc inactives, et toute clé que le squelette ne connaît pas est conservée en fin de fichier.
Les étapes affichées à la fin de ajean install ont été corrigées dans la foulée : elles oubliaient ajean llamacpp install, sans quoi la clé BIN reste vide et le moteur ne peut pas démarrer.
Presets : un nouveau preset repart propre (issue #17)
Créer un preset avec le bouton « + » recopiait la configuration active en entier. Les réglages du modèle précédent (les experts déportés sur le CPU, la répartition entre cartes, le contexte, jusqu'au modèle lui-même) se mélangeaient donc aux options cochées pour le nouveau, et il fallait penser à tout nettoyer à la main.
Un nouveau preset ne reprend plus que ce qui relève de la machine : le moteur, l'adresse et le port. Tout le reste part des valeurs par défaut, que vous ajustez pour ce modèle-là.
Presets : les guillemets ne sont plus mangés
Le rapport parlait de contenu tronqué et de guillemets non appariés, et c'était exact, avec une cause plus profonde qu'il n'y paraissait. En relisant une ligne de configuration, AJEAN retirait tout guillemet en début et en fin de valeur, sans vérifier qu'il s'agissait d'une paire. Une valeur comme :
EXTRA_ARGS=--jinja --chat-template-file "/etc/ajean/gabarit.jinja"
ressortait donc amputée de son guillemet final, déséquilibrée, et repartait ainsi dans le preset enregistré. Le défaut existait des deux côtés, dans le moteur comme dans l'interface. Seule une paire entourante est maintenant retirée, et l'écriture est faite pour être relue à l'identique. Un test verrouille l'aller-retour.
Dans la même zone : EXTRA_ARGS était découpé sur les espaces sans tenir compte des guillemets, si bien qu'un chemin contenant une espace devenait deux arguments et que llama-server refusait de démarrer. Le découpage respecte désormais les guillemets, comme le ferait un shell.
Mise à jour
ajean updateOu, depuis l'interface, le bandeau de mise à jour. Rien à migrer : les presets, la configuration et la mémoire sont inchangés.
v0.8.0
Le renommage est terminé. Plus rien ne s'appelle jean : ni le binaire, ni les services, ni les variables, ni les dossiers. Et le dossier de données, qui s'était couvert d'une douzaine de petits fichiers d'état, tient désormais dans six dossiers et une base.
Mise à jour : il faut réinstaller
Ne passez pas par le bouton « mettre à jour » de la 0.7. Il ne trouvera d'ailleurs rien : les binaires de cette version portent de nouveaux noms, que la 0.7 ne sait pas chercher. C'est délibéré. La laisser installer ce binaire aurait remplacé l'exécutable sans migrer ni les données ni les unités : le service de lien serait reparti en boucle d'échec sur une sous-commande disparue, et le moteur n'aurait plus trouvé sa configuration. Une machine à réparer en SSH après un clic dans un navigateur.
La marche à suivre, sur une machine déjà installée :
curl -L -o ajean https://github.com/nathaninline/ajean/releases/latest/download/ajean-linux
chmod +x ajean && sudo mv ajean /usr/local/bin/ajean
sudo ajean installinstall fait la reprise complète : il arrête et désactive les anciens services, déplace configs/ vers presets/, MEMORY/ vers memory/ et les .gguf vers models/, reprend en base la configuration, les préférences, la conversation, les clés, le jeton de liaison, les interrupteurs, les serveurs MCP et les benchmarks, puis installe les deux nouvelles unités.
Rien n'est supprimé. Les presets, la mémoire et les modèles sont déplacés, jamais copiés ni effacés. Les anciens fichiers d'état sont rangés dans avant-0.8/, que vous pourrez supprimer quand tout ira bien. La clé du chiffrement de bout en bout n'est pas touchée, donc l'empreinte confirmée dans le portail reste valable.
Une base à la place des fichiers d'état
config.env, webprefs.json, conversation.json, model_dirs.json, mcp.json, .api_key, .web_key, .link_token, .agent_enabled, .internet_enabled et le reste ont disparu. Tout cela vit maintenant dans ajean.db, un fichier unique en bbolt, pur Go, transactionnel, sans dépendance système.
Ce n'est pas qu'un rangement. Chaque fichier avait sa façon d'être écrit, et donc sa façon de rater une écriture concurrente : l'interface et un tour de chat qui touchaient au même réglage au même instant pouvaient en perdre un. Une transaction remplace tout ça. Une bascule de preset, en particulier, remplace la configuration d'un bloc : il n'existe plus d'instant où elle serait à moitié l'ancienne et à moitié la nouvelle.
Restent des fichiers ceux qui sont faits pour être lus, édités et sauvegardés à la main : les presets, les pages de mémoire, les modèles. ajean edit déroule donc la configuration au format clé=valeur dans votre éditeur, puis la relit ; le fichier n'existe que le temps de l'édition.
Six dossiers
$AJEAN_HOME contient backends/, bin/, presets/, memory/, models/, workspace/, plus la base. configs/ devient presets/, MEMORY/ devient memory/, et les .gguf ont enfin leur models/ au lieu d'être posés à la racine. Ne restent à côté que ce qui ne peut pas aller ailleurs : la clé privée du chiffrement de bout en bout, le dossier des certificats TLS, les journaux et les fichiers PID des services.
Deux services qui portent enfin leur nom
ajean.service et ajean-link.service deviennent ajean-engine et ajean-ui. Le premier exécute le modèle, le second sert l'interface web, le tunnel d'accès distant et l'endpoint OpenAI. Le nom « link » cachait l'essentiel : ce service est d'abord le serveur web.
Surtout, il n'y a plus qu'une seule façon de servir l'interface. Avant, ajean web et ajean link serve savaient tous deux le faire, ce qui posait un piège permanent : lancer les deux, c'était un conflit sur le port 8090 et surtout deux fils de conversation qui divergeaient (la conversation vit en mémoire, deux process qui la servent finissent par s'écraser l'un l'autre). La consigne « ne jamais lancer ajean web » circulait comme une règle à retenir ; elle n'existait que parce que le code offrait deux portes pour la même pièce.
Désormais ajean web est cette porte unique : il sert l'interface et, si un jeton de liaison est enregistré, ouvre le tunnel dans le même process. ajean link ne s'occupe plus que du compte (jeton, code d'appairage, état), et ajean ui start|stop|restart|status pilote le service. ajean link serve, link start, link stop et link restart disparaissent.
Séparer les deux services garde son intérêt : redémarrer l'interface est instantané, alors que redémarrer le moteur recharge des dizaines de gigaoctets.
Sur Windows
« Quitter » arrête vraiment tout. Le moteur tourne dans un processus détaché, qui survit volontairement à la fermeture de l'interface pour garder le modèle chargé entre deux ouvertures. Mais après un « Quitter » depuis la zone de notification, plus rien ne le pilotait et il conservait des dizaines de gigaoctets de mémoire, sans la moindre fenêtre pour l'expliquer. Quitter décharge désormais le modèle. Ailleurs, rien ne change : sous Linux et macOS le moteur appartient à systemd ou launchd, et fermer une interface n'a pas à arrêter un service système.
AJEAN se comporte enfin comme une vraie commande. Dans un terminal, ajean rendait la main avant d'avoir écrit quoi que ce soit : l'invite se réaffichait, et la sortie du programme arrivait par-dessus, comme si la commande avait été tapée deux fois.
La même cause empêchait ajean where > fichier.txt d'écrire dans le fichier, ajean help | findstr ... de transmettre quoi que ce soit, et rendait ajean chat inutilisable depuis un terminal, le shell et AJEAN se disputant le clavier. Le binaire était compilé en sous-système graphique, et Windows n'attend jamais la fin d'un programme de ce type.
Il est désormais en sous-système console. La contrepartie, une fenêtre noire au double-clic, est traitée par le programme lui-même : constatant qu'aucun terminal ne l'a lancé, il referme aussitôt la console que Windows lui a allouée. Les raccourcis posés à l'installation demandent en plus un démarrage minimisé, si bien qu'elle n'est jamais peinte à l'écran.
Plus de question au premier lancement. Le double-clic demandait s'il fallait installer AJEAN ou seulement le lancer. La question n'avait qu'une réponse utile : rester à l'emplacement du fichier téléchargé ne donne pas une installation exploitable, sans raccourci, sans rien dans le PATH, et avec une application qui disparaît le jour où l'on vide ses téléchargements. L'installation se fait donc directement, et le message qui suit dit ce qui a été fait au lieu de demander une permission.
Ce qui a été retiré
Tout le code écrit pour ménager les installations « jean » : la migration du dossier de données et ses reprises après échec, la migration de l'agencement système (unités, /etc/default, réécriture des chemins), l'élévation Windows qu'elle demandait, la résolution du nom d'unité réellement installée, la reprise des fichiers PID et des skills, les alias jean posés à l'installation, les variables JEAN_* lues en second.
La CLI perd ses alias hérités (skills, machine, tools, web-access, mem, upgrade, self-update, paths, llama) et son aide est réorganisée autour des deux services. Chaque commande a désormais un seul nom. app quitte l'aide : c'est le comportement du double-clic, qu'on n'atteint pas en tapant son nom.
Les binaires publiés prennent des noms lisibles : ajean-linux, ajean-linux-arm, ajean-macos, ajean-macos-arm, ajean-windows.exe, ajean-windows-arm.exe. Le suffixe -arm désigne l'arm64, son absence l'x86-64.
Ce qui n'a pas été testé
La reprise 0.7 vers 0.8 a été vérifiée sur Linux (un serveur réel, avec 9 presets, 24 pages de mémoire et 8 modèles) et sur Windows (dossier de test complet), plus par trois tests automatisés. Elle n'a pas été essayée sur macOS, faute de machine : le support macOS reste globalement non validé sur du matériel Apple. Sauvegardez $AJEAN_HOME avant de vous lancer.
0.7.13
Une version de finition sur la liste des presets, plus une correction qui rend le changement de preset instantané.
Changer de preset est désormais immédiat
/api/switch attendait la fin du redémarrage du service (arrêt de llama-server, puis deux secondes de vérification) avant de répondre. Pendant tout ce temps la liste ne bougeait pas et le clic semblait sans effet. Le serveur écrit maintenant la configuration, répond aussitôt, et relance le moteur en arrière-plan : la sélection bascule tout de suite.
Le clic donne aussi un retour visuel : le liseré de la ligne visée arrive en glissant depuis la gauche, atténué tant que la bascule est en cours, puis passe au blanc avec sa puce quand le preset est réellement actif.
Le bouton d'envoi attend que le modèle soit prêt
Envoyer un message à un moteur qui n'a pas fini de charger son modèle ne menait nulle part. Le bouton et la touche Entrée sont maintenant bloqués tant que le modèle n'est pas chargé, et un message sous le champ l'annonce. Si l'état du service est inconnu, rien n'est verrouillé.
Détails d'affichage
- Le liseré du preset actif est un trait droit, un peu plus large, correctement rogné par l'arrondi de la carte au lieu d'en dépasser.
- La puce du preset actif est dessinée en CSS : même taille et même hauteur sous Windows que sous macOS, là où le caractère
●dépendait de la police du système. - Le survol et la sélection dans la liste sont moins appuyés.
- Les animations de sélection ne se rejouent plus à chaque rafraîchissement de la liste — la puce ne clignote plus en boucle.
v0.7.12
Correctifs d'interface dans la foulée de la 0.7.11. Tout ce qui bougeait, sautait ou clignotait après un chargement se tient enfin tranquille.
L'éditeur de preset ne s'ouvre plus au milieu
Ouvrir un preset après en avoir fait défiler un autre rouvrait la fiche à la position précédente. La remise à zéro du défilement existait, mais elle était faite avant l'affichage : écrire une position de défilement sur un élément non affiché ne fait rien du tout, le navigateur restaurait donc l'ancienne. Elle se fait maintenant après l'affichage, puis une seconde fois une fois le contenu en place.
Le sélecteur de moteur ne glisse plus tout seul
En passant d'un preset à l'autre, on voyait le curseur du sélecteur (précompilé, compilé, personnalisé) glisser de l'ancienne valeur vers la nouvelle une fois le chargement terminé. Le changement d'état avait pourtant lieu pendant que le formulaire était caché : WebKit diffère les transitions des éléments non rendus et les rejoue à leur retour à l'écran. Les transitions du formulaire sont donc coupées tant qu'il est masqué, et pendant l'image où il réapparaît.
Dans le même esprit, le voile de chargement de la modale est devenu opaque, sous le titre resté lisible. Rien du preset précédent ne peut plus apparaître une fraction de seconde.
Le menu ne saute plus au chargement
Plusieurs blocs (config active, jauges machine, liste des presets, état de l'accès internet, serveurs MCP) sont vides tant que le serveur n'a pas répondu, puis prennent leur taille réelle : tout le menu se décalait d'un cran, ce qui se voyait surtout sur les sections du bas. Leur hauteur est désormais mémorisée d'une session à l'autre et réservée dès le démarrage, puis rendue quand le vrai contenu arrive. La réserve tient la place, elle n'affiche jamais d'information fausse.
Les champs Crawl4AI, eux, étaient visibles au départ puis repliés dès que l'état arrivait, le moteur par défaut étant l'intégré. Ils partent maintenant masqués. La section Moteur ne grandit plus non plus : son texte d'en-tête part à sa taille définitive et la ligne d'état des trois moteurs a sa hauteur réservée.
Au passage, un chargement en échec (accès distant coupé par exemple) n'interrompt plus les autres.
Détails
Les pastilles « actif » et « inactif » disparaissent de l'accès internet et des serveurs MCP : l'interrupteur et la liste le disent déjà. Reste la seule information utile, l'anomalie « injoignable », qui signale que les outils web ne sont pas fournis au modèle.
Dans la mémoire, la ligne « Voir la mémoire » perd son chevron et son libellé est enfin centré. Créer une page annonçait « Nouveau Page » et demandait un « Nom du preset » : chaque type a maintenant ses propres libellés.
Mise à jour
ajean update
Chaque correction a été vérifiée en mesurant le rendu réel, position et hauteur des éléments avant et après chargement, pas seulement à l'oeil.
v0.7.11
Version d'interface. Le menu de réglages suit désormais une seule et même grammaire de bout en bout, les modales s'ouvrent sans temps mort, et plusieurs irritants de l'app iPhone disparaissent.
Un menu homogène
Chaque section obéit à la même règle : une étiquette de groupe, puis une carte, puis des lignes séparées par un filet. « Mode agent » était le plus voyant, ses quatre sous réglages (mémoire, accès internet, serveurs MCP, paramètres) formaient des îlots décollés, chacun avec son titre enfermé dans sa carte et des marges au cas par cas. Même traitement pour « Accès OpenAI » (trois cartes accolées pour un seul sujet, maintenant réunies), « Accès distant » (le bouton de démarrage du tunnel flottait entre deux cartes), « Moteur » (les trois choix llama.cpp étaient des cartes dans une carte, ce sont des lignes), « System prompt » et « Presets », dont le contenu flottait à nu sous le titre.
Les presets et les pages mémoire ne sont plus une pile de petites cartes bordées mais des lignes d'une seule carte. Le preset actif se signale par un voile et un liseré à gauche. Son bouton d'édition devient un engrenage.
Les boutons « copier » et « ouvrir » des adresses passent en icônes au trait dans un bouton carré à la hauteur du champ : les glyphes texte se posaient de travers et changeaient de largeur selon la police. Dans la mémoire, « Pages » devient « Voir la mémoire ». La pastille d'état n'affiche plus le port, qui reste visible là où il sert vraiment, dans l'adresse de l'endpoint OpenAI.
Modales
Ouvrir un preset attendait la fin de trois requêtes avant d'afficher quoi que ce soit. En accès distant, le clic paraissait mort pendant une seconde. La modale s'ouvre maintenant tout de suite, avec un indicateur de chargement à la place du formulaire, et le formulaire apparaît complet d'un seul coup. La carte occupe sa taille finale dès l'ouverture, donc aucun à coup au moment où le contenu arrive.
Ouverture et fermeture sont animées, dans les deux sens, sur toutes les modales. L'animation se désactive si le système demande de réduire les animations.
Le pied de l'éditeur tient sur une seule ligne : la suppression devient une icône, et son option « supprimer aussi le fichier .gguf » se pose là où elle a du sens, dans la boîte de confirmation. Elle est décochée à chaque ouverture, aucun modèle ne peut donc être effacé parce que la case serait restée cochée d'une fois sur l'autre.
iPhone
Dans l'app installée sur l'écran d'accueil, le tiroir du menu passait sous l'encoche, contrairement à la conversation. L'esquive d'encoche était bien posée, mais un bloc de style plus bas dans la feuille réécrivait la marge et l'effaçait.
Le rectangle gris que Safari peint sur tout élément touché est supprimé. Les états visuels propres à l'application, eux, sont conservés.
Chat
Quand le raisonnement ou les appels d'outils sont masqués, l'indicateur d'activité disparaissait dès qu'une bulle arrivait, même invisible : le fil restait donc vide, sans aucun signe, pendant que le modèle travaillait. Il tient maintenant compte de ce qui est réellement affiché et reste présent jusqu'à l'arrivée de la réponse.
Une conversation vide affiche le logo AJEAN et une invitation, au lieu d'un écran nu.
Mise à jour
ajean update
Les changements ont été vérifiés sur le rendu réel, en thème clair et sombre, et déployés sur un serveur de production avant publication. La correction PWA sous iOS découle de la cause identifiée dans la feuille de style, mais n'a pas été reproduite sur un appareil de test.