1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
# Référence : outils intégrés
> Description neutre des outils déclarés au modèle. Un outil ne renvoie jamais d'erreur Go pour un échec ordinaire : le texte de l'échec est renvoyé au modèle comme sortie de l'outil. Chaque outil enregistre son appel dans le détecteur de boucle ; le même outil, la même entrée et la même sortie trois fois de suite ajoutent `[LOOP_DETECTED: …]` à la sortie.
> **Permissions.** « Permission demandée » ci-dessous s'applique quand `yolo: false` (voir la [configuration](configuration.md)), dans toutes les façades : l'invite `🔐` du mode ligne, la zone de la fenêtre, le dialogue de l'éditeur. Avec `yolo: true` (la valeur par défaut), les mêmes appels s'exécutent sans demander. Avec `-p` et `mm --ask`, un appel qui demanderait est refusé. Un refus renvoie `[<outil> rejected by the user]` (`[command rejected by the user]` pour `bash`) au modèle.
## `bash`
Déclaré quand `bashTool: true` (la valeur par défaut).
| Champ | Type | Description |
|-------|------|-------------|
| `command` | chaîne | La commande shell à exécuter. |
| Aspect | Comportement |
|--------|--------------|
| Shell | `bash -c <command>`, un nouveau shell par appel, pas de stdin. |
| Répertoire de travail | Le répertoire du processus en mode terminal ; le `cwd` de la session en mode ACP. |
| Délai | 30 s ; la sortie se termine alors par `[error: 30s timeout exceeded]`. |
| Annulation | La commande appartient au tour : annuler le tour (`Échap`, `Ctrl-C`, `/abort`, ou poser une autre question) la tue, et la sortie se termine par `[error: cancelled]`. |
| Tâches de fond | La sortie est collectée dans un fichier temporaire, pas dans un tube : l'outil rend la main dès que le shell se termine, même si la commande a laissé une tâche en cours (`nohup serve &`). La tâche continue de tourner — c'est en général le but — et écrit dans un fichier que personne ne lit. Par un tube, `os/exec` attend *tous* les processus qui détiennent l'extrémité d'écriture, et une telle commande bloquait le tour pour toujours. |
| Sortie très volumineuse | Relue par ses deux extrémités (1 Mio chacune) avant la troncature à `maxOutput`, avec `[... output too large to read back in full ...]` entre les deux. |
| Sortie | stdout et stderr fusionnés, tronqués à `maxOutput` caractères. |
| Code de sortie non nul | La sortie se termine par `[exit code: exit status N]`. |
| Affichage terminal | `🛠️ bash: <command>` avant exécution (puis l'invite `🔐` quand la permission est demandée), puis jusqu'à `previewLines` lignes grisées et indentées, ou `(no output)`. |
| ACP | Appel d'outil de genre `execute` ; permission demandée avant exécution ; un refus renvoie `[command rejected by the user]` avec le statut `failed`. |
| Mode fenêtre | Une ligne `‣ bash <commande>` avec statut, durée et sortie repliée. Permission demandée : attend `a`/`s`/`r`/`n` ; un refus renvoie le même texte qu'en ACP. |
## `read_skill`
Déclaré seulement quand `skillsDir` contient au moins un skill (`<nom>.md` ou `<nom>/SKILL.md`).
| Champ | Type | Description |
|-------|------|-------------|
| `name` | chaîne | Nom du skill, exactement tel que listé dans la description de l'outil. |
| Aspect | Comportement |
|--------|--------------|
| Description | Phrase fixe suivie du catalogue : une ligne ` <name> — <description>` par skill, triées par nom. |
| Sortie | Le markdown complet de `<skillsDir>/<name>.md`, ou de `<skillsDir>/<name>/SKILL.md` s'il n'y a pas de fichier à plat, tronqué à `maxOutput`. |
| Nom inconnu | `No skill named "x". Available skills: a, b, c` |
| Sécurité des chemins | Seul le nom de base de `name` est utilisé ; pas de traversée de répertoire. |
| Affichage terminal | `📖 read_skill: <name>` ; le contenu n'est pas affiché. |
| ACP | Appel d'outil de genre `read` ; pas de dialogue de permission. |
## `read_file`
Déclaré quand `editTools: true`.
| Champ | Type | Défaut | Description |
|-------|------|--------|-------------|
| `path` | chaîne | — | Chemin du fichier, relatif au répertoire de travail. |
| `start` | entier | `0` | Première ligne (base 1) ; `0` = depuis le début. |
| `end` | entier | `0` | Dernière ligne (base 1, incluse) ; `0` = jusqu'à la fin. |
| `numbered` | booléen | `false` | Préfixe chaque ligne de son numéro. |
| Aspect | Comportement |
|--------|--------------|
| Fichier absent | `<path> does not exist. Create it with write_file` |
| Sortie | Les lignes, tronquées à `maxOutput` ; le saut de ligne final n'est conservé que si la fin du fichier est affichée. |
| ACP | Genre `read`, avec le chemin absolu comme emplacement. |
## `write_file`
Déclaré quand `editTools: true`.
| Champ | Type | Description |
|-------|------|-------------|
| `path` | chaîne | Chemin du fichier, relatif au répertoire de travail ; les répertoires parents sont créés. |
| `content` | chaîne | Le contenu entier du fichier. |
| Aspect | Comportement |
|--------|--------------|
| Fichier existant | Entièrement remplacé. |
| Contenu identique | Rien n'est écrit ; le résultat le dit. |
| Sortie | Un en-tête d'une ligne et le diff par rapport au contenu précédent. |
| ACP | Genre `edit` ; permission demandée ; le changement voyage comme un diff (diff de nouveau fichier à la création). |
## `edit_file`
Déclaré quand `editTools: true`.
| Champ | Type | Défaut | Description |
|-------|------|--------|-------------|
| `path` | chaîne | — | Chemin d'un fichier existant. |
| `edits` | tableau de `{old, new}` | — | Remplacements, tous résolus par rapport au fichier original. |
| `edits[].old` | chaîne | — | Texte exact à trouver, présent exactement une fois. Les espaces sont pardonnés — voir *Comment `old` est mis en correspondance*. |
| `edits[].new` | chaîne | — | Remplacement ; vide supprime le texte trouvé. |
| `dry_run` | booléen | `false` | Rapporte ce qui changerait, n'écrit rien. |
| Règle | Conséquence si violée |
|-------|-----------------------|
| `old` apparaît exactement une fois | Refusé : introuvable, ou ambigu — `old text appears N times (lines 11, 20) — to change ONE of them, include its surrounding lines in old until the text is unique; to change several, send one edit per place…`. |
| Deux éditions ne doivent ni se chevaucher, ni s'imbriquer, ni se répéter | Refus. |
| Les éditions sont résolues sur le texte original, jamais en cascade | — |
| Toute édition refusée | Rien n'est écrit. |
| Aspect | Comportement |
|--------|--------------|
| Sortie | Un en-tête d'une ligne (éditions appliquées, lignes ajoutées et supprimées, première ligne modifiée, et ` · N matched ignoring whitespace` quand une édition a eu besoin d'une correspondance tolérante) et le diff avec numéros de ligne. |
| ACP | Genre `edit` ; permission demandée ; le changement voyage comme un diff avec ancien et nouveau texte. |
### Comment `old` est mis en correspondance
Trois passes, de la plus stricte à la plus tolérante ; **la première passe qui trouve quoi que ce soit gagne**, donc un fichier qui contient le texte à l'octet près n'est jamais mis en correspondance autrement.
| Passe | Ce qu'elle pardonne | Signalé par |
|---|---|---|
| 1. Exacte | Rien. `old` est cherché octet pour octet. | — |
| 2. Bords | Les espaces *autour* de `old` : une indentation que le modèle a conservée sur une ligne seule, un saut de ligne en tête ou en queue. Le même rembourrage est retiré de `new` quand `new` le porte, donc un `new` qui ajoute volontairement une ligne vide l'ajoute toujours. | `matched ignoring whitespace` |
| 3. Indentation | Les espaces de début et de fin de chaque ligne, pour un `old` d'au moins deux lignes. C'est le cas tabulations contre espaces : un modèle retape un bloc Go avec quatre espaces là où le fichier a une tabulation. | `matched ignoring whitespace` |
Deux choses ne sont **pas** assouplies :
- **L'unicité.** Une correspondance tolérante doit rester la seule. Deux d'entre elles sont refusées exactement comme deux exactes, avec le même message et les mêmes numéros de ligne — c'est cette règle qui rend une édition vérifiable.
- **L'indentation du fichier.** Avant écriture, `new` est ré-indenté avec l'indentation de base et l'unité du fichier, pour qu'un fichier indenté aux tabulations ne se retrouve pas avec un bloc d'espaces au milieu. Une ligne vide reste vide au lieu de devenir une ligne d'espaces.
L'en-tête se termine alors par ` · N matched ignoring whitespace`. C'est signalé plutôt que caché : un modèle à qui l'on dit que son texte ne correspondait pas exactement écrit mieux le suivant.
Quand rien ne correspond, le refus montre la région du fichier qui s'en approche le plus, avec les vrais numéros de ligne — la moitié des lignes doit coïncider avant qu'un extrait soit proposé, car désigner une partie innocente du fichier est pire que de ne rien dire :
```
main.go: edit #1: old text not found — it must match the file exactly, including indentation and line breaks:
│ func main() {
│ count := 2
│ print(count)
│ }
the closest the file comes to it — copy these lines:
│ 3 │ func main() {
│ 4 │ count := 1
│ 5 │ print(count)
│ 6 │ }
```
## `lsp`
Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur le `PATH` au démarrage. Un serveur listé mais introuvable est ignoré avec un `[warning: lsp server <nom> (<commande>) not found on the PATH — skipped]` ; quand aucun n'est trouvé, l'outil n'est pas déclaré et la bannière le dit.
| Champ | Type | Défaut | Description |
|-------|------|--------|-------------|
| `action` | chaîne | — | `definition`, `references`, `hover`, `symbols`, `diagnostics` ou `rename`. |
| `path` | chaîne | — | Chemin du fichier, relatif au répertoire de travail. Son extension choisit le serveur. |
| `symbol` | chaîne | `""` | L'identifiant, tel qu'écrit dans le code. Guillemets et ponctuation parasites sont coupés, un préfixe `receveur.` ou `pkg.` est retiré (`Greet', ` et `person.Greet` désignent tous deux `Greet`). Ses occurrences dans le fichier sont essayées dans l'ordre — lignes de code d'abord, lignes de commentaire en dernier — jusqu'à ce que le serveur réponde. Pour `definition`, `references`, `hover`, `rename`. |
| `line` | entier | `0` | Ligne en base 1. Avec `symbol` : restreint la recherche à cette ligne. Avec `column` : la position exacte. |
| `column` | entier | `0` | Colonne en base 1, en caractères, à l'intérieur de l'identifiant. Nécessaire seulement sans `symbol`. |
| `new_name` | chaîne | `""` | Le nouvel identifiant, pour `rename`. Nettoyé comme `symbol`. Quand il est vide et que `symbol` contient plusieurs mots (`Greet 1 12 Hello` — un petit modèle qui écrit symbole, ligne, colonne et nouveau nom dans une seule chaîne), le premier identifiant est le symbole et le dernier le nouveau nom. |
| Aspect | Comportement |
|--------|--------------|
| Description | Nomme les serveurs et leurs extensions (`Servers available: gopls (.go), …`) et les six actions. |
| `definition`, `references` | Une ligne `chemin:ligne:colonne <ligne source>` par emplacement, triée par chemin puis position, doublons retirés ; les chemins du projet sont relatifs, les autres absolus. `references` est préfixé de `N reference(s):` et inclut la déclaration. |
| `hover` | Le texte du serveur tel qu'envoyé (signature, type, documentation ; Markdown pour la plupart des serveurs). |
| `symbols` | Une ligne `<sorte> <nom> [<détail>] line N` par symbole, enfants indentés de deux espaces. |
| `diagnostics` | Le fichier est synchronisé, puis l'appel attend jusqu'à 3 s la publication du serveur. `N diagnostic(s) in <chemin>:` puis une ligne `<sévérité> chemin:ligne:colonne <message> [<source>]` par constat ; `no diagnostics: <chemin> is clean` ; ` (as last published)` quand le fichier n'a pas changé depuis la dernière publication ; `no diagnostics received for <chemin> within 3s (…)` quand le serveur n'a jamais publié. |
| `rename` | Applique les modifications du serveur aux fichiers sur disque (positions UTF-16 converties), informe le serveur du nouveau contenu, et répond `renamed to <nouveau>: N edit(s) in M file(s)` suivi d'une ligne `chemin: N edit(s)` par fichier. Sans `new_name` : `[lsp: rename needs new_name — call lsp again with {"action": "rename", "path": …, "symbol": …, "new_name": "<the new identifier>"}; do not use edit_file for a rename]`. |
| Positions | Lignes et colonnes en base 1 à la frontière de l'outil, UTF-16 en base 0 sur le fil. Une position au-delà de la fin d'une ligne ou du fichier est refusée, avec le compte. |
| Synchronisation | Avant chaque requête, le fichier est relu sur disque et envoyé en `didOpen` (première fois) ou en `didChange` complet (quand son contenu a bougé) : les modifications faites avec `edit_file` sont vues sans étape supplémentaire. |
| Cycle de vie du serveur | Démarré au premier appel pour l'une de ses extensions, dans la racine du projet (le répertoire où `mm` tourne), gardé pour la session, arrêté (`shutdown`, `exit`, puis le processus) à la sortie de `mm`. Un serveur mort est relancé à l'appel suivant. |
| Échecs | Toujours du texte, jamais une erreur Go : `[lsp: no language server for "x.md" — configured: gopls (.go)]`, `[lsp: "foo" not found in main.go]`, `[lsp: column 40 is past the end of line 3 (15 characters)]`, `[lsp: gopls: hover failed: …]`, `[lsp: no definition found for the symbol at main.go:5:4]`. |
| Affichage terminal | `🔎 lsp: definition main.go greet` (ou `…:9:14` avec une position, `rename main.go greet → hello`), puis la réponse affichée comme la sortie d'une commande. |
| ACP / fenêtre | Sorte `read` pour la navigation, sans permission ; `rename` est de sorte `edit` et demande la permission comme `edit_file` (`[rename rejected by the user]` en cas de refus). |
| Compteur | `🔎 N lsp call(s)` dans la ligne de récapitulatif (voir plus bas). |
## `fetch_content`
Déclaré seulement avec `fetchContentTool: true`. Télécharge une page web et la renvoie en Markdown, ou enregistre ce Markdown dans un fichier. Paquet `internal/webmd`.
| Champ | Type | Défaut | Description |
|-------|------|--------|-------------|
| `url` | chaîne | — | La page. Sans schéma, `https://` est ajouté (`k33g.org`) ; un schéma tapé sans ses deux-points est réparé (`https//k33g.org`). Seuls `http` et `https`. |
| `output` | chaîne | `""` | Fichier où enregistrer le Markdown, relatif au répertoire de travail (le `cwd` de la session en ACP) ; les répertoires parents sont créés. Renseigné, le Markdown n'est pas renvoyé. |
| `baseUrl` | chaîne | URL de la page | URL absolue qui sert à résoudre les liens et images relatifs. Par défaut : l'URL finale de la page, après redirections. |
| `tables` | chaîne | `html` | Tableaux avec `rowspan`/`colspan` ou un tableau imbriqué : `html` les garde en HTML brut, `flatten` produit un tableau GFM en recopiant une cellule fusionnée dans chaque case qu'elle couvre. |
| `strikethrough` | booléen | `false` | `<del>`, `<s>`, `<strike>` → `~~texte~~` ; sinon le texte seul. |
| `taskList` | booléen | `false` | Un élément de liste qui commence par une case à cocher → `- [ ] …` / `- [x] …` ; sinon la case est retirée. |
| Aspect | Comportement |
|--------|--------------|
| Requête | `GET` avec `User-Agent: mm/<version>` ; 10 redirections au plus ; 30 s pour toute la récupération ; pages de plus de 5 Mio refusées. |
| robots.txt | Lu (et gardé 24 h par origine) avant la page et avant chaque cible de redirection, pour le jeton `mm`, sinon le groupe `*`. `Allow`/`Disallow`, la correspondance la plus longue l'emporte, `Allow` gagne en cas d'égalité, `*` et `$` final pris en charge (RFC 9309). Un robots.txt en 4xx autorise tout ; en 5xx ou injoignable, il interdit tout. |
| Types acceptés | `text/html`, `application/xhtml+xml` ; sans `Content-Type`, le type est deviné. |
| Encodage | Dans l'ordre : marque d'ordre des octets, `charset` de l'en-tête `Content-Type`, `<meta charset>` ou `<meta http-equiv="Content-Type">` dans les 4 premiers Kio, UTF-8 si les octets sont de l'UTF-8 valide, sinon `windows-1252`. Un nom inconnu est ignoré. Le contenu est converti en UTF-8. |
| Retiré | `script`, `style`, `noscript`, `template`, `meta`, `link`, `base`, `title`, `svg`, `canvas`, `iframe`, `object`, `embed`, `nav`, `aside`, `dialog`, `button`, `select`, `textarea`, les champs autres que les cases à cocher, un `footer` hors de `article`/`main`, les commentaires HTML, les rôles ARIA `navigation` `banner` `complementary` `search` `contentinfo` `dialog`, `aria-hidden="true"`, `hidden`, les mots de class/id `ad` `ads` `advert` `sponsor` `promo` `cookie` `consent` `gdpr` `newsletter` `subscribe` `popup` `modal` `share` `social` `breadcrumb` `nav` `navbar` `navigation` `menu` `sidebar` `skiplink` (et quelques variantes), les classes `sr-only` `visually-hidden` `screen-reader-text`. Un élément repéré par class/id est gardé s'il contient `main`, `article` ou `h1`. |
| Attributs | Tous retirés sauf `a[href]`, `img[src,alt]`, `td/th[colspan,rowspan]`, `ol[start]`, `input[type,checked]`, et les mots `language-*`/`lang-*` de la classe d'un `pre`/`code`. |
| Conversion | `h1`–`h6` → `#`–`######` ; `p` → paragraphe, ligne vide entre les blocs ; `strong`/`b` → `**…**` ; `em`/`i` → `*…*` ; `code`/`kbd`/`samp`/`tt` en ligne → accents graves ; `pre` → bloc clôturé (langage tiré de `language-x`), clôture plus longue que toute suite d'accents graves du code ; `br` → saut de ligne forcé `\` ; `hr` → `---` ; `blockquote` → `> ` ; `a` → `[texte](url)`, l'URL faute de texte, le texte seul pour `javascript:` ; `img` → ``, `` sans alt, l'alt seul pour une URI `data:` ; `ul`/`ol` → `- ` / `N. ` (depuis `start`), listes imbriquées indentées de la largeur du marqueur parent ; tableaux simples → GFM, première ligne comme en-tête, `|` échappé, `<br>` pour un saut de ligne dans une cellule, `<caption>` en paragraphe au-dessus. |
| Élément inconnu | Balises retirées, texte conservé ; s'il contient un bloc, il est traité comme un conteneur. |
| Élément vide | Rien, pas de ligne vide. |
| HTML invalide | Réparé par le parseur HTML5 (`golang.org/x/net/html`), puis converti. |
| Échappement | La barre oblique inverse, `*`, l'accent grave, `[`, `]`, `<` et un `_` hors d'un mot reçoivent une barre oblique inverse ; de même une ligne qui commence par `#`, `>`, `-`, `+`, `=`, `\|` ou `N.`. |
| Résultat | Le Markdown, terminé par un unique retour à la ligne ; ni titre, ni front matter, ni préambule. Tronqué à `maxOutput` caractères. |
| Avec `output` | `[fetch_content: saved N lines of Markdown (B bytes) from <url> to <fichier> — the content is not shown]`. |
| Page vide une fois nettoyée | `[fetch_content: <url> has no text content once scripts, navigation and ads are removed — nothing saved]` ; aucun fichier écrit. |
| Échec | `[fetch_content: error N (<classe>) — <raison>]`, voir les codes ci-dessous. |
| Affichage terminal | `🌐 fetch_content: <url>` (`→ <fichier>` avec `output`), indicateur `Fetching`, puis le résultat affiché comme la sortie d'une commande (seulement la ligne de confirmation avec `output`). |
| ACP / fenêtre | Genre `fetch`. Pas de permission sans `output` ; avec `output`, permission demandée comme pour `write_file` (`[fetch_content rejected by the user]` en cas de refus), et le chemin absolu du fichier comme emplacement. |
| Code | Classe | Cause |
|------|--------|-------|
| `1` | `conversion` | Pas une page HTML, plus de 5 Mio, impossible à décoder, ou fichier de sortie impossible à écrire. |
| `2` | `input` | Hôte injoignable, délai dépassé, statut HTTP ≥ 300 après redirections, plus de 10 redirections, page interdite par le robots.txt ou robots.txt illisible. |
| `3` | `usage` | `url` vide ou illisible, schéma autre que `http`/`https`, valeur de `tables` inconnue, `baseUrl` non absolue. |
## `ask_agent`
Déclaré quand `mmConfigs` nomme au moins un agent. Confie une tâche à l'un d'eux et renvoie sa réponse comme résultat de l'outil : c'est le modèle qui décide quand, d'après les descriptions des agents. Même mécanique que `/ask` (paquet `internal/ask`) : l'agent tourne comme un `mm -acp` enfant, démarré au premier appel puis réutilisé, avec une session neuve à chaque appel.
| Champ | Type | Défaut | Description |
|-------|------|--------|-------------|
| `agent` | chaîne | — | Un nom de `mmConfigs`. |
| `prompt` | chaîne | — | La tâche, complète et autonome. |
| `with_context` | booléen | `false` | Envoie cette conversation avant le prompt : l'historique au début du tour (questions, réponses, une ligne par appel d'outil, jamais les sorties d'outils), limité à `maxOutput` caractères, les plus récents gardés. |
| Aspect | Comportement |
|--------|--------------|
| Description | Un paragraphe fixe, puis `Agents:` et une ligne ` <nom> — <rôle>` par agent, triés par nom. `<rôle>` est la `description` de l'entrée, sinon l'`acp.title` du fichier de l'agent (s'il n'est pas celui intégré), sinon `(no description)`. |
| Permission | L'appel lui-même est soumis comme `bash` : demandé quand `yolo: false`. |
| Appels de l'agent interrogé | Décidés par le `yolo` de son entrée dans `mmConfigs` s'il est fixé (`true` exécutés, `false` demandés), sinon par le `yolo` de cet agent. |
| Répertoire de travail | Le répertoire du processus dans le terminal et la fenêtre ; le `cwd` de la session en ACP. |
| Affichage | Le texte, les pensées et les appels d'outils de l'agent interrogé défilent dans le tour, ses appels titrés `<agent> · <titre>`. La fenêtre le nomme pendant qu'il travaille : en-tête `mm [<acp.title>] · <modèle> @ <fournisseur> · asked by <cet agent>`, sa réponse sous `‣ mm [<acp.title>]` (son nom dans `mmConfigs` s'il n'a pas de titre). Mode ligne : `🗣 ask_agent: <agent> (<fournisseur>/<modèle>) — <première ligne du prompt>`, puis la sortie de l'agent. |
| Chien de garde | Chaque événement de l'agent interrogé (texte, pensée, appel d'outil, permission répondue) compte comme un signe de vie pour le chien de garde de cet agent. Un silence plus long que `watchdogTimeout` — un modèle qui se charge, un long prompt en lecture — termine quand même le tour. |
| Résultat | La réponse, tronquée à `maxOutput`. La sortie de l'appel lui-même dans la fenêtre et l'éditeur est `<agent> answered (N characters)` : la réponse a déjà défilé. Pas ajoutée à l'historique comme un échange `/ask` : c'est un résultat d'outil. |
| Hooks | Les hooks `preAsk` de cet agent s'exécutent après la permission, avant que l'agent soit interrogé ; ses hooks `postAsk` s'exécutent sur la réponse avant que le modèle la lise. Un relais refusé renvoie `[ask denied by a hook: <raison>]` ; une réponse remplacée donne à l'appel la sortie `<agent> answered; a hook replaced the answer (N characters)` ; le contexte d'un hook suit la réponse après `[context added by a hook]`. Voir [hooks](hooks.md#preask). |
| Erreurs | `[ask_agent: unknown agent "x" — agents: a, b]` ; `[ask_agent: the prompt is empty — say what <agent> must do]` ; `[ask_agent: <agent> failed: <raison>]` ; `[ask_agent: <agent> gave no answer]` ; une exécution annulée ou interrompue renvoie ce qui a été répondu, puis `[ask_agent: <agent> stopped before finishing: <raison>]`. |
| Profondeur | Un agent interrogé peut lui-même avoir des `mmConfigs` et appeler `ask_agent`, jusqu'à deux niveaux (`MM_ASK_DEPTH`) ; au-delà, l'appel échoue avec le message de profondeur. |
## `ask_user`
Déclaré sauf avec `askUserTool: false` (actif par défaut), et jamais à côté d'un `structuredOutput`. Pose une à quatre questions à l'utilisateur et renvoie les réponses. Non soumis à `yolo` : poser une question ne modifie rien.
| Champ | Type | Défaut | Description |
|-------|------|--------|-------------|
| `questions` | tableau d'objets | — | 1 à 4 questions, posées ensemble. Une question seule envoyée sans la liste (ses champs au premier niveau) est acceptée. |
| `questions[].question` | chaîne | — | La question. |
| `questions[].type` | chaîne | `choice` quand `options` est donné, `text` sinon | `choice` (une option), `multi` (un nombre quelconque d'options), `text` (réponse libre), `confirm` (oui ou non). Synonymes acceptés : `single`, `select`, `radio`, `enum`, `one` → `choice` ; `multiple`, `multiselect`, `checkbox`, `checkboxes`, `many` → `multi` ; `input`, `string`, `free`, `freetext` → `text` ; `yesno`, `yes_no`, `boolean`, `bool`, `confirmation` → `confirm`. |
| `questions[].options` | tableau de chaînes | — | Pour `choice` et `multi` : au moins deux, espaces retirés, doublons (sans tenir compte de la casse) supprimés. Ignoré pour `text` et `confirm`. |
| `questions[].default` | chaîne | aucun | Gardé seulement s'il a un sens : une des options (`choice`), un oui ou un non en anglais ou en français (`confirm`), un texte quelconque (`text`). Aucun pour `multi`. |
Le schéma déclaré décrit les champs d'une question en mots, sans `properties` : llama.cpp les imposerait dans l'ordre alphabétique (voir [l'explication](../explanation/asking-the-user.md)).
| Aspect | Comportement |
|--------|--------------|
| Mode ligne | `❓ ask_user: N question(s)`, puis chaque question à son tour : `❓ <question>`, une ligne ` N. <option>` par option (`(default)` après le défaut), une aide, et ` ? `. Une ligne tapée y répond. `choice` : un numéro, le texte d'une option, le début d'une seule option, ou tout autre texte (pris comme réponse). `multi` : des numéros séparés par des virgules, espaces ou points-virgules, tous valides. `confirm` : `y`/`yes`/`o`/`oui`/`true`/`1` ou `n`/`no`/`non`/`false`/`0`. `text` : le texte. Une ligne vide prend le défaut, sinon la question est reposée ; une ligne qui ne répond pas réaffiche l'aide. `/skip` refuse ; les autres commandes gardent leur sens (`/abort`, `/quit` arrêtent le tour et annulent la question). |
| Fenêtre (`-tui`) | La question remplace la boîte de saisie, suivie de `(i/N)` quand il y en a plusieurs ; les options sont dessinées au bas de la conversation, celle en surbrillance en vidéo inverse avec `❯`. `choice` : ↑↓ (en boucle) et Entrée, ou un chiffre de 1 à 9. `multi` : ↑↓, Espace coche (`[x]`), Entrée envoie quand au moins une option est cochée. `confirm` : `y`/`o` ou `n`, ou ←→↑↓ et Entrée. `text` : saisi dans la boîte (pré-remplie avec le défaut), Retour arrière, Entrée. Échap refuse tout le formulaire. L'appel d'outil affiche `… waiting for your answer`. |
| Éditeur (ACP) | Le client a annoncé `clientCapabilities.elicitation.form` : une requête `elicitation/create` par appel (voir [ACP](acp.md#requêtes-elicitationcreate-envoyées)). Sinon, quand chaque question est un `choice` ou un `confirm` : un dialogue `session/request_permission` par question, dont les boutons sont les options (`Yes`/`No` pour un `confirm`) et `Decline`. Sinon : personne ne peut être interrogé. |
| Agent interrogé (`/ask`, `ask_agent`) | Ses questions remontent à l'utilisateur de l'agent qui l'interroge, par l'interface de ce dernier, chacune préfixée `<agent> asks: `. Avec `mm --ask`, personne ne peut être interrogé. |
| Exécution unique (`-p`) | Personne ne peut être interrogé. |
| Chien de garde | Tant qu'une question attend, le chien de garde de la génération est relancé chaque seconde : attendre un humain ne termine pas le tour. Un tour arrêté annule la question. |
| Résultat | Répondu : `The user answered:` puis une ligne `- <question> → <réponse>` par question (plusieurs options jointes par `, `). Refusé : `[the user declined to answer: do not ask the same thing again; go on without it or stop]`. Annulé : `[the question was cancelled before the user answered]`. Personne à interroger : `[the user cannot be asked in this mode: make a reasonable assumption, say which, and go on]`. Tout ce qui n'est pas une réponse termine l'appel en échec dans la fenêtre et l'éditeur. |
| Erreurs | `[ask_user: no question — send questions: [{question, type, options}]]` ; `[ask_user: N questions — ask at most 4 at a time]` ; `[ask_user: question N has no text]` ; `[ask_user: question N is a choice question — give it at least two options]` (ou `multi`) ; `[ask_user: question N has type "x" — use choice, multi, text or confirm]`. |
## Détection de boucle
Chaque outil enregistre `(outil, entrée, sortie)` dans le détecteur de boucle après chaque appel.
| Situation | Comportement |
|-----------|--------------|
| Le même triplet 3 fois de suite | La sortie de l'outil reçoit `[LOOP_DETECTED: You have attempted the same action multiple times with the same result. Please try a different approach or provide more information.]` en annexe. Rien ne s'arrête. |
| 3 appels identiques de plus (6 de suite) | L'outil renvoie une erreur au lieu de texte et le tour se termine : `[error: the model repeated the same edit_file call 6 times in a row with the same failing result — turn stopped by the loop detector; rephrase the request, or fix what the tool kept reporting]`. L'historique garde tout ce qui a tourné ; l'erreur est enregistrée dans le tableau `errors` du fichier de session (`where: generation`). |
| Un appel différent | Le compte repart. `/new` réinitialise le détecteur ; `/compact` non. |
| Un appel d'outil dont les arguments diffusés répètent un motif (les 256 derniers caractères sont un motif de 4 à 64 caractères répété) ou dépassent 256 Ko | La génération est arrêtée aussitôt : `[error: the model's tool call ran away: its arguments repeat the same pattern (N characters so far: …) — generation stopped; rephrase the request, or try another model]`. Sans cela, un modèle qui déraille compose du charabia pendant des minutes pendant que l'écran affiche « Thinking » (le chien de garde est nourri par chaque fragment). Pendant qu'un appel d'outil est diffusé, le spinner dit `Composing a tool call`. |
Les seuils (3, puis 3 de plus) sont fixés dans `main.go` ; `maxTurns` reste la borne extérieure d'un tour.
## Compteurs affichés après un tour
| Compteur | Compte |
|----------|--------|
| `⚙ N command(s)` | Réponses de l'outil `bash` dans le tour. |
| `📝 N file op(s)` | Réponses de `read_file`, `write_file`, `edit_file`. |
| `📖 N skill(s)` | Réponses de `read_skill`. |
| `🔎 N lsp call(s)` | Réponses de `lsp`. |
|