tient compte de `MAIA_CWD`, puis de `INIT_CWD` (dossier où pnpm/npm a été lancé), puis de `process.cwd()`. C'est aussi vrai pour `maia run` et les chemins relatifs vers un `AGENT.md`. Le `--` entre `pnpm dev` (ou `pnpm maia`) et les options est facultatif : s'il est transmis, MAIA l'ignore.
| `/createAgent [nom]` | crée un sous-agent depuis le modèle `.ressources/AGENT.md` : demande le niveau (projet `./.maia/agents` ou global `~/.maia/agents`) puis le nom |
L'interface est une application React rendue par [Ink](https://github.com/vadimdemedes/ink) (Ink 8 : React ≥ 19.2 ; vérifiez la version de Node qu'il exige).
Le CLI garde une façade impérative (`InkUI` : `info`, `select`, `readInput`, `confirm`, `withSpinner`…) au-dessus d'un `UIStore` sans dépendance à React,
ce qui rend la logique testable (`store.test.ts`). Les composants (saisie, liste, confirmation, spinner) sont écrits à la main ; seuls les badges viennent de [`@inkjs/ui`](https://github.com/vadimdemedes/ink-ui).
Présentation : la bannière d'accueil reprend le style « MAIA » : image à gauche, affichée telle quelle en couleurs réelles (demi-blocs ▀▄█ : 32 × 32 pixels, soit 32 colonnes × 16 lignes ; sans pixelisation). Elle vient de `packages/ui/assets/mask.png` ; `python3 packages/ui/scripts/make-mask.py [taille]` (Pillow) régénère `src/mask-data.ts` à une autre taille, titre « Maia » en gros caractères pixel avec ombre et dégradé arc-en-ciel à droite, puis trois textes (vert : « Assistant IA en ligne de commande… », rose : « Un seul agent, plusieurs modèles », puis la signature « Arnaud VINCENT » en jaune et « ClaudeCode » en orange). Terminal d'au moins 58 colonnes pour la version complète ; entre 25 et 57 : sans le masque ; en dessous : cadre à double bordure compact, titre centré ;
Les réponses de l'agent sont rendues en markdown (titres, listes, code, tableaux, gras…) avec [`markdansi`](https://github.com/steipete/markdansi). Tout est isolé dans `packages/ui/src/markdown.tsx` : pour le retirer, supprimer ce fichier, la dépendance `markdansi` et remplacer `<Markdown />` par `<Text>` dans `components.tsx`. Le texte en cours de streaming reste brut ; la mise en forme apparaît quand la réponse est terminée. `MAIA_NO_MARKDOWN=1` désactive le rendu sans toucher au code, et un échec de `markdansi` retombe sur le texte brut.
Les couleurs sont dans `packages/ui/src/colors.ts`. Une fois la réponse validée, la question est remplacée par la ligne `◆ Question : choix` dans le journal.
Barre de contexte : sous la zone de saisie, en bas à droite, une barre de carrés ■ (de gauche à droite) montre la taille du contexte de la conversation par rapport à la taille maximale du modèle, à l'échelle **1 carré = 10 000 tokens** (200 000 tokens → 20 carrés), avec le libellé « 45.7k / 200k · 23% ». Les carrés sont verts, jaunes à partir de 60 %, rouges à partir de 85 %. Si la barre ne tient pas dans le terminal (très grands contextes), l'échelle est agrandie par paliers de 10 000 et indiquée (`1■ = 20k`).
- **Taille maximale** : après le choix du modèle (au démarrage, avec `--model`/config, et avec `/model`), MAIA demande au LLM « Taille maximale du contexte pour ce modèle » (réponse en tokens, lue par `parseTokenCount` : `128000`, `128 000 tokens`, `200k`, `1,5 million`…). La valeur est annoncée (INFO) ; une réponse par modèle et par exécution. Si la réponse est illisible, ou si la question échoue ou dépasse 60 s (Ctrl+C l'abandonne), un avertissement s'affiche et la barre est masquée. Un LLM peut se tromper sur sa propre limite : la valeur affichée en INFO permet de la vérifier.
- **Contexte utilisé** = tokens de tout ce que la session enverrait au modèle : prompt système (MAIA.md…), définition des outils, messages (texte, appels d'outils et leurs résultats, +4 tokens de structure par message) et contexte en attente (résultats de commandes `!`, fichiers `@`). Le calcul est **uniquement local et déterministe**, avec [js-tiktoken](https://github.com/dqbd/tiktoken) (vocabulaire `o200k_base`) ; la consommation renvoyée par le serveur (`usage`, dont `total_tokens`) n'est jamais utilisée pour la barre. C'est une **somme** des comptes de chaque élément : la valeur ne fait qu'augmenter quand la conversation s'enrichit, et ne diminue que si le contexte est réduit (`/clear`, `/compact`, tour interrompu, autre session). Le comptage est refait **avant chaque saisie** de l'utilisateur, après chaque réponse du LLM et chaque lot de résultats d'outils, et **pendant** la réponse du modèle (au plus 4 fois par seconde, avec le texte déjà reçu) ; la valeur est affichée à 100 tokens près (`123.5k`) ; le compte de chaque message est mémorisé, donc le recalcul reste instantané. Pour les modèles qui n'utilisent pas ce vocabulaire (Claude, Mistral…), la valeur est une bonne approximation, pas le compte exact du serveur.
La zone de saisie est encadrée par une bordure en pointillés (┈) **au-dessus et au-dessous** seulement, en `greenBright`, sans bords gauche ni droit.
À sa gauche, le spinner `arrow3` de [cli-spinners](https://github.com/sindresorhus/cli-spinners) s'anime en continu. Pendant l'attente de la réponse après validation de la saisie, « En réflexion » s'affiche avec le spinner `dots10` à sa gauche, en `yellowBright` (même spinner pour les autres attentes : chargement des modèles, exécution d'une commande `!`).
| Saisie | Entrée : envoyer · Alt+Entrée ou Ctrl+J : saut de ligne (sans envoyer) ; Maj+Entrée aussi, mais seulement sur un terminal compatible avec le protocole clavier kitty (Kitty, WezTerm, Ghostty, iTerm2…) : ailleurs il est identique à Entrée et envoie le prompt · ←/→ : se déplacer · Début (Home) ou Ctrl+A : début du prompt · Fin (End) ou Ctrl+E : fin du prompt · ↑/↓ : historique · Ctrl+U/W : effacer · Ctrl+C ou Ctrl+D (saisie vide) : quitter · le collage multi-lignes est conservé |
(`~/…` désigne le dossier personnel). Ce dossier est capturé une fois au démarrage, affiché (« Dossier de travail : … ») et transmis aux outils et aux providers ;
les règles de permissions (`Read(./.env)`, `Edit(src/**)`) sont elles aussi relatives à ce dossier. Seul `~/.maia` (config, `settings.json` et skills utilisateur) est lu depuis le dossier personnel.
- stdout et stderr sont affichés dans l'ordre d'arrivée, avec le code de sortie en cas d'échec ; durée maximale 2 minutes ;
- aucune confirmation n'est demandée (c'est vous qui la tapez) et les règles de permissions des outils ne s'appliquent pas ;
- le résultat est ajouté au contexte de **votre prochaine demande** (jusqu'à 30 000 caractères, milieu tronqué au-delà), pour que le modèle sache ce qui a été lancé. `/clear` l'efface ;
- Ctrl+C interrompt la commande ; `!` seul affiche l'aide. Les programmes interactifs (vim, less, saisie au clavier…) ne sont pas pris en charge.
| `compare @a.ts et @ b.ts` | plusieurs fichiers ; la forme `@ chemin` (avec espace) est acceptée |
| `lis @'mon dossier/a b.txt'` | guillemets simples pour un chemin avec espaces (`\'` : guillemet littéral) |
| `résume @docs` | un dossier : la liste de ses entrées est injectée |
| `@/chemin/complet.txt` · `@~/notes.md` | chemin complet, ou depuis le dossier personnel |
- Chemins relatifs au dossier de lancement (voir « Dossier de base »). Aucune confirmation : c'est vous qui citez le fichier, les règles de permissions des outils ne s'appliquent pas.
- Un `@mot` qui ne désigne aucun fichier existant reste du texte ordinaire (`@mention`, `@Override`, `jean@exemple.fr`) ; un avertissement n'apparaît que s'il ressemble à un chemin (`@src/x.ts` introuvable).
- Limites : 2 Mo par fichier, 50 000 caractères injectés par fichier (milieu tronqué au-delà) et 150 000 par message ; les fichiers binaires sont ignorés ; un même fichier cité plusieurs fois n'est injecté qu'une fois.
- Le contenu est marqué comme donnée, pas comme instructions. Les `@fichier` écrits *dans* le corps d'une commande personnalisée ne sont pas encore traités (seuls ceux de votre saisie le sont).
Au lancement, `./.maia` et `./.maia/.sessions` sont créés dans le dossier d'exécution s'ils n'existent pas. Les sessions sont une fonctionnalité de l'application.
-`/delete` : liste les sessions enregistrées ; **Espace** coche ou décoche la session sous le curseur, **Suppr** supprime les sessions cochées **du disque** après confirmation (`o/n`). La session courante n'est pas proposée (`/new` d'abord pour la supprimer) ; les fichiers illisibles sont proposés aussi. Les traces de sous-agents et les sessions compactées (`xxx__n`) sont des sessions comme les autres : elles ne sont pas supprimées en cascade.
-`/new` : nouvelle session, nouveau contexte (`MAIA.md` / `CLAUDE.md` et skills rechargés) ; l'utilisateur est averti que la session quittée est sauvegardée (sous son nom).
-`OpenAI_EndPoint` : suffixe ajouté à `LiteLLM_URL` pour envoyer une requête au format OpenAI.
-`LiteLLM_Models` : suffixe ajouté à `LiteLLM_URL` pour récupérer la liste des modèles disponibles.
- Optionnel : `LiteLLM_API_Key` (ou variable d'environnement `LITELLM_API_KEY`), envoyée en `Authorization: Bearer …`. Ce fichier peut donc contenir un secret : ne pas le versionner.
- Optionnel : `DefaultModel` (ex. `litellm:gpt-4o`, ou un morceau de nom comme `sonnet`) : modèle des workflows autonomes (`maia run`) quand ni `--model` ni l'agent parent n'en indiquent.
- Optionnel : `MaxOutputTokens` (entier, ex. `16000`) : `max_tokens` envoyé à chaque requête. Sans lui, le serveur applique sa limite par défaut, parfois courte : un long fichier écrit par l'outil `Write` peut être coupé (le paramètre `content` arrive alors absent). MAIA affiche un avertissement quand la réponse est coupée (finish_reason `length`, ou sortie qui tombe pile sur une limite usuelle 4096/8192… avec un appel d'outil : certains serveurs « réparent » alors l'appel et annoncent `tool_calls`).
Cas constaté avec `anthropic--claude-4.5-sonnet` : `completion_tokens: 4096` et `Write` reçu avec `path` seul → fixer par ex. `"MaxOutputTokens": 32000`.
- Diagnostic : `MAIA_DEBUG_LLM=1 maia` enregistre le flux brut renvoyé par LiteLLM dans `maia-llm-debug.log` (dossier temporaire ; `MAIA_DEBUG_LLM=/chemin/fichier.log` pour choisir le fichier).
et demande de choisir celui de la session. `/model` rouvre cette liste en cours de session (la conversation est conservée) ; `/models` l'affiche seulement, sans rien changer.
Au lancement, le contexte de départ est chargé depuis le **dossier de lancement** : `MAIA.md` s'il existe, sinon `CLAUDE.md` (s'il existe).
Le fichier chargé est indiqué par un message `INFO` ; si aucun des deux n'existe, un `WARN` le signale. Seul ce dossier est regardé (pas les dossiers parents).
Les dossiers `.claude` ne sont **plus lus** (ni `settings.json`, ni `commands/`, ni `skills/`, ni `.claude/CLAUDE.md`, ni `~/.claude`).
- **Skills** : `.maia/skills/<nom_du_skill>/SKILL.md` (ou `skill.md`), chargés depuis trois emplacements. Le plus spécifique l'emporte en cas de même nom :
1. le dossier utilisateur : `~/.maia/skills` ;
2. le dossier parent du dossier de lancement : ex. `/X/.maia/skills` pour un lancement dans `/X/Y` ;
3. le dossier de lancement : ex. `/X/Y/.maia/skills`.
Le fichier commence par un en-tête YAML : `description` (objectif du skill) et `name` (optionnel ; s'il est présent, il doit être identique au nom du dossier, qui fait foi).
Seule la liste (nom + description) est mise dans le prompt ; le contenu est chargé à la demande par l'outil `Skill`, ou lancé directement avec `§nom`.
```
---
name: sk_test # optionnel, = nom du dossier
description: Lance les tests du projet et résume les échecs
---
Instructions du skill… ($ARGUMENTS = arguments passés avec §sk_test)
Conséquence : il n'y a plus de variables d'environnement venant de la configuration de l'agent ni de slash-commandes personnalisées. Les permissions sont décrites dans la section suivante.
| `deny` | l'outil ne peut pas être utilisé (l'agent reçoit un refus) |
**Ordre d'évaluation : deny, puis ask, puis allow.** La première famille qui correspond décide, quelle que soit la précision des règles : un `allow` étroit comme `Bash(aws s3 ls)` ne fait pas exception à un `deny` large comme `Bash(aws *)`, ni à un `ask` plus large. **Sans règle applicable, c'est `ask` qui s'applique** (pour tous les outils, y compris Read, Glob, Grep…).
Où les définir : `~/.maia/settings.json` (utilisateur) et `./.maia/settings.json` (dossier de lancement). Les listes des deux fichiers s'additionnent. Le fichier `../.maia/settings.json` du dossier parent n'est **pas** pris en compte. Dans chaque fichier, la propriété `permissions` contient une liste par type, tous facultatifs :
Format d'une règle : `Outil` (tous les appels de l'outil) ou `Outil(motif)` :
| Outil | Le motif porte sur | Exemples |
| --- | --- | --- |
| `Bash` | la commande ; `*` = n'importe quelle suite ; `cmd *` couvre `cmd` avec ou sans arguments (mais pas `cmdx`) ; sans `*` : commande exacte | `Bash(allow stop *)`, `Bash(git status)`, `Bash(npm run test:*)` |
| `Read`, `Write`, `Edit`, `Glob`, `Grep` | le chemin (relatif au dossier de lancement, motifs glob) | `Read(./.deny.md)`, `Edit(src/**)` |
| `WebFetch` | le domaine | `WebFetch(domain:docs.example.com)`, `WebFetch(domain:*.example.org)` |
| `Skill` | le nom du skill | `Skill(pdf)` |
| autres outils (ex. ajoutés plus tard) | une valeur texte de l'entrée de l'outil (`*` accepté) | `mcp__db__query(SELECT *)` |
Précisions sur `Bash` :
- commande composée (`&&`, `;`, `|`) : un `deny` ou un `ask` s'applique dès qu'**une** des parties correspond, y compris cachée dans `$(…)`, des backticks ou des parenthèses ; un `allow` ne s'applique que si **toutes** les parties sont couvertes ;
- un `allow` ne couvre jamais une commande avec substitution (`$(…)`, backticks) : elle repasse en `ask` ; une redirection de sortie (`>`, `>>`, `2>`, `&>`) repasse en `ask` sauf si le fichier visé est autorisé par une règle `Redirect(chemin)` (`2>&1` n'écrit dans aucun fichier : sans effet). `deny: ["Redirect(.env)"]` interdit d'écrire dans ce fichier par redirection, même avec `Bash` autorisé. Détails : `GUIDE_PERMISSIONS.md` § 8.
Au démarrage, MAIA indique (`INFO`) les fichiers chargés et le nombre de règles, ou qu'aucune règle n'est définie ; un JSON invalide ou une règle mal formée donne un `WARN` et est ignoré. Les `deny` sont du « meilleur effort » : ils ne remplacent pas un vrai bac à sable. Les commandes `!` et les références `@fichier` tapées par l'utilisateur ne passent pas par ces règles.
Attention : `./.maia/settings.json` d'un dépôt tiers peut contenir des règles `allow` larges ; relisez-le avant de lancer MAIA dans un projet que vous ne connaissez pas.
**Réponse « stop »** : à une demande d'autorisation `ASK … (o/n/s=stop)`, `s` signifie « je ne réponds pas à la question, je veux interrompre pour donner d'autres informations ou faire autre chose ». L'action demandée n'est **pas** exécutée (le modèle en est informé), les autres actions qui suivaient dans la même réponse ne sont pas lancées (celles déjà autorisées vont à leur terme), puis MAIA rend la main avec le message « Itération interrompue (stop) ». **Rien n'est annulé** : la conversation et les résultats déjà obtenus restent dans le contexte, et ce que vous tapez ensuite est ajouté à la suite (contrairement à Ctrl+C, qui restaure la conversation d'avant la demande). « stop » n'existe que pour les confirmations d'outils de l'agent principal (pas pour « Continuer ? » à la limite d'itérations, ni pour les confirmations d'un sous-agent, où `s` est ignoré).
La commande `/permissions` affiche les règles actives, fichier par fichier, avec un badge `ALLOW` (vert), `ASK` (jaune) et `DENY` (rouge) suivi des règles du type, une par ligne ; seuls les types non vides sont montrés. Après avoir modifié un `settings.json`, `/clear` recharge la configuration.
- **TodoWrite** : liste de tâches de la session (`pending` / `in_progress` / `completed`, forme `activeForm` pour la tâche en cours). Chaque appel remplace la liste ; le résultat (☐ ▶ ☑) est affiché à l'utilisateur et renvoyé au modèle. Un avertissement est ajouté si plusieurs tâches sont « en cours ».
- **CurrentModel** : renvoie le modèle LLM utilisé par la session (valeur à jour après un `/model`). Sans paramètre, sans confirmation ; le modèle est lu au moment de l'appel.
- **ListModels** : renvoie la liste des modèles disponibles auprès de LiteLLM (même source que `/models`, interrogée à chaque appel), en signalant celui de la session. Sans paramètre, sans confirmation. Permet à l'agent de répondre à « quels sont les modèles disponibles ? » ; il ne change pas de modèle (c'est `/model`, côté utilisateur). Disponible aussi pour les sous-agents et l'agent parent des workflows.
- **WebFetch** : télécharge une URL http(s) (HTML converti en Markdown avec `turndown`, JSON mis en forme, texte brut ; 5 Mo et 30 s maximum, `max_length` caractères renvoyés). Pas de JavaScript exécuté, pas de PDF ni d'images.
- Une redirection vers un **autre domaine** n'est pas suivie : l'outil renvoie la nouvelle URL, que le modèle doit rappeler (ce qui repasse par la confirmation).
- Le contenu est signalé au modèle comme non fiable (risque d'injection de prompt) ; il ne passe par aucun second modèle de résumé.
L'utilisateur peut créer ses propres outils programmatiques : le LLM les appelle comme n'importe quel autre outil quand leur description correspond au besoin. Ils sont chargés au démarrage et rechargés par `/clear` et `/new`.
- **Où** : `./.maia/tools` (dossier de lancement) et `~/.maia/tools` (utilisateur, valable pour tous les projets) ; jamais le dossier parent. À nom égal, l'outil du projet l'emporte. Un sous-dossier = un outil : `./.maia/tools/addition/TOOL.json` (`tool.json` accepté aussi) et ses scripts. Un exemple complet est fourni dans `examples/tools/addition` ; exemple de sous-agent : `examples/agents/code_reviewer` (à copier dans `./.maia/agents`).(à copier dans `./.maia/tools/`).
| `description` | **Obligatoire** : dit au LLM à quoi sert l'outil et quand l'appeler. |
| `name` | Facultatif, le nom du dossier par défaut. Lettres, chiffres, `_` et `-` (64 caractères maximum). Ne peut pas reprendre le nom d'un outil intégré (Read, Bash…) ni d'un autre outil personnalisé. |
| `command.parameters` | Paramètres fournis par le LLM. `type` : `"number"` ou `"string"`. `default` : valeur utilisée si le LLM n'en donne pas (ex. `{{param2}}` devient `0`) ; sans `default`, le paramètre est obligatoire. `description` facultative par paramètre. |
| `command.run` | Ligne de commande ; chaque `{{paramètre}}` est remplacé par sa valeur. `./` désigne le dossier de l'outil (`./test.js` = `./.maia/tools/addition/test.js`) et c'est le dossier d'exécution. |
- **Sécurité : pas de shell.** La ligne `run` est découpée en arguments (guillemets simples ou doubles possibles) et exécutée directement : une valeur envoyée par le LLM reste **toujours un seul argument** (`; rm -rf /` ou `$(cmd)` sont reçus tels quels par le script, jamais interprétés). En contrepartie, `run` n'accepte ni tubes `|`, ni redirections `>`, ni variables `$HOME` ; pour cela, appeler un script (`sh ./script.sh {{p}}`). Le programme (premier mot) ne peut pas être un `{{paramètre}}`. Sous Windows, les scripts `.cmd`/`.bat` ne sont pas lancés directement (utiliser `node`, `python`, ou `cmd /c ./script.cmd`).
- **Exécution** : timeout de 2 minutes, sortie limitée à 30 000 caractères ; un code de sortie non nul est renvoyé au LLM comme une erreur (avec la sortie d'erreur du script). Le script reçoit `MAIA_CWD` (dossier de lancement de MAIA, pour retrouver le projet) et `MAIA_TOOL_DIR`.
- **Permissions** : un outil personnalisé exécute un programme, donc il demande confirmation par défaut. Les règles de permissions s'appliquent avec son nom : `"Addition"` dans `allow` l'autorise sans demander, `"Addition(5)"` cible une valeur de paramètre.
- **Erreurs** : une définition invalide (description absente, type inconnu, `{{param}}` non déclaré, JSON cassé…) est signalée par un avertissement (`WARN`) et l'outil est ignoré ; les autres restent chargés. Une virgule en trop avant `]` ou `}` est tolérée. Au chargement, les outils valides sont annoncés (`INFO`).
## Exécution des outils : premier plan et parallèle
- Le **raisonnement du LLM reste bloquant** : l'utilisateur attend la réponse, ce qui préserve l'intégrité du contexte.
- **1 outil demandé** : exécuté au premier plan (annonce, confirmation éventuelle, exécution, résultat).
- **2 outils ou plus** dans la même réponse : chaque outil part dans sa **propre tâche, en arrière-plan**, dès qu'il est autorisé. L'agent attend la fin de **toutes** les tâches, puis renvoie les résultats au LLM **dans l'ordre où il les a demandés**. Une erreur ou un refus n'annule pas les autres outils.
- Le spinner indique « Outils en parallèle : n/total terminés (…) ».
- Limites : ce sont des tâches asynchrones Node (pas des threads système ; les processus lancés par Bash ou les outils personnalisés tournent réellement en parallèle) ; 8 outils au plus en même temps (`MAX_PARALLEL_TOOLS`) ; les confirmations sont posées une par une ; les appels qui portent sur le même fichier (Read/Write/Edit) ou TodoWrite passent l'un après l'autre (`Tool.lockKey`).
Le compactage résume toute la conversation de la session pour libérer du contexte.
1. La conversation actuelle est sauvegardée telle quelle sous son nom.
2. Le LLM reçoit la demande « Faire un résumé compacté de nos échanges… » (5 rubriques : Contexte du projet, Objectif actuel, Fichiers pertinents, Décisions/Conclusions, Prochaines étapes), limité à 4000 tokens. Les commandes `!` et fichiers `@` pas encore envoyés font partie de ce qui est résumé.
3. Le contexte est supprimé comme avec `/clear`, mais `MAIA.md` / `CLAUDE.md` ne sont **pas** rechargés : le résumé les remplace comme contexte de départ (dans le prompt système). Outils, skills et permissions sont rechargés.
4. La session continue sous le nom `{nom d'origine}__{n}` : `xxx` → `xxx__1` → `xxx__2`… Après `__1000`, le suivant reprend à `__1` et écrase ce fichier. Le fichier d'avant compactage reste intact ; le fichier `xxx__n` contient le résumé (`startContext`) et la suite de la conversation, et `/load xxx__n` le restaure.
5. Le nombre de tokens est recalculé et la barre de progression mise à jour.
Déclenchement automatique : dès que le contexte dépasse `Seuil_max = Max_Token_Context - (4000 + 500)` (4000 = tokens du résumé, 500 = marge d'erreur). La taille maximale est celle demandée au LLM au choix du modèle ; si elle est inconnue (barre masquée), seul `/compact` fonctionne. Le contrôle a lieu avant l'envoi de chaque demande et après chaque tour.
L'appel est **bloquant** (pas de tâche de fond). Ctrl+C l'annule sans rien modifier ; en cas d'erreur, la conversation reste inchangée.
**Contexte refusé par le serveur** : si LiteLLM répond « Input is too long » (ou équivalent), MAIA compacte automatiquement la conversation (résultats d'outils tronqués à 1 500 caractères dans la demande de résumé, pour qu'elle tienne), puis reprend la demande une seule fois. Si le compactage échoue aussi, l'erreur d'origine est affichée (utilisez alors `/new`). Les sous-agents n'ont pas ce mécanisme.
MAIA peut confier une tâche à un **sous-agent** : un processus Node autonome, avec sa propre conversation (vide) et sa propre définition. L'agent principal le lance avec l'outil `Agent` (`agent` = nom, `task` = tâche complète : le sous-agent ne voit rien de la conversation principale). Seul le **résumé final** du sous-agent revient dans la conversation principale. Plusieurs appels `Agent` dans une même réponse s'exécutent en parallèle.
Définition : un dossier par agent, avec un fichier `AGENT.md`, dans `./.maia/agents/<nom>/` (dossier de lancement) ou `~/.maia/agents/<nom>/` (utilisateur) ; le projet l'emporte sur l'utilisateur à nom égal. **Créer un agent** : `/createAgent` (ou `/createAgent mon_agent`) demande le niveau (Projet / Global) et le nom, puis copie le modèle `.ressources/AGENT.md` dans `<dossier agents>/<nom>/AGENT.md` en y mettant le bon `name:` (jamais d'écrasement d'un agent existant). Le modèle explique chaque propriété en commentaires (`#`) ; les propriétés facultatives y sont commentées, supprimez le `#` pour les activer. Tapez ensuite `/clear` pour charger le nouvel agent. Le dossier `.ressources` est celui de MAIA (racine du projet MAIA, ou `MAIA_RESSOURCES`), pas celui de votre projet. Les commentaires `# …` en fin de ligne de l'en-tête sont acceptés (mettez la valeur entre guillemets si elle contient ` #`).
- Les trois sections « Instructions system », « Missions » et « Output » sont obligatoires (titres Markdown de n'importe quel niveau). La section « Constraints » (ou « Contraintes »), à placer **après Missions**, est facultative : les limites de l'agent, c'est-à-dire ce qu'il n'a pas le droit de faire et dans quelles conditions il doit s'arrêter pour demander l'intervention de l'utilisateur ou d'un tiers. Un sous-agent ne pouvant pas poser de question, devant un de ces cas il n'agit pas et le signale dans son résumé (ce qui bloque, ce qui est attendu, de qui). C'est une consigne donnée à l'agent ; les interdits techniques se garantissent avec `tools` et `permissions` ; un agent invalide est ignoré avec un avertissement qui dit pourquoi.
- **Héritage** : sans `tools`, `skills` ou `permissions`, le sous-agent a les mêmes outils (y compris les outils personnalisés), skills et règles que l'agent principal. Les règles `permissions` de l'agent **s'ajoutent** aux règles héritées : `deny > ask > allow` reste vrai, un `deny` ou `ask` hérité n'est jamais levé. Un sous-agent ne peut pas lancer de sous-agent.
- **Résumé** : la dernière réponse du sous-agent suit la section `Output` ; s'il épuise son budget d'étapes sans conclure, un appel supplémentaire lui demande ce résumé.
- **Effort** : borne le nombre d'étapes du sous-agent (low 10, medium 25, high 50) et module son prompt.
- **Modèle** : identifiant exact ou fragment (`sonnet` → premier modèle LiteLLM dont le nom contient « sonnet ») ; introuvable → modèle de l'agent principal, avec avertissement.
- **Autorisations** : si le sous-agent doit demander une confirmation, la question s'affiche dans l'interface principale, préfixée `[sous-agent nom]` (une question à la fois). Ctrl+C interrompt aussi les sous-agents en cours.
- **Lancement par l'utilisateur** : une saisie qui **commence** par `&` lance un sous-agent directement : `&code_reviewer relis src/a.ts` (le nom est reconnu sans tenir compte de la casse ; sans tâche, l'agent exécute sa mission telle que décrite). `&` seul liste les agents. Aucune confirmation de l'outil `Agent` n'est demandée (c'est vous qui lancez), mais les autorisations des outils du sous-agent suivent les règles habituelles. Le résumé s'affiche et est joint à votre prochaine demande, comme le résultat d'une commande `!`. Ctrl+C interrompt le sous-agent.
- **Fin de mission** : un message INFO est affiché dans l'interface principale (durée, nombre d'appels d'outils) ; un échec est signalé par un avertissement et renvoyé à l'agent principal comme erreur d'outil. Délai maximal : 30 minutes.
- **Trace** (`trace: true`) : la conversation du sous-agent est enregistrée dans `./.maia/.sessions/` sous le nom `{session de l'agent principal}-{agent}`, ex. `session_2026-10-20_00_01_53-agent_1.json` (`_2`, `_3`… si le nom est pris). Sans `trace`, rien n'est enregistré.
MAIA peut utiliser les outils de serveurs **MCP** accessibles par une URL (Streamable HTTP, ou l'ancien transport SSE). Pas d'OAuth ; seuls les **outils** MCP sont utilisés (pas les ressources ni les prompts).
**1. Déclarer les serveurs (global uniquement)** : `~/.maia/mcp.json`. La clé est le nom du serveur.
-`Mcp(serveur)` : active le serveur, tous ses outils sont autorisés sans confirmation ;
-`Mcp(serveur,outil)` : un outil (`*` accepté, ex. `Mcp(github,delete_*)`) ; en `deny` l'outil n'est jamais proposé au modèle ; en `ask` il demande confirmation ;
-`Mcp(*)` (= `Mcp(*,*)`) : tous les serveurs déclarés et tous leurs outils ; `Mcp(*,outil)` : cet outil sur tous les serveurs ;
-`allow: ["Mcp(github)"]` + `deny: ["Mcp(github,*)"]` : serveur **désactivé**, MAIA ne cherche pas à s'y connecter.
Les permissions sont traitées **avant** toute connexion ; seuls les serveurs activés sont ensuite contactés (en parallèle). Un serveur en échec n'empêche pas MAIA de démarrer : avertissement, état `FAIL`.
**3. Utilisation** : les outils s'appellent `mcp__serveur__outil`. `/mcp` liste les serveurs **activés** : bandeau vert `OK` s'il est connecté, bandeau rouge `FAIL` (avec la cause en grisé) sinon. Les connexions sont coupées à la sortie de MAIA (`/exit`, Ctrl+D, SIGTERM/SIGHUP) et refaites à chaque `/clear`, `/new`, `/load`, `/model` et `/compact`. Les sous-agents n'utilisent pas les outils MCP.
> Guide complet (créer, configurer, lancer, superviser, scripts et services, limites, dépannage) : [`GUIDE_AUTONOMOUS.md`](GUIDE_AUTONOMOUS.md).
Un **workflow** = un **agent parent** (orchestrateur) lancé sans interface + les **sous-agents** qu'il lance. On le démarre en ligne de commande, à la main ou depuis un script, un service, cron… et on interroge MAIA pour connaître son état et celui de chacun de ses agents.
**L'agent parent** est un `AGENT.md` au même format qu'un sous-agent (sections Instructions system, Missions, Constraints (optionnelle), Output). Ses missions et ses contraintes peuvent faire appel aux autres agents par leur nom. Exemple complet : `examples/agents/release_check`.
```bash
maia run ./.maia/agents/release_check/AGENT.md --task "Vérifie la branche feature/x"
maia run X:/projX/.maia/agents/Main/AGENT.md --detach # en arrière-plan : affiche l'identifiant et rend la main
```
- **Sous-agents disponibles** : tous ceux du dossier `.maia/agents` qui contient l'agent parent (ex. `X:/projX/.maia/agents/*` pour `X:/projX/.maia/agents/Main/AGENT.md`) et tous les agents globaux (`~/.maia/agents`) ; à nom égal, celui du dossier du parent l'emporte. Le parent ne se lance pas lui-même.
- **Dossier de travail** : le projet qui contient ce `.maia/agents` (`X:/projX`) ; si le `AGENT.md` est ailleurs, le dossier de lancement.
- **Modèle** : `--model`, sinon le champ `model` du parent, sinon `DefaultModel` dans `~/.maia/settings.json`, sinon le premier modèle de LiteLLM. Chaque sous-agent prend **le modèle du parent**, sauf s'il a son propre champ `model`.
- **En-tête du parent** (en plus des champs habituels) : `timeout` (`90s`, `30m`, `2h` : durée maximale) et `permissionMode`.
- **Options** : `--task` (ou le texte après le fichier), `--model`, `--name` (nom pour retrouver le workflow), `--detach`, `--json`, `--permission-mode wait|deny|allow`, `--timeout`, `--quiet`.
- **Codes de sortie** : 0 terminé, 1 en erreur, 2 mauvaise utilisation, 130 arrêté. La progression s'écrit sur la sortie d'erreur, le résultat sur la sortie standard (`--json` : `{ id, state, label, exitCode, result, error }`).
**Autorisations sans interface** (`permissionMode`, défaut **`wait`**) : les règles `allow` / `ask` / `deny` des `settings.json` s'appliquent comme d'habitude ; seule une demande `ask` change :
-`wait` : la demande est enregistrée, l'agent passe **en attente** jusqu'à `maia approve <id> <demande>` (ou `--deny`) ; le travail déjà fait n'est jamais perdu ;
-`deny` : refusée tout de suite (le modèle en est informé et continue autrement) ;
-`allow` : acceptée tout de suite — à réserver à un environnement isolé.
**Intervention humaine** : l'agent parent dispose de l'outil `RequestIntervention` (« ce qui bloque, ce qui est attendu, de qui ») : c'est ainsi qu'une contrainte du type « si X, demande l'intervention de l'équipe sécurité » se réalise. Le workflow passe en attente jusqu'à `maia reply <id> <demande> "réponse"`, puis le parent continue avec la réponse.
**États** (de chaque agent, et du workflow) :
| État | Sens |
| --- | --- |
| `non exécuté` | agent cité dans les sections du parent mais pas encore lancé (ou workflow pas encore démarré) |
| `en exécution` | il travaille |
| `en attente` | il attend une réponse : autorisation (`approve`) ou intervention (`reply`) |
maia result <id> # résultat final de l'agent parent
maia approve <id> r1 [--deny] # répondre à une autorisation
maia reply <id> r2 "texte" # répondre à une intervention
maia stop <id> # arrêter (sous-agents compris)
```
`<id>` : identifiant (`wf_20261026_234112_a3f9`), un morceau unique de celui-ci, le nom donné par `--name`, ou `last`. Dans MAIA interactif : `/workflows` (`/workflows all`) et `/workflow <id>`.
**Registre** (global) : `~/.maia/workflows/<id>/` — `workflow.json`, `agents.json` (états), `events.jsonl` (journal), `requests/` (demandes et réponses), `traces/` (conversation de chaque agent), `result.md`, `run.log` (mode `--detach`). Pas de serveur : les commandes lisent ces fichiers, écrits uniquement par le processus du workflow (les réponses et l'arrêt sont déposés dans des fichiers séparés).
- Provider LiteLLM : validation sur un vrai serveur (streaming SSE et appels d'outils testés seulement sur des flux simulés) ; compatibilité de `@inkjs/ui` avec Ink 8 à confirmer à l'usage (l'affichage des badges a été vu fonctionner par l'utilisateur).
- Agent : hooks ; MCP : OAuth, ressources/prompts, outils MCP pour les sous-agents ; sous-agents : `reasoning_effort` du modèle non utilisé (l'effort borne les étapes), pas de sous-agent imbriqué.
- Éventuellement rétablir sous `.maia` ce que supprimait l'abandon de `.claude` : variables d'environnement et slash-commandes personnalisées (le code des commandes existe encore dans `packages/agent/src/commands.ts`, il n'est plus appelé).
- Taille de contexte : lue en interrogeant le LLM (demande de l'utilisateur) ; la route `/model/info` de LiteLLM (`max_input_tokens`) serait plus fiable, et un réglage manuel dans `settings.json` pourrait corriger une réponse erronée.
- Nerd Fonts (icônes) : proposé comme réglage optionnel dans `settings.json`, non réalisé.
- Tests : les suites vitest et le typecheck sont à lancer après `pnpm install` (écrits sans avoir pu être exécutés dans l'environnement de développement).
## Travaux réalisés
Journal des évolutions, dans l'ordre :
1.**Squelette** : monorepo pnpm (TypeScript strict, tsup, vitest), contrats `LLMProvider` / `Tool` / `AgentUI`, boucle agent avec appels d'outils, agent par défaut de type « Claude Code » (outils Read, Write, Edit, Bash).
2.**Provider SAP AI Core** puis **choix du modèle** au démarrage et commande `/model`. *(Remplacé ensuite par LiteLLM, voir 12.)*
3.**Outils Glob et Grep** (JavaScript pur, sans ripgrep).
4.**Configuration de l'agent (lot 2)** : fichiers de contexte, permissions, slash-commandes, skills. *(Abandonné avec les dossiers `.claude`, voir 13.)*
5.**`§skill`** : appel direct d'un skill de l'agent.
6.**Dossier de lancement** : tous les chemins relatifs partent du dossier où MAIA est lancé (`MAIA_CWD` > `INIT_CWD` > `cwd`).
7.**Interface Ink** (remplace Clack.js) : bannière « MAIA » avec masque pixel-art et titre arc-en-ciel, messages en bleu clair, zone de saisie en pointillés `greenBright` avec spinner `arrow3`, attente « En réflexion » avec `dots10` en `yellowBright`.
8.**Outils TodoWrite et WebFetch** (lot 3, première partie).
9.**`!commande`** (avec la syntaxe `!'commande avec paramètres'`) et **`@fichier`**.
10.**Renommage en MAIA** (titre « Maia ») et **sessions** : `./.maia/.sessions`, sauvegarde automatique, `/rename`, `/load` (avec réaffichage du contenu), `/new`.
11.**Corrections** : import manquant de `normalizeSessionName`, `session.tools` est une `Map`, en-tête de skill avec espace avant les deux-points (`description : …`).
12.**LiteLLM** : un seul provider, requêtes au format OpenAI en streaming vers le endpoint OpenAI de LiteLLM ; configuration `~/.maia/settings.json` (`LiteLLM_URL`, `OpenAI_EndPoint`, `LiteLLM_Models`, `LiteLLM_API_Key` optionnel). Suppression de SAP AI Core, Anthropic, OpenAI et Ollama.
13.**Renommage technique**`magiccode` → `maia` (packages `@maia/*`, commande `maia`, variables `MAIA_*`, dossier `.maia`) ; textes « MagicCode » → « MAIA » ; bannière « Un seul agent, plusieurs modèles ».
14.**Skills dans `.maia/skills`** (dossier de lancement, dossier parent, dossier utilisateur) ; les dossiers `.claude` ne sont plus lus.
15.**Badges `@inkjs/ui`** : `INFO`, `WARN`, `FAIL` (texte d'erreur `#DC8ADD`) et `ASK` devant les questions.
16.**Suppression des modes et des extensions** : un seul agent MAIA (`packages/agent`), contexte de départ `MAIA.md` sinon `CLAUDE.md` (message `INFO`, ou `WARN` si aucun).
19.**Permissions granulaires** : règles `allow` / `ask` / `deny` par outil (`Outil` ou `Outil(motif)`), évaluées dans l'ordre deny > ask > allow, `ask` par défaut ; définies dans `~/.maia/settings.json` et `./.maia/settings.json` (pas le dossier parent).
20.**`/permissions`** : affiche les permissions chargées, par fichier et par type (badges ALLOW / ASK / DENY) ; les fichiers sont listés l'un sous l'autre au démarrage.
21.**Rendu markdown** des réponses de l'agent via `markdansi`, dans un seul composant isolé (`packages/ui/src/markdown.tsx`), désactivable avec `MAIA_NO_MARKDOWN=1`.
22.**Image de la bannière** : nouvelle image (visage à couronne), fond retiré, réduite à 32 × 32 en couleurs réelles (sans pixelisation ni réduction de palette), centrée verticalement à côté du titre ; données générées par `packages/ui/scripts/make-mask.py`.
23.**Barre de progression du contexte** sous la zone de saisie (1 carré = 10 000 tokens) : taille maximale demandée au LLM après la sélection du modèle, contexte utilisé mis à jour après chaque réponse (consommation renvoyée par LiteLLM, sinon estimation) ; correction d'un import de type manquant dans `ink-ui.tsx`. Valeur alignée sur le contexte réel de la session : contexte en attente compté, tokens de raisonnement exclus, estimation calibrée sur le serveur et signalée par `~`.
24.**Outils personnalisés** : `./.maia/tools/<nom>/TOOL.json` (dossier de lancement uniquement) ; paramètres `number`/`string` avec valeurs par défaut, `run` avec `{{paramètre}}` exécuté sans shell dans le dossier du tool (aucune injection possible), permission `ask` par défaut ; module `packages/agent/src/custom-tools.ts`, exemple `examples/tools/addition` ; exemple de sous-agent : `examples/agents/code_reviewer` (à copier dans `./.maia/agents`).
25.**Threading async des outils** : 1 outil au premier plan ; dès 2 outils, une tâche de fond par outil, attente de toutes les fins avant de répondre au LLM (résultats dans l'ordre demandé, 8 en parallèle au plus, verrou par fichier).
26.**Compactage** : `/compact` et compactage automatique au seuil `Max_Token_Context - (4000 + 500)` ; résumé en 5 rubriques comme contexte de départ (à la place de MAIA.md / CLAUDE.md), session renommée `xxx__n` (cycle 1..1000), barre de contexte recalculée.
27.**Correction de la barre de contexte** : comptage local avec js-tiktoken (`o200k_base`) de tout le contexte (système, outils, messages, contexte en attente), recalculé avant chaque saisie ; fin du mélange consommation du serveur / estimation calibrée (cause des variations) et du marqueur `~`.
28.**Documentation** : `SPECS_TECH.md`, spécifications techniques de toutes les fonctionnalités implémentées.
29.**Outil Write plus tolérant** : message d'erreur qui liste les paramètres reçus (tous les outils), alias de `content` acceptés, description du paramètre précisée.
30.**Appels d'outils coupés ou mal assemblés** : paramètre optionnel `MaxOutputTokens`, avertissement quand la réponse est coupée (`max_tokens`), assemblage plus tolérant des fragments d'arguments d'appels d'outils du flux SSE, journal de diagnostic `MAIA_DEBUG_LLM`.
31.**Coupure à 4096 tokens diagnostiquée** (log `MAIA_DEBUG_LLM`) : détection d'une sortie tombant sur une limite usuelle avec un appel d'outil, message d'erreur de `Write` qui conseille d'écrire en plusieurs morceaux.
32.**Outil Bash via `runShell`** : timeout et Ctrl+C arrêtent toute l'arborescence de processus (groupe de processus, SIGTERM puis SIGKILL) ; stdout et stderr fusionnés dans l'ordre d'arrivée, dernière ligne `[code de sortie : N]` ou état (délai dépassé, interrompu).
34.**Compactage sur « Input is too long »** : réponse du serveur reconnue (`isContextTooLongError`), compactage d'urgence (résultats d'outils tronqués dans la demande de résumé), reprise de la demande une fois.
35.**Barre de contexte pendant le travail de l'agent** : taille recomptée à chaque itération (après la demande, chaque réponse du LLM et chaque série de résultats d'outils) et barre affichée pendant les itérations, plus seulement à la saisie.
36.**Barre de contexte toujours affichée** : en bas de l'écran en permanence (saisie, itérations, outils, confirmations, listes) ; si la taille maximale du contexte est inconnue, seul le nombre de tokens utilisés est montré.
37.**Zone de saisie toujours visible** : grisée pendant le travail de l'agent (« Traitement en cours — Ctrl+C : interrompre »), et c'est elle qui porte les demandes d'autorisation (`ASK … (o/n)`), dans le même cadre en pointillés.
38.**Ligne d'état réservée** au-dessus de la zone de saisie (« En réflexion… », outils en cours) : toujours présente, vide hors traitement, pour que la zone de saisie ne remonte ni ne descende.
39.**Limite d'itérations** : 50 par défaut, réglable avec `MaxIterations` dans `~/.maia/settings.json` ; à la limite, MAIA demande s'il faut continuer (o/n) pour une nouvelle série ; une demande tapée après un arrêt est fusionnée dans le dernier message utilisateur (alternance respectée).
40.**Guide des permissions** : `GUIDE_PERMISSIONS.md` (syntaxe, exemples simples et complexes, interdiction d'une commande cachée dans un tube, limites).
41.**Signature dans la bannière** : « Arnaud VINCENT (avec ClaudeCode) » sous « Un seul agent, plusieurs modèles » (ligne séparée par un espace ; nom en #EEF527, ClaudeCode en #F57D27).
42.**Lancement direct d'un sous-agent** : une saisie qui commence par `&` (`&Agent_1 tâche…`) exécute le sous-agent sans passer par le LLM ; son résumé est affiché et ajouté au contexte.
43.**`/createAgent`** (ex-`/newAgent`) : création guidée d'un sous-agent (niveau projet ou global) à partir du modèle commenté `.ressources/AGENT.md` ; commentaires de fin de ligne acceptés dans l'en-tête de `AGENT.md`, commentaires HTML des sections ignorés.
44.**Règles `Redirect`** : autoriser, demander ou interdire les redirections de sortie (`>`, `>>`, `2>`, `&>`) fichier par fichier ; sans règle, elles demandent confirmation ; `2>&1` et les `>` entre guillemets ne comptent pas ; guide mis à jour (§ 8).
45.**Serveurs MCP distants** : déclaration globale `~/.maia/mcp.json` (expansion `${VAR}`, `type` http/sse, `headers`, `timeout`, `includeTools`/`excludeTools`), aucun serveur actif par défaut, activation par `Mcp(serveur)` dans le `settings.json`, `Mcp(serveur,outil)` pour retirer/confirmer un outil, `Mcp(serveur,*)` en `deny` désactive sans connexion ; connexion parallèle après traitement des permissions ; outils `mcp__serveur__outil` ; commande `/mcp` (bandeaux `OK` vert / `FAIL` rouge).
46.**Outils personnalisés utilisateur** : chargés aussi depuis `~/.maia/tools` ; à nom égal l'outil du projet (`./.maia/tools`) l'emporte, sans avertissement ; pris en compte par les sous-agents.
47.**Réponse « stop »** aux demandes d'autorisation d'outil (`o`/`n`/`s`) : l'action n'est pas exécutée, l'itération s'arrête **sans rollback** (le contexte est conservé), puis la saisie suivante est ajoutée à la conversation ; sert à donner une information oubliée ou à changer de sujet.
48.**Section `# Constraints`** des agents (facultative, après `# Missions`) : limites et conditions d'intervention de l'utilisateur ou d'un tiers ; incluse dans le prompt du sous-agent avec la consigne de s'arrêter et de le signaler dans son résumé ; modèle `.ressources/AGENT.md` et exemple `code_reviewer` mis à jour.
51.**Barre de contexte qui semblait figée** : au-delà de 100k, la valeur n'était affichée qu'au millier près (`123k`) et un carré vaut 10 000 tokens, donc plusieurs étapes passaient sans changement visible ; affichage à 100 tokens près (`123.5k`, `1.049M`) et mise à jour en direct pendant la réponse du modèle (toutes les 250 ms au plus).
52.**Touches Début / Fin** (Home / End) dans la zone de saisie : curseur au début ou à la fin du prompt (comme Ctrl+A / Ctrl+E).
53.**Maj+Entrée** dans la zone de saisie : saut de ligne, le prompt n'est pas envoyé (Alt+Entrée et Ctrl+J aussi, pour les terminaux qui n'envoient pas Maj+Entrée distinctement ; la séquence « ESC O M » envoyée par certains terminaux, qui s'affichait « OM », est aussi reconnue).
54.**Maj+Entrée avec Ink 8** : Ink 8 ne transmet plus que « Entrée » pour Maj+Entrée sur certains terminaux (le journal des touches montre `\r` + `return`, sans `shift`). Maia demande donc le protocole clavier kitty (`kittyKeyboard: { mode: "auto" }`), qui rend Maj+Entrée distinct sur les terminaux compatibles (Kitty, WezTerm, Ghostty, iTerm2, Windows Terminal récent…). Ailleurs (constaté sur le terminal de l'auteur : `\r` + `return` pour Entrée comme pour Maj+Entrée), les deux touches sont indiscernables : utiliser Alt+Entrée ou Ctrl+J, qui fonctionnent partout.
55.**Astuce Konsole (KDE)** : Konsole n'envoie pas Maj+Entrée distinctement. Pour l'obtenir : Configuration → Modifier le profil courant → Clavier → Modifier, ajouter la ligne `key Return+Shift : "\n"`, valider puis rouvrir l'onglet. Maj+Entrée envoie alors un saut de ligne brut (comme Ctrl+J), que Maia traite comme un saut de ligne. (Syntaxe non testée sur Konsole : en cas d'échec, `MAIA_DEBUG_KEYS=1` montre ce qui est reçu.)
56.**Diagnostic des touches** : `MAIA_DEBUG_KEYS=1 pnpm maia` écrit chaque touche reçue (texte + drapeaux Ink) dans `./.maia-keys.log`, pour comprendre pourquoi une touche (Maj+Entrée…) n'est pas reconnue.
57.**Barre de contexte : js-tiktoken uniquement.** L'essai avec `usage.total_tokens` du serveur a été retiré : la taille du contexte est toujours comptée localement avec js-tiktoken, la consommation du serveur ne sert qu'à détecter une réponse coupée.
58.**`/delete`** : suppression d'une ou plusieurs sessions (Espace pour cocher, Suppr pour supprimer, confirmation), sauf la session courante.
59.**`/models`** : liste des modèles disponibles auprès de LiteLLM (interrogé à chaque appel, `LiteLLM_URL` + `LiteLLM_Models`), modèle actif signalé ; aucun changement de modèle.
60.**Outil `ListModels`** : l'agent (et les sous-agents, les workflows) peut lister les modèles disponibles auprès de LiteLLM.