La Passerelle : piloter l'Atelier depuis une messagerie - #32
Merged
Conversation
Le streaming input du SDK était déjà en place — `agent/queue.ts` est exactement ce mécanisme, et `POST /send` le nourrit. Ce qui manquait n'était pas le tuyau mais la porte. Les channels MCP, qui semblaient la réponse, n'ont pas pu la fournir : `Options` du SDK n'expose aucun champ `channels`, `createSdkMcpServer` n'accepte pas de capability `experimental` — donc le serveur in-process d'`agent/ask.ts` ne peut pas en être un — et un channel ne livre que dans une session déjà ouverte, jamais pour en ouvrir une. D'où cette forme : la Passerelle appelle le registre directement, dans le même process. Son long-polling est sortant, si bien qu'aucun port ne s'ouvre — l'écoute reste `127.0.0.1` et `guard.ts` ne bouge pas d'une ligne. Ce que cela coûte, et qui est assumé : un secret entre dans AURA, elle appelle un service externe, et qui écrit dans une conversation autorisée obtient un accès distant au poste. La liste blanche est la garde, pas une commodité — sans elle, la Passerelle refuse de démarrer. Une session pilotée d'ici reste abonnée au runner : c'est l'abonnement, et lui seul, qui la protège du balayeur des trente minutes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La fonctionnalité était livrée sans sa page. Le manuel est une surface à part entière — il alimente le tiroir contextuel, la page /aide et le site vitrine —, et une capacité qui ouvre un accès distant est précisément celle qu'on ne peut pas laisser sans mode d'emploi. Une page transversale (`routes: []`) plutôt qu'une section de l'Atelier : la Passerelle n'a pas d'écran, et ce qu'elle demande de comprendre — ce qu'elle coûte, ce qu'elle n'ouvre pas — ne se lit pas en marge d'autre chose. La barre latérale du site et l'ordre du manuel s'en déduisent seuls, il n'y avait rien à inscrire ailleurs. Trois documents affirmaient par ailleurs quelque chose qui vient de devenir faux, et un document de sécurité qui ment est pire que muet : - README — « aucun appel sortant », « aucun secret à fournir », et deux variables de configuration là où il y en a désormais cinq ; - SECURITY — « ni compte, ni mot de passe, ni service externe », qui décrivait la seule frontière du modèle. Chacun dit maintenant ce que la Passerelle change, ce qu'elle ne change pas, et que tout reste éteint tant qu'aucun jeton n'est posé. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Elle parlait à Telegram par `fetch` : deux cents lignes de long-polling, d'acquittement et de reprises, qui marchaient. Ce n'est pas pour elles qu'on prend la dépendance, mais pour ses **types**. L'API Bot accepte les champs qu'elle ne connaît pas et les ignore en silence. Un `header` écrit pour `is_header` ne produit donc aucune erreur — seulement un tableau sans en-tête, sans rien pour dire pourquoi. C'est exactement l'erreur qui a été commise, et que rien à l'exécution n'aurait signalée. Le compilateur, lui, la refuse. Version 2.0.0, réécriture complète, **zéro dépendance transitive**. Ses types sont pourtant faux sur deux points de l'API riche, et il a fallu les corriger localement (voir `passerelle/riche.ts`) : son `RichText` n'admet ni chaîne nue ni tableau — ce qui rend inexprimable un paragraphe de texte simple —, et `align`/`valign` y sont requis alors qu'une cellule sans eux est acceptée. Le reste est juste, et c'est le reste qui comptait. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Telegram n'affiche pas le Markdown, et son HTML n'a ni titre, ni liste, **ni tableau** — `<table>` y est refusé net. Un tableau rendu en chasse fixe se disloque dès qu'il dépasse la largeur d'un téléphone : les colonnes passent à la ligne et l'alignement, sa seule raison d'être, disparaît. `sendRichMessage` est une autre API, arrivée en Bot API 10.1 : des blocs structurés en JSON plutôt qu'un balisage. De vrais tableaux, avec bordures et ligne d'en-tête, de vrais titres, de vraies listes — et une borne de 32 768 caractères au lieu de 4 096, soit huit fois moins de pages à tourner. `riche.ts` traduit vers ces blocs ; `markdown.ts` reste le repli, pour le jour où l'API riche refuserait un document. Les deux sont mesurés contre l'API réelle, et les pièges relevés au passage y sont écrits — un tableau porte `cells` à la racine et non `rows`, un titre exige un `size` numérique, une liste porte `items[].blocks`. Une case à cocher, elle, est écrite dans le texte (`☑︎`, `☐︎`). Le format prévoit `has_checkbox`, mais aucun client ne le dessine, et `sendChecklist` — la checklist native — exige un compte Business (`PREMIUM_ACCOUNT_REQUIRED`). Sans ce préfixe, une liste de tâches perdrait l'état de chaque ligne sans laisser de trace. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Trois commandes de consultation — `/projets`, `/projet`, `/voir` — et une navigation par boutons dans un message qui **se réécrit** : une conversation n'a pas de bouton précédent, et empiler une liste par clic laisserait derrière soi une file d'états morts. Ce qui est montré est l'**arborescence du projet**, pas un classement inventé : les « catégories » de la page Projet *sont* les dossiers de `.claude`. Un dossier qui n'en contient qu'un autre est fondu avec lui — `rules/back/application` s'ouvre d'un clic au lieu de trois écrans qui ne posent aucune question. Aucune garde n'est élargie : les trois lecteurs de `projects.ts` sont appelés tels quels, et chacun garde son bac à sable. C'est l'origine de l'entrée qui décide lequel, jamais la forme du chemin — les confondre faisait refuser un document de dossier inclus parfaitement légitime. **`/atelier` n'ouvre plus que sur un projet connu.** Un chemin quelconque de la machine ne tombe sur rien : il n'y a pas de règle à contourner, seulement une liste dans laquelle figurer. C'est délibérément plus strict que l'Atelier à l'écran, où l'on voit ce qu'on choisit. Deux détails de mise en page qui ne se devinent pas, et qui sont mesurés plutôt que supposés : - une rangée se remplit tant que les libellés tiennent, trois au plus — au-delà, un bouton fait moins d'un tiers d'écran et devient une cible qu'on manque ; - **le clavier prend la largeur de la bulle, donc du texte.** Un en-tête de vingt caractères donnait des boutons de soixante pixels. Le remplissage invisible qui corrige cela prend sa propre ligne : collé au texte, il le poussait au-delà du bord d'un téléphone et coupait la dernière phrase en deux. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Il décrivait une liste numérotée à plat et un rendu HTML : deux choses qui n'existent plus. Il décrit maintenant la navigation en arborescence, le rendu structuré, la pagination, et `/sessions` en deux groupes. Trois passages tiennent une observation plutôt qu'une intention, et c'est volontaire — ce sont ceux qu'on aurait envie de « corriger » plus tard : une case à cocher n'est pas dessinée par les clients actuels, un tableau en chasse fixe se disloque sur un téléphone, et les écrans de navigation vivent en mémoire, donc un redémarrage périme les boutons d'un vieux message. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
L'accesseur `arrete` datait du client à la main, où la boucle de long-polling était la nôtre et où `index.ts` avait besoin d'en interroger l'état. Depuis que `Bot` la tient, plus personne ne le lit — `stopped` ne sert qu'en interne, à taire l'erreur d'une requête abandonnée par `stop()`. Un accesseur public que rien n'appelle laisse croire qu'il répond à une question que quelqu'un se pose. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ières Quatre pertes de sens à la traduction, toutes constatées sur un document réel — `docs/projet/mise-en-recette.md`, qui les porte toutes les quatre. **Une citation se relit entièrement**, comme un document à elle seule. Traitée ligne à ligne, une citation de six lignes donnait six cadres empilés, et la consigne numérotée qu'elle contenait perdait sa numérotation. La récursion s'arrête d'elle-même : chaque tour retire un chevron. **Les listes suivent l'indentation de la source.** Aplatir remontait un sous-point au rang de son parent, ce qui inverse le sens d'une consigne : « à vérifier, dans cet ordre » suivi de quatre sous-points devenait cinq points de même rang. **La suite d'un élément long lui reste attachée.** Une deuxième ligne rentrée fermait la liste, et le point suivant repartait à « 1 » dans un nouveau bloc. **L'alignement des colonnes est lu dans la ligne de séparation.** Les deux-points du Markdown portent une intention — une colonne de nombres alignée à droite dans la source doit l'être à l'écran. C'est précisément ce que `align` exprime, et le champ qu'on avait pris la peine de rendre optionnel contre les types de la bibliothèque. Le commentaire d'en-tête disait deux fois la même chose sur `is_header` ; il ne le dit plus qu'une. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`code` seul est rendu par le client en chasse fixe teintée. Sur un grand écran la teinte suffit à distinguer un chemin du texte qui l'entoure ; sur un téléphone elle se perd, et un document dense en chemins redevient un mur. `marked` ajoute un fond — une différence de **surface**, non de couleur, et c'est ce qui traverse la réduction d'échelle. Les deux entités se cumulent : mesuré contre l'API, `marked(code)` porte bien la chasse fixe, la teinte et le fond, et l'ordre de l'emboîtement est sans effet. Cela valait d'être mesuré plutôt que déduit : le client ignore en silence ce qu'il ne sait pas rendre — `has_checkbox` l'avait déjà montré — et la spec seule n'aurait rien prouvé. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deux défauts qui se voyaient sur la même ligne d'un document réel : « la marche à suivre est dans [`docs/livraison/0.livraison.md`](…) ». **Le libellé d'un lien n'était jamais analysé**, donc ses accents graves restaient à l'écran. Il se relit désormais comme le reste, y compris quand la cible est écartée : un lien relatif reste refusé — il ne mène nulle part depuis une messagerie — mais son texte ne doit pas s'en trouver appauvri. La même relecture vaut pour le gras, l'italique et le barré, qui avaient le même trou. **Telegram fabriquait des liens dans notre dos.** `.md` est un domaine de premier niveau — la Moldavie —, si bien que `0.livraison.md` partait vers un site qui n'existe pas. Le piège est propre à ce que la Passerelle affiche : un dépôt est plein de `.py`, `.pl`, `.sh`, `.io`. `skip_entity_detection` le ferme, et laisse intactes les entités qu'on déclare nous-mêmes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t vraiment Le piège de cette API n'a pas de garde possible : elle accepte en silence les champs qu'elle ne connaît pas, et les types de la bibliothèque déclarent leurs valeurs `string`. Le compilateur ne dira jamais que `pull_quotation` aurait dû s'écrire `pullquote`. Les chaînes exactes ont donc besoin d'un endroit où vivre. `blocs-riches.md` recense les vingt-et-un blocs et les vingt-cinq entités en ligne, dit lesquels on émet et pourquoi les autres sont écartés — et surtout note, à côté de chaque cas, ce que l'observation contredit dans la spec. Une correction en découle. Le commentaire des listes numérotées accusait le champ `label` : c'est faux, `label` n'existe que sur les blocs *reçus*. Le champ d'envoi est `value`, secondé par `type`. Mesuré : `type` seul déclenche la numérotation, `value` seul ne fait rien, et la forme décimale décale d'un rang — le premier point s'affiche « 0. », là où les lettres et les romains sont justes. Garder le numéro dans le texte reste donc le bon choix, mais pour la vraie raison. Le fichier note aussi ce que la spec offre et qu'on n'exploite pas : `markdown` en entrée directe — écarté, on y perdrait la main sur les cas où le rendu par défaut est faux —, le bloc repliable `details`, et la borne des 500 blocs, la seule limite qu'on ne garde pas. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entre l'envoi d'un message et la réponse, il pouvait se passer dix minutes sans un octet. À l'écran, la ligne d'activité comble ce silence ; dans une conversation il ne restait rien — ni signe que le message avait été reçu, ni moyen de distinguer « ça travaille » de « c'est tombé ». Le support est le **brouillon** (`sendRichMessageDraft`), pas un message. Il est éphémère, ne persiste pas dans le fil, et s'anime au lieu de s'empiler. C'est ce qui le rend compatible avec la règle du manuel : AURA n'envoie pas le flux d'activité, et un brouillon n'est pas envoyé — il est montré, puis il disparaît. Deux contraintes de l'API dictent le reste. Un brouillon expire au bout de trente secondes : d'où un battement de vingt. Une phase d'outil se remplace toutes les deux ou trois secondes : d'où un pas minimal de deux, en dessous duquel un libellé qui change se lit comme un scintillement. Deux réserves mesurées, et l'action de saisie qui en découle : le brouillon ne vaut qu'en conversation privée, et le client web ne le rend pas — seuls les clients mobiles le font, vérifié. `sendChatAction` l'accompagne donc : elle ne dit que « quelque chose se passe », mais elle passe partout. Le libellé reprend celui de l'Atelier — mêmes phases, mêmes noms d'outils — parce qu'une session lue de deux endroits ne doit pas raconter deux histoires. La durée affichée est celle du tour, jamais de la phase : c'est la seule qu'on se demande vraiment. Le battement s'arrête sur trois événements, et jamais sur une échéance : fin de tour, demande de permission, question. Sur les deux derniers, la balle est dans le camp de l'utilisateur — laisser battre la bulle ferait croire le contraire. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Un agent écrit du Markdown — titres, listes, tableaux, chemins entre accents graves. On l'envoyait en texte nu, si bien qu'un tableau arrivait avec ses barres verticales et ses tirets, à côté d'une traduction en messages riches qu'on ne servait qu'aux fichiers. C'était le rendu le plus souvent lu de toute la Passerelle : on lit une réponse à chaque tour, un fichier de temps en temps. Deux différences avec l'envoi d'un fichier, et elles tiennent à ce qu'une réponse n'en est pas un. Pas d'en-tête — on sait qui parle. Pas de pagination : tourner la page suppose de pouvoir relire la source, or celle-ci ne vit que dans la session. Une réponse trop longue reste donc coupée, mais la coupe passe de 4 000 à près de 23 000 caractères, la borne du riche étant huit fois celle d'un message ordinaire. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deux choses visibles ont changé, et le manuel les taisait. La bulle d'activité y gagne sa propre section, avec les deux réserves qu'il vaut mieux lire avant de s'étonner : elle demande une conversation privée et un client mobile. Sur le web ou dans un groupe, il ne reste que le « en train d'écrire… » de l'en-tête. Et la section « ce que je ne vous envoie pas » disait le contraire de ce que la Passerelle fait maintenant : elle promettait de ne pas envoyer la ligne d'activité. La promesse tenait à ce qu'une messagerie n'est pas une timeline — elle tient toujours, mais l'exception doit se dire : une bulle qui s'efface n'empile rien. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Le barreau intermédiaire de `envoieRendu` rendait le document en HTML. Mesuré sur 162 pages de deux dépôts réels, il ne pouvait partir que sur 20 % d'entre elles : le HTML pèse 10 à 16 % de plus que sa source pour une limite huit fois plus basse, si bien qu'il échouait précisément sur les documents qui en avaient besoin — et retombait alors sur du texte nu coupé à 4 000 caractères. Il ne protégeait pas non plus de ce qu'on croyait : un bloc mal formé par `riche.ts` produit une structure légale qui s'affiche mal, l'API répond `ok: true`, et le repli ne se déclenche jamais. Il ne couvrait que le refus de charge, où le HTML est le plus faible des deux. Le repli est désormais le même appel avec un bloc unique : le document tel quel dans un `paragraph`. Mesuré contre l'API — les retours à la ligne et les lignes vides sont conservés, en texte proportionnel. On perd la mise en forme, on garde la mise en page, les 32 768 caractères et le repli « Afficher plus ». `pre` conservait autant mais enfermait la prose dans un encadré de code. Ce qui disparaît avec : un second analyseur Markdown qui dérivait du premier — la fusion des citations corrigée dans `riche.ts` n'y avait jamais été portée. `markdown.ts` se réduit à la pagination, et son défaut de bloc vide est corrigé au passage : la clôture d'un bloc de code reste sur la page qui l'a ouvert, au lieu d'ouvrir un bloc aussitôt refermé sur la suivante. Les bornes de Telegram passent dans `telegram.ts`, qui est le seul à avoir à les connaître. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`tranche` était synchrone et lançait ses branches en `void`. Le `void` détachait la promesse : elle sortait du `try` de l'appelant, et du crochet d'erreur de la bibliothèque avec. Or `navigue` appelle `listProjects()` hors de tout `try` — un `~/.claude` déplacé, un disque qui répond mal, et c'était un rejet non traité. Il n'y a pas de `unhandledRejection` dans `server/`, donc c'est le défaut de Node : le process se termine. Un clic sur un bouton emportait le BFF et les sessions de l'Atelier avec lui. Le chemin des messages n'avait pas le défaut — `await traite(...)` est bien dans son `try`. C'est l'asymétrie entre les deux portes qui faisait le trou. Au passage, deux points relevés à la relecture : Un bouton de descente dont le rang ne désigne plus un dossier ne disait rien. Partout ailleurs le fichier répond `navigationPerimee`, avec pour raison écrite qu'un bouton sans effet passe pour une panne — ce cas-là contredisait sa propre règle. Et `applique` traite `waiting` comme une fin de tour, ce qui n'en est pas une : le commentaire le disait mal. C'est voulu, et c'est maintenant écrit — ce que l'agent a rédigé avant de demander part tout de suite, la demande suit avec ses boutons, et l'on voit ce qu'il veut faire avant d'avoir à le trancher. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Trois listes devaient s'accorder à la main : le `switch` de `routage.ts`, le texte de l'aide dans les deux catalogues, et ce que Telegram propose sous le `/`. Cette dernière était saisie dans `@BotFather`, donc hors du dépôt — invisible à toute relecture, et libre d'annoncer une commande retirée depuis. `commandes.ts` porte désormais la table. L'aide s'en compose, et `setMyCommands` en dérive au démarrage, une fois par langue : la langue de référence tient la liste par défaut, celle que voit un client dont la langue n'a pas la sienne. Le routage garde son `switch` — il traduit une commande en intention, ce qui n'est pas mécanique. C'est un test qui tient les deux listes égales, dans les deux sens : la lecture inverse est celle qui attrape le vrai oubli, une commande ajoutée au routage et jamais annoncée. Vérifié en le mettant en défaut. Les descriptions changent de forme parce qu'elles changent d'usage : la liste de Telegram les affiche seules, hors de l'aide. « son arborescence » ne veut plus rien dire quand il n'y a pas de phrase avant. La déclaration ne bloque rien : un refus ne coûte que l'autocomplétion, et les commandes restent reconnues puisque c'est `routage.ts` qui en juge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Le mobile n'affichait aucune commande là où le web les affichait toutes. La cause se lit dans `getMyCommands` : la liste par défaut répondait, la même interrogée en `fr` était vide. N'ayant posé que le défaut pour le français — en jugeant qu'une liste `fr` explicite serait redondante —, un client réglé en français demandait `fr`, ne trouvait rien à aucun scope, et n'affichait rien. Le web retombait sur le défaut, le mobile non. Plutôt que de départager les deux clients, on ne laisse plus de repli à prendre : le défaut est posé, puis chaque langue reçoit la sienne, la référence comprise. La redondance est mesurée, pas décorative. C'était un raisonnement là où il fallait une mesure — le même défaut que `riche.ts` documente pour les blocs : l'API accepte sans erreur ce qui ne s'affichera pas. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`/start` était un alias d'`/aide` : le geste que Telegram propose de lui-même à qui ouvre la conversation répondait par la liste des huit commandes. C'est une référence, pas une porte d'entrée — elle arrive avant qu'on sache quoi demander. Il a donc son intention à lui. Deux lignes et trois boutons : ce que je suis, ce que je vois — le nombre de projets, la session déjà ouverte ici, ce qui tourne ailleurs — et par où entrer. `docs/voix.md` range l'accueil parmi les surfaces où le « je » doit porter ce que la formulation impersonnelle ne portait pas ; l'état du parc est cette information, un sommaire des boutons ne l'aurait pas été. L'accueil prend la place du message de navigation : « Projets » le réécrit au lieu d'empiler, si bien qu'il devient la première marche du même parcours que l'arborescence. « Sessions » et « Aide » produisent du contenu, pas un pas : elles partent dans leur propre message et laissent l'accueil en place — la même règle que `/voir`. Les boutons sont en rangée et non en `solo` : `solo` détache une action d'une liste, et ici il n'y a pas de liste. Leurs libellés perdent leur article pour tenir sur une rangée, et parce que `docs/voix.md` veut les boutons nominaux — `Projets`, comme `Autoriser`. `/aide` est inchangée et reste la référence. Le listage des sessions devient une fonction, les deux entrées qui y mènent devant aboutir au même endroit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deux silences de la Passerelle, relevés en revue et laissés en suspens. `AskUserQuestion` n'était traitée qu'au quart : la première option de la première question, le reste jeté, et un refus dès qu'il y en avait plusieurs. Deux pertes étaient muettes — `multiSelect` était ignoré, si bien qu'une question à choix multiples se répondait d'un seul clic ; et `description` et `preview` disparaissaient, alors qu'`ask.ts` s'était donné du mal pour que la seconde traverse son schéma zod. `passerelle/questions.ts` porte désormais le formulaire de `AskPrompt.vue`, sans rien y ajouter : une question par message, le choix multiple qui se coche, la réponse écrite quand aucune option ne convient. Même doctrine que `routage.ts` — il décide sans réseau, donc il se teste. Deux mesures le dessinent. Un message riche ne se réécrit pas : la bibliothèque expose `sendRichMessage` et aucun `editMessageRichText`. Cocher ne touche donc qu'au clavier, par `editMessageReplyMarkup` — ce qui est de toute façon ce qu'on veut, la question n'ayant aucune raison de bouger sous les doigts. Et l'écran quitté est désarmé avant le suivant : ses boutons ne portent qu'un rang, un clic tardif cocherait une option de la question d'après. Vu à l'écran, corrigé. La réponse écrite est interceptée dans le seul cas `parler`, après `parseIntention` : sous une question, `/stop` reste `/stop`. `snapshot` avait deux sites d'émission et un commentaire qui n'en nommait qu'un. L'abonnement rejoue l'historique — on le passe, et le premier reçu est le sien par construction, `subscribe` émettant avant d'enregistrer. L'autre est `resetConversation` : le contexte vient d'être vidé et la conversation n'en apprenait rien, continuant de parler à quelqu'un qui a tout oublié. Elle relaie maintenant les mots mêmes de l'écran. Rien ne peut le provoquer d'ici — `/clear` n'est pas une commande servie —, ce qui est la raison de le dire plutôt que l'inverse. Vérifié de bout en bout sur Telegram : trois écrans enchaînés, un `multiSelect` coché puis validé, deux maquettes ASCII rendues en blocs préformatés, une réponse tapée à la main acceptée, et un `/clear` lancé du dehors annoncé dans la conversation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
De loin, on ne savait pas où l'on en était. La conversation dit ce que l'agent répond, jamais la place qu'il lui reste — et une compaction, qui vide la fenêtre en plein travail, ne laissait aucune trace : `applique` ne relaie que les événements `assistant`. `/etat` rend la fenêtre, et le chiffre ne peut pas diverger de la page Contexte. Le runner le relève sur `message.message.usage` comme `input + cache_read + cache_creation`, la somme même dont `transcript.ts` ancre un tour. Pas de transcript à relire : c'est en mémoire, et c'est exact. Le cache y est compté — ce qui est relu occupe la fenêtre comme le reste, seul le prix diffère. Le runner relève et ne juge pas. La limite vient de `contextLimitFor`, qui existe pour une raison : un modèle à fenêtre longue s'enregistre sans son suffixe `[1m]`, et seules deux preuves la révèlent — un contexte observé au-dessus de 200 k, ou les réglages. `pre_tokens` d'une compaction est la plus précoce des deux. Deux messages qu'AURA avance d'elle-même, et le manuel les nomme désormais : la compaction avec ses chiffres, et le franchissement des 80 % dit une seule fois. Ce seuil est le garde-fou de `contextFill`, recopié faute de pouvoir l'importer, et un test tient les deux nombres égaux. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`30 527 / 1 000 000` demande de compter les chiffres des deux côtés pour saisir le rapport ; `31 k / 1 M` le donne. Sur un téléphone, où cette ligne est lue en une œillade, c'est la différence entre lire et déchiffrer. `Intl` plutôt qu'un formatage à la main : la forme compacte est une affaire de langue — `31 k` en français, `31K` en anglais, séparateur décimal compris. Aucune décimale, parce que les deux seules limites qui existent — 200 k et 1 M — sont exactes, et qu'un `1,0 M` laisserait croire à un arrondi qui n'a pas eu lieu. La compaction passait ses chiffres bruts, relevé à l'essai : la conversation annonçait « 31 k » sur une ligne et « 30549 » sur la suivante, pour la même fenêtre. Elle emprunte désormais le même formateur. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deux manques que `/etat` avait rendus visibles : on voyait la fenêtre se remplir sans pouvoir rien y faire, et la compaction s'annonçait sans dire ce qu'elle avait gardé. `/compacter` est la seule commande de Claude Code que la Passerelle relaie, et la surface le justifie — de loin, il n'y avait aucun autre geste. Elle passe par la file d'entrée comme un tour : c'est le CLI qui compacte. Le résumé, lui, n'arrive pas avec la frontière. Mesuré en instrumentant le flux SDK : il suit immédiatement, dans un message `user` marqué `isSynthetic` dont le contenu est une chaîne — et le `<local-command-stdout>` qui vient après porte `isReplay`, ce qui les sépare. Le runner le capte et le pose sur l'événement de compaction par un `replace-event`, si bien que la compaction s'annonce tout de suite : un résumé qui ne viendrait pas ne l'emporterait pas dans son silence. Il se rend replié. Le bloc riche `details` existe — `blocs-riches.md` le donnait pour « la piste la plus sérieuse » —, il a été essayé et il rend : titre visible, corps qui s'ouvre au clic, formatage conservé et les 32 768 caractères du riche. L'« Afficher plus » automatique ne le remplace pas, ne s'étant pas déclenché sur sept mille caractères. Un défaut relevé à l'essai, et il dépasse ce chantier : les événements d'un fil partaient de front, si bien que le résumé — un gros message — doublait l'annonce qui le précédait. Ils se traitent désormais à la file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ExitPlanMode arrivait comme une permission ordinaire : deux boutons et le nom de l'outil. Le plan lui-même vit dans `input.plan` et n'était pas montré — on approuvait donc un texte qu'on n'avait pas lu. C'est le seul appel dont l'argument *est* la décision. Il passe désormais par `envoieRendu`, comme un document : le markdown entier, mis en forme, et les deux boutons sous lui. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Les boutons d'une permission restaient pressables après la réponse : on ne savait pas si le clic avait porté, et un fil relu donnait à croire qu'une question attendait encore. Le désarmement se fait sur `permission-settled`, jamais sur le clic — c'est le seul point de passage qui voie aussi les réponses données dans l'Atelier et les refus du garde-fou du quart d'heure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La liste était déclarée sur la portée par défaut, donc visible de quiconque ouvre le bot — et un bot est adressable de toute la Terre. Un inconnu n'obtenait déjà aucune réponse, mais il lisait le menu : ouvrir une session, lancer une commande, lire un fichier. C'est de l'obscurité et non de la sécurité, mais elle ne coûte rien. `declare` accepte désormais une portée de conversation, et la liste est posée pour chacune des conversations autorisées, langue par langue comme avant. Le défaut est effacé au même moment : ce qui a été posé chez Telegram y reste tant qu'on ne le retire pas, et une version antérieure l'avait posé. L'effacement passe sur chaque langue. Telegram range ses listes par langue autant que par portée, si bien qu'effacer le défaut sans langue laisserait debout celle qu'un client français avait reçue. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ce qui transite passe par les serveurs de la messagerie, sans chiffrement de bout en bout. Le manuel, SECURITY et le README le disent maintenant avant qu'on configure le jeton, et nomment la piste cherchée pour s'en passer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Une capacité nouvelle, éteinte par défaut : la Passerelle. Mineure, donc, au sens que le journal donne aux trois nombres. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
La Passerelle relie une messagerie à l'Atelier : lancer une session, la
surveiller, la débloquer, quand on n'est pas devant le poste. Elle est
éteinte par défaut, n'ouvre aucun port, et refuse de démarrer sans sa liste
blanche de conversations.
Ce que la branche apporte
Claude Code, tour ordinaire sans syntaxe,
/sessions,/etat,/stop,/fin— et une navigation dans l'arborescence d'un projet qui n'ouvreaucune session.
pagination des documents longs, repli sur le texte brut plutôt que la perte.
choix simple ou multiple, échéance du quart d'heure qui refuse par défaut.
/etatet/compacter, avis de compaction avec sonrésumé replié, et un seul avis au passage des 80 %.
L'usage recommandé, et ses limites
Ce qui transite passe par les serveurs de la messagerie, sans chiffrement de
bout en bout. Le manuel,
SECURITY.md, leREADMEetCLAUDE.mddisentmaintenant que la Passerelle vise l'usage personnel et n'est pas recommandée
en contexte professionnel, et nomment la piste cherchée pour s'en passer —
un réseau privé rendant l'Atelier joignable depuis un téléphone, ce qui
supposerait d'adapter l'interface à cet écran.
Vérifications
pnpm lint,pnpm typecheck,pnpm test(518 tests) etpnpm formatpassent.Fusion attendue par merge commit — pas de squash : les correctifs commités
sur la branche doivent rester lisibles.
v1.3.0sera posé sur le merge.🤖 Generated with Claude Code