Passerelles de chat
Une passerelle de chat fait d’un produit de chat externe — Slack aujourd’hui — un moyen d’accès de premier ordre à un espace de travail Control Center. Mentionnez le bot et un agent se réveille ; répondez dans le fil Slack et la conversation se poursuit ; tapez une commande slash et un ticket est créé. En dessous, les mêmes espaces, transcriptions, tickets et attributions que ceux qu’utilise l’application de bureau. Le chat est une surface de l’espace de travail, pas une copie de celui-ci.
Slack est le seul fournisseur livré. Il n’existe aucune implémentation de Discord, Telegram, Teams ou Matrix nulle part dans le produit. La fonctionnalité est écrite sans connaissance du fournisseur, à dessein — ajouter un second produit, c’est une valeur d’énumération, un adaptateur et un plugin —, mais aujourd’hui « chat » signifie Slack.
Deux surfaces couvrent le sujet et elles appartiennent à des personnes différentes. Connecter un espace de travail à Slack et personnaliser le bot relèvent du rôle Admin, sous Paramètres → Espace de travail → Général. Lier votre propre compte de chat à votre identité Control Center relève du rôle Membre, sous Paramètres → Espace de travail → Profil et identité.
Trois décisions façonnent toute la fonctionnalité : dans quelle direction circule la connexion, ce qui autorise un message venu de l’extérieur et où la connaissance propre à chaque produit a le droit de vivre.
Sortant par conception
Section intitulée « Sortant par conception »La passerelle n’écoute jamais le fournisseur. Slack est joint via le Socket Mode : le serveur demande à Slack une URL de socket et établit la connexion sortante, puis reçoit les mentions, les messages privés et les commandes slash sur cette connexion.
La conséquence, c’est tout l’intérêt. Un portable derrière un NAT, un serveur domestique sans domaine, une machine qui change de réseau — tous peuvent faire tourner la passerelle sans port entrant, sans tunnel, sans certificat TLS et sans URL publique à changer quand elle évolue. Il n’y a aucun point de terminaison webhook à sécuriser, parce qu’il n’y a aucun point de terminaison webhook. Quand Control Center compose la définition de l’application, il impose le Socket Mode et supprime toute URL de requête, car une application configurée pour les deux serait une application qui ne fonctionne qu’à moitié.
Le coût, c’est une connexion à maintenir en vie, ce qui est le travail du serveur : la reconnexion suit un repli exponentiel avec jitter plafonné à 30 secondes, une rotation de socket déclenchée par le fournisseur se reconnecte sans aggraver ce repli, et un identifiant rejeté est définitif — réessayer indéfiniment un jeton révoqué ne rapporterait qu’une limite de débit, donc la passerelle s’arrête et attend de nouveaux jetons, tout en rapportant mot pour mot l’erreur du fournisseur dans les paramètres.
Un espace de travail sans application de chat ne coûte rien du tout : ni socket, ni minuteurs, ni trafic. Et l’ensemble démarre après la bannière de disponibilité du serveur, car une application de chat lente à répondre ne doit jamais retarder le démarrage.
L’appartenance autorise, pas la possession du jeton
Section intitulée « L’appartenance autorise, pas la possession du jeton »La personne qui a installé l’application n’est pas celle qui envoie le message. Chaque événement entrant est donc résolu en un Principal avant que quoi que ce soit ne se produise, et tout échec se solde par un refus assorti d’une explication :
- Un compte de chat non lié reçoit la marche à suivre pour se lier, dans le fil Slack où il a écrit.
- Un compte lié dont l’utilisateur n’est pas membre de cet espace de travail est invité à demander une invitation à un admin.
- Un membre dont le rôle est en lecture seule (
viewer,guest) est informé que son rôle ne permet pas de lancer du travail.
Aucun de ces cas ne se poursuit. Un message de chat n’est jamais attribué à quelqu’un qui ne l’a pas envoyé — c’est ce qui préserve l’exactitude de la piste d’audit, des limites de débit par Principal et des auteurs de tickets. Voir Multijoueur — identité, appartenance et présence pour le modèle d’appartenance sur lequel cela s’appuie.
La liaison est une résolution d’identité, pas une autorisation d’accès. L’appartenance est revérifiée à chaque événement, si bien qu’une liaison appartenant à quelqu’un qui a été retiré depuis se résout en refus plutôt qu’en ses anciens privilèges. Deux chemins établissent une liaison : un code à usage unique et à courte durée de vie que le membre saisit dans le chat, ou une correspondance automatique sur l’adresse e-mail vérifiée du fournisseur — et le chemin par e-mail exige encore que l’utilisateur correspondant soit déjà membre, de sorte qu’un inconnu dont l’adresse existe par hasard sur le serveur n’est lié à rien.
Les codes sont délibérément gardés en mémoire, avec une durée de vie de 15 minutes. En persister un créerait une ligne durable en forme d’identifiant pour quelque chose destiné à s’évaporer, et le fait qu’un redémarrage du serveur invalide tous les codes en cours est le comportement correct, pas une régression — le membre appuie de nouveau sur « Lier mon compte ».
Un refus est répondu dans le fil Slack que le message a ouvert, comme une vraie réponse. Une réponse en fil signale le message auquel elle répond, atteint la seule personne qui en a besoin et garde malgré tout « vous n’êtes pas lié » en dehors du canal lui-même. Publier un message est aussi la seule capacité que possèdent tous les fournisseurs, donc l’explication ne dépend jamais d’une fonctionnalité que l’application pourrait ne pas porter.
Provenance et problème d’écho
Section intitulée « Provenance et problème d’écho »Une passerelle qui reflète dans les deux sens finit par boucler, à moins que
quelque chose ne distingue les deux directions. Chaque message que la passerelle
fait entrer est estampillé avec metadata['chat'] — fournisseur, équipe,
conversation, fil, message et expéditeur. Le miroir sortant ignore tout message
portant cette estampille.
Cette règle unique est ce qui rend le miroir bidirectionnel sûr : un message humain tapé dans Control Center est relayé vers le fil lié (préfixé par le nom de son auteur), tandis qu’un message venu du chat s’arrête là où il est entré.
Ce qui traverse vers l’extérieur est délibérément étroit. La réponse de
l’agent est relayée comme du texte de chat ordinaire. Une brève réflexion peut
apparaître sur la ligne Thinking… de la carte. La sortie des outils est un mur
de diffs qui reste dans Control Center, lieu vers lequel pointe aussi une réponse
tronquée.
Une carte rend compte du travail ; le texte reste le message
Section intitulée « Une carte rend compte du travail ; le texte reste le message »Là où le fournisseur les affiche, un tour porte une carte de tâche, modifiée sur place tant que le tour dure et posée au-dessus de la réponse qu’elle explique.
La carte est un plan groupé, pas un paragraphe écrasé. Slack concatène details
et output sur la même tâche, donc les détails d’une ligne sont envoyés une fois
(la réflexion sur Thinking…) et output n’est jamais diffusé en continu. La
préparation est une ligne dont le titre est remplacé (Working on it…,
Cloning …). Le raisonnement ajoute une ligne Thinking…. Chaque outil a sa
propre ligne (Read lib/main.dart). La réponse est du texte ordinaire sous la
carte — la vue plan de Slack la lit dans un ajout markdown ultérieur, et non dans
un fragment mêlé aux tâches. Rien d’autre : aucune sortie d’outil, et pour
seul argument d’outil ceux d’une courte liste d’autorisations, car les arguments
d’un outil inconnu peuvent être tout le corps d’un fichier, et une ligne, c’est
une seule ligne.
La carte couvre aussi l’attente avant le tour. Une première mention crée la
conversation, et la créer clone chaque dépôt dans lequel l’agent travaillera — ce
qui, sur un vrai dépôt, représente des minutes pendant lesquelles on n’a encore
rien demandé à l’agent et où le fil donne l’impression que le bot a ignoré la
demande. La carte s’ouvre donc à l’instant où la mention est acceptée
(Working on it…), puis cette ligne de préparation se met à jour avec les mots
que montre l’application de bureau — Cloning acme/widgets…, Setting up Reviewer…, Starting the agent… —, ensuite une ligne Thinking… dès que
l’agent commence à raisonner, puis une ligne par outil. Une carte, un message,
depuis « j’ai bien reçu » jusqu’à la réponse en texte ordinaire en
dessous. La préparation n’est signalée que là où quelqu’un dans le chat l’attend :
une conversation en cours de préparation dans l’application, ou une conversation
orpheline reprise au démarrage du serveur, s’approvisionne sans aucune carte.
Elle porte la seule chose que le chat ne peut pas donner au lecteur : un lien
View in Control Center vers la conversation qui se trouve derrière la
réponse. Les produits de chat n’acceptent que http(s) dans un lien et rien
d’autre, donc le bouton pointe vers une petite page que sert ce serveur, laquelle
redirige le navigateur vers le lien control-center:// de l’application de
bureau. Cette page ne lit rien et ne résout rien — les identifiants de son URL
sont répercutés dans un lien profond et nulle part ailleurs —, et un serveur sans
URL publique joignable produit simplement une carte sans bouton plutôt qu’une
carte cassée.
Les cartes sont une capacité comme une autre. Un fournisseur qui n’en propose pas
se comporte exactement comme avant, en texte seul, et un fournisseur qui en
refuse une au moment de l’appel conserve son flux : la réponse l’emporte
toujours sur la carte qui la décrit. Quand la diffusion en continu est
entièrement désactivée, la carte terminée voyage dans l’unique message publié. Un
ticket créé reçoit le même traitement — la confirmation de /cc ticket est une
carte terminée dont la raison d’être est le lien vers le ticket qui vient d’être
créé.
Les capacités se négocient, puis se mémorisent
Section intitulée « Les capacités se négocient, puis se mémorisent »Les produits de chat diffèrent par ce qu’ils savent afficher, donc la passerelle est écrite pour la surface la plus riche — une réponse qui grandit en direct dans un fil, un statut transitoire « is thinking… », un fil avec titre, une réponse éphémère à une commande slash — et dégrade une capacité à la fois. Un fournisseur qui n’en a aucune fonctionne encore ; les réponses arrivent simplement en un seul message.
Annoncer une capacité n’est pas la promettre. Les réponses en flux de Slack exigent une fonctionnalité de forfait payant, et Slack ne refuse qu’au moment de l’appel. Un refus marqué comme définitif est mémorisé pour toute la vie de la connexion : la passerelle cesse de demander et publie des réponses entières à partir de là, et les paramètres le disent sans détour, parce que « mes réponses n’arrivent plus en flux » doit pouvoir se résoudre sans lire un journal. Les cartes de tâche sont abandonnées de la même façon si Slack en refuse une.
Chaque limite appartient au fournisseur et non au relais : le plafond de messages, le plafond de fragments de flux et la question de savoir si un flux a besoin d’un point d’ancrage de fil sont tous déclarés par l’adaptateur, et une longue réponse est découpée sur une frontière de paragraphe, de ligne ou de mot plutôt qu’au milieu d’un mot.
Une passerelle, un adaptateur par produit
Section intitulée « Une passerelle, un adaptateur par produit »Tout ce qui a la forme d’un produit vit derrière un port. Le cœur générique
connaît le markdown, les conversations de chat, les fils de chat et les membres ;
il ne sait rien des enveloppes Slack, de mrkdwn ni des intents de la passerelle.
Trois pièces font que cela fonctionne :
- L’adaptateur normalise un produit : son transport, ses appels d’API,
son codec de texte et ses bizarreries d’enveloppe. Ceux de Slack sont
instructifs — une mention de canal arrive deux fois (comme
app_mentionet commemessage) et la plupart des sous-types de message relèvent de l’habillage plutôt que de nouvelles requêtes. Les deux cas sont résolus dans l’adaptateur, si bien que le cœur n’apprend jamais que Slack livre en double. - Le descripteur est la forme publique du fournisseur : de quels identifiants il a besoin, quels sont leurs formats, quelles URL aident un utilisateur à les trouver, ce qu’il sait faire et si son application peut être créée ou modifiée depuis ici. La carte de paramètres, la boîte de dialogue de connexion et la validation des arguments sont toutes générées à partir de lui — le client renvoie un fournisseur ainsi qu’une table indexée par identifiant de champ.
- Le plugin est la composition côté serveur : comment des identifiants deviennent une connexion vérifiée, comment un adaptateur est construit et comment l’application côté fournisseur est gérée.
Ajouter un produit, c’est donc une valeur d’énumération, un adaptateur et un plugin. Aucune nouvelle opération RPC, aucun changement de schéma, aucun changement côté client — et le client reste honnête, car un champ dont il n’a jamais entendu parler s’affiche quand même avec le libellé fourni par le serveur.
Deux détails empêchent l’abstraction de fuir vers le haut. Les étapes de gestion d’application pour lesquelles un fournisseur n’a pas d’API (générer un jeton, installer l’application, réinstaller après un changement de permissions) sont renvoyées comme des étapes nommées avec leurs propres liens, si bien que l’écran de fin de configuration les affiche sans savoir de quel fournisseur il s’agit. Et chaque instruction qu’écrit la passerelle nomme le fournisseur ainsi que la commande réellement utilisée par l’application, donc une commande renommée ou un second fournisseur se lit correctement sans ajouter de chaîne de texte.
Où vivent les identifiants
Section intitulée « Où vivent les identifiants »Les jetons de chat d’un espace de travail résident dans le répertoire propre de
cet espace de travail, à côté de sa base de données — sous forme de JSON brut à
l’emplacement <dataDir>/<workspaceId>/chat_credentials/<provider>.json, ni dans
la base de données ni, délibérément, dans le trousseau du système d’exploitation.
Deux conséquences s’oublient facilement ensuite : supprimer un espace de
travail délie le répertoire et ses jetons partent avec lui, et exporter ou
sauvegarder un espace de travail copie workspace.db, donc un espace de travail
confié à quelqu’un d’autre ne transporte pas de jetons de bot actifs à
l’intérieur. Le fichier hérite de la frontière de confiance du système de
fichiers de l’hôte du serveur — tout ce qui a accès en lecture au répertoire de
données peut lire un jeton de bot, ce qui est la même frontière que celle derrière
laquelle se trouvent les bases de données.
Un fichier par fournisseur, écrit de façon atomique et accessible au seul propriétaire : une application révoquée se supprime en déliant un seul fichier, et un fichier corrompu coûte un fournisseur plutôt que tous. Les identifiants circulent vers l’intérieur lors de l’opération de connexion et ne ressortent jamais — les lectures d’état ne répondent qu’avec les métadonnées de connexion.
Ce qui n’est délibérément pas proposé
Section intitulée « Ce qui n’est délibérément pas proposé »- Les paramètres structurels de l’application. Le transport, les scopes et les abonnements aux événements sont imposés, pas exposés. Une application que l’on peut configurer jusqu’au silence depuis un écran de paramètres est un ticket de support, pas une fonctionnalité.
- Conversation et commandes slash. La surface Slack, ce sont les messages, les réponses en fil et les commandes slash.
- Une seconde source de vérité. Le chat n’est pas un endroit où le travail est stocké. Le fil Slack est un espace, la réponse est une transcription, le ticket est un ticket — tous dans l’espace de travail, accessibles depuis chaque client.
Voir aussi
Section intitulée « Voir aussi »- Multijoueur — identité, appartenance et présence : les Principals et les rôles à travers lesquels la passerelle résout
- Espaces de travail et isolation : pourquoi les liens et les identifiants sont propres à chaque espace de travail
- Le modèle d’agent : ce qui se réveille quand une mention arrive
- Tickets et délégation : ce que crée
/cc ticket - Architecture : où se situe la passerelle entre le serveur et les clients
- Relier Slack à votre espace de travail
- Configurer l’intégration Slack
- Lier votre compte Slack
- Personnaliser le robot de chat
- Conversations de PR : les trois mêmes décisions, prises pour une forge