Passerelle de clavardage
Fournisseurs
Section intitulée “Fournisseurs”| Fournisseur | Valeur transmise | Transport | Type d’espace |
|---|---|---|---|
| Slack | slack |
Socket Mode (WSS sortant) | slack |
Slack est le seul fournisseur. ChatProvider est une énumération à valeur unique et cc_server n’enregistre qu’un greffon. Le schéma, chaque recherche et chaque index unique ont provider pour clé, si bien que les identifiants ne traversent jamais d’un fournisseur à l’autre et qu’en ajouter un est un changement purement côté serveur.
Une application côté fournisseur par espace de travail × fournisseur.
Commandes de clavardage
Section intitulée “Commandes de clavardage”Le nom de la commande est propre à chaque application (cc par défaut, donc /cc). Un administrateur peut le renommer ; la passerelle utilise alors celui avec lequel elle a été appelée en dernier.
| Commande | Effet |
|---|---|
/cc help (ou /cc sans verbe) |
Liste ce que le robot peut faire |
/cc link <code> |
Échange un code à usage unique et lie l’identité de clavardage de l’expéditeur |
/cc ticket <title> |
Crée un ticket local signalé par le membre lié |
/cc ticket <title> | <description> |
Idem, tout ce qui suit le premier | servant de description |
| Verbe inconnu | Signale le verbe inconnu ainsi que le texte d’aide |
Façons de joindre un agent :
| Geste | Comportement |
|---|---|
@bot … dans un canal |
Ouvre un fil Slack et un espace Control Center correspondant ; l’agent répond dans le fil Slack |
| Réponse dans un fil Slack relié | Poursuit le même espace Control Center — aucune mention nécessaire |
| Message ordinaire dans un canal | Ignoré (non adressé au robot et hors d’un fil Slack relié) |
| Message privé au robot | Un espace Control Center par conversation privée ; définit un titre de fil Slack et un état « is thinking… » pendant que l’agent travaille |
Champs d’identifiants (Slack)
Section intitulée “Champs d’identifiants (Slack)”Déclarés par le descripteur du fournisseur, qui génère la boîte de dialogue de connexion et valide la soumission.
| Identifiant du champ | Étiquette | Obligatoire | Préfixe | Où il se trouve |
|---|---|---|---|---|
botToken |
Jeton du bot | Oui | xoxb- |
OAuth & Permissions |
appToken |
Jeton d’application | Oui | xapp- |
Basic Information → App-Level Tokens, portée connections:write |
configRefreshToken |
Jeton de configuration d’app | Non | xoxe |
App configuration tokens sur api.slack.com/apps |
configRefreshToken est l’identifiant de gestion de l’application : sans lui, la connexion fonctionne, mais le robot ne peut être ni créé ni modifié depuis Control Center. Il subit une rotation à chaque utilisation, et la valeur renouvelée est stockée immédiatement.
Les valeurs sont débarrassées des espaces superflus, les clés non déclarées sont écartées et un préfixe non conforme est refusé avec la même phrase côté client et côté serveur. Les champs obligatoires doivent être présents à chaque connexion ; un champ facultatif laissé vide conserve ce qui était déjà stocké.
Exigences de l’application Slack
Section intitulée “Exigences de l’application Slack”Portées du jeton du robot :
app_mentions:readassistant:writechannels:historychat:writecommandsgroups:historyim:historyusers:readusers:read.emailAbonnements aux événements du robot :
app_home_openedapp_mentionmessage.channelsmessage.groupsmessage.imRéglages structurels que Control Center impose quand il crée ou modifie l’application : Socket Mode activé, aucune URL de requête, l’onglet Messages activé et accessible en écriture, le robot always_online, les portées et les événements requis fusionnés dans ce que l’application possède déjà (jamais retranchés) et une commande slash portant la description File a ticket or link your Control Center account — le marqueur qui identifie la commande propre à Control Center dans un manifeste exporté.
États de la connexion
Section intitulée “États de la connexion”| État | Valeur transmise | Signification |
|---|---|---|
| Non connecté | disconnected |
Aucun identifiant stocké pour cet espace de travail × fournisseur |
| Connexion… | connecting |
Identifiants stockés, le transport se connecte ou se reconnecte |
| En ligne | connected |
Transport établi et réception des événements |
| Erreur de connexion | error |
Stockés mais en échec ; l’erreur propre au fournisseur est affichée à côté |
La reconnexion est une temporisation exponentielle avec gigue, plafonnée à 30 secondes. Une rotation de socket amorcée par le fournisseur se reconnecte rapidement, sans faire grimper la temporisation. Un identifiant rejeté (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) ou une portée manquante est définitif : la passerelle cesse de réessayer jusqu’à l’arrivée de nouveaux identifiants.
Capacités du fournisseur (Slack)
Section intitulée “Capacités du fournisseur (Slack)”La passerelle est écrite contre la surface la plus riche et se dégrade une capacité à la fois.
| Capacité | Slack | Effet en son absence |
|---|---|---|
streaming |
oui | La réponse entière est publiée une seule fois, à la fin |
streamingRequiresThread |
oui | Une cible de réponse sans ancre de fil publie des réponses entières |
ephemeralMessages |
oui | Une réponse à une commande slash sans poignée de réponse utilisable est abandonnée (les refus sont toujours publiés dans le fil) |
threadStatus |
oui | Aucune ligne transitoire « is thinking… » |
threadTitle |
oui | Le fil de clavardage conserve son titre par défaut |
slashCommands |
oui | Aucune surface /cc |
taskCards |
oui | Texte seulement — aucune carte au-dessus de la réponse et aucun lien « View in Control Center » |
maxMessageLength |
39 000 | Les réponses plus longues sont tronquées, avec un renvoi vers Control Center |
maxStreamChunkLength |
3 800 | Les ajouts sont scindés à la frontière d’un paragraphe, d’une ligne ou d’un espace |
Annoncer une diffusion n’est pas la promettre : celle de Slack exige la fonctionnalité Agents & AI Apps d’un forfait payant. Un refus au moment de l’appel (streaming_not_supported, feature_not_enabled, paid_only, …) est mémorisé pour toute la durée de la connexion et exposé sous streamingAvailable: false. Les cartes de tâche de Slack empruntent le même appel, de sorte qu’un Slack qui refuse le mode d’affichage de la carte conserve son flux et ne perd que la carte, une seule fois, pour toute la durée de la connexion.
Un flux doit aussi nommer le lecteur auquel il est destiné. Slack exige à la fois un utilisateur destinataire et son équipe pour en ouvrir un dans un canal (un message privé ne le permet pas) et refuse avec missing_recipient_user_id / missing_recipient_team_id dans le cas contraire — un refus par appel, de sorte que la passerelle continue d’essayer et que, entre-temps, le tour arrive sous forme d’une seule réponse publiée. Le destinataire est la personne qui a demandé, d’après le message entrant (son équipe, et non celle de l’application, pour qu’un canal Slack Connect se résolve correctement). Pour un tour que personne n’a lancé depuis le clavardage — du travail amorcé dans Control Center sur une conversation déjà reliée —, il s’agit du membre dont la liaison a créé la passerelle ; s’il a depuis délié son compte de clavardage, ce tour est publié en entier plutôt que diffusé en continu.
Cartes de tâche
Section intitulée “Cartes de tâche”Une carte groupée par tour d’agent (task_display_mode: plan), avec une ligne par étape. Chaque ligne est un task_update composé de id + title + status, plus un details ponctuel quand la réflexion est prête. Les details et l’output de même id se concatènent sans séparateur, donc les details sont envoyés une seule fois et l’output n’est jamais diffusé en continu. Le titre du plan est la demande ; la réponse est un ajout distinct et ultérieur portant un segment markdown_text.
| Partie | Contenu |
|---|---|
| Titre du plan | Ce qui a été demandé |
| Ligne de préparation | Working on it…, puis Cloning …, puis Starting the agent… — une seule ligne, dont le titre est remplacé |
| Ligne de réflexion | Thinking…, ajoutée quand l’agent commence à raisonner ; la réflexion est le details de cette ligne, envoyé une seule fois à la fin du raisonnement |
| Lignes d’outil | Une par outil (Bash which swift), avec un id unique pour qu’elles se placent côte à côte |
| État | En cours pendant que cette ligne s’exécute ; terminée quand la ligne suivante démarre ou que le tour se termine ; en erreur en cas d’échec, d’interruption ou de limite de tours atteinte |
| Appel à l’action | View in Control Center comme ligne finale quand le tour se termine — absente tant que le travail est en cours et absente quand le serveur n’a aucune URL publique joignable |
Ce qu’une carte ne porte jamais : la sortie des outils, un argument hors de cette liste d’autorisation, ou quoi que ce soit de lu dans la base de données. La prose de raisonnement se limite aux détails de la ligne Thinking… (plafonnés à 256 caractères), et non au corps du message. Une carte qui ne dirait rien de nouveau n’est pas renvoyée. Une mention ouvre le flux immédiatement pour que le fil Slack ne reste pas silencieux ; un tour qui n’a ensuite produit aucun texte termine cette même carte par une courte note plutôt que de publier un second message. Un tour sans paroles sur un espace qui n’a jamais ouvert de flux (aucune carte, ou diffusion déjà refusée) publie quand même un message portant la carte terminée et la note.
Les cartes diffusées et les cartes publiées sont des charges utiles différentes :
| Forme | Structure | Prose |
|---|---|---|
| Diffusée | plan_update plus des segments task_update ayant id pour clé |
Chaîne simple, plafonnée à 256 caractères par segment |
| Publiée | Un bloc plan enveloppant des task_card ayant task_id pour clé |
Texte enrichi |
Gestion des défaillances :
| Situation | Comportement |
|---|---|
| Le fournisseur refuse la carte lors d’un ajout | L’ajout est réessayé immédiatement sans la carte ; la prose continue d’arriver en direct et les cartes sont abandonnées pour toute la durée de la connexion |
| Le fournisseur refuse aussi la prose | Le flux est fermé et la réponse est publiée en entier |
| Le flux se brise après qu’une partie de la réponse est arrivée | Le flux est fermé et rien n’est republié |
Voir Passerelles de clavardage pour comprendre pourquoi la charge utile est scindée ainsi.
La préparation, avant qu’il y ait un tour
Section intitulée “La préparation, avant qu’il y ait un tour”La carte s’ouvre quand une mention est acceptée, avant le clonage et avant le premier jeton ; le tour qui suit adopte la même carte et le même flux. La narration qui vient ensuite provient de SpaceProvisioningChanged sur le bus d’événements de domaine — la même progression que la bannière de préparation de l’application de bureau lit sur la ligne de l’espace.
| État de préparation | Narration |
|---|---|
| Mention acceptée | Working on it… |
Étape repo |
Cloning <repo>… |
Étape prCheckout |
Checking out the pull request in <repo>… |
Étape script |
Running the setup script for <repo>… |
Étape agent |
Setting up <agent>… |
provisioning, aucune étape encore |
Preparing the workspace… |
ready |
Starting the agent… |
| L’agent raisonne | Thinking… |
failed |
Workspace setup failed — details are in Control Center. |
cancelled |
Workspace setup was stopped — details are in Control Center. |
La narration de préparation exige les quatre conditions suivantes :
| Condition |
|---|
| Le fournisseur affiche des cartes |
| La diffusion en continu est disponible sur cette connexion |
| L’espace a une demande de clavardage en cours |
| Aucune carte n’y rend déjà compte d’un tour |
La demande reste en cours jusqu’à ce qu’un tour démarre ou que la préparation échoue. Un événement ready sans carte déjà ouverte correspond à un espace déjà chaud : il est laissé silencieux, et une préparation que personne n’attend dans le clavardage — une conversation ouverte dans l’application, une conversation abandonnée reprise au démarrage — ne produit aucune carte.
La page de rebond /open
Section intitulée “La page de rebond /open”Les produits de clavardage n’acceptent que http(s) dans un lien et rien d’autre, de sorte qu’un appel à l’action ne peut pas nommer directement le schéma de l’application de bureau.
| Propriété | Valeur |
|---|---|
| Route | GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id> |
| Origine | Dérivée de l’URL publique du serveur (ws → http, wss → https) ; le loopback est conservé |
| Réponse | Une petite page HTML no-store qui navigue vers control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> et affiche un bouton manuel |
| Validation | Les identifiants doivent correspondre à [A-Za-z0-9_.-]{1,128} ; tout le reste est un 404 |
| Accès | Public et inerte — elle ne lit aucune base de données, ce n’est donc pas un oracle d’existence ; l’application qu’elle ouvre fait l’autorisation |
Champs du profil du robot
Section intitulée “Champs du profil du robot”| Champ | Limite | Remarques |
|---|---|---|
appName |
1–35 caractères | Par défaut : Control Center · <workspace name> |
botDisplayName |
1–80 caractères | Par défaut : control-center |
description |
≤ 140 caractères | |
agentDescription |
≤ 300 caractères | Affiché au-dessus d’un message privé avec le robot |
command |
≤ 32 caractères | Normalisé : les barres obliques initiales sont retirées, le tout mis en minuscules, [^a-z0-9_-] supprimé, et une valeur vide revient à cc |
agentEnabled |
— | Indique si l’expérience de message privé et d’agent est exposée |
Une case vide signifie « laisser tel quel », et non « effacer ». Les limites sont celles de Slack, qui sont les plus serrées parmi les produits modélisés.
Codes de liaison
Section intitulée “Codes de liaison”| Propriété | Valeur |
|---|---|
| Longueur | 6 caractères |
| Alphabet | 23456789ABCDEFGHJKLMNPQRSTVWXYZ (sans 0/O, 1/I, U) |
| Durée de vie | 15 minutes |
| Utilisations | Une |
| Stockage | En mémoire — un redémarrage du serveur invalide les codes en circulation |
| Portée | Un espace de travail et un fournisseur ; la création d’un nouveau code invalide le précédent du membre |
L’utilisation d’un code est vérifiée sans égard à la casse. Les codes inconnus, expirés, propres à un autre espace de travail et destinés à un autre fournisseur échouent tous de façon identique, à dessein.
Méthodes de liaison
Section intitulée “Méthodes de liaison”| Méthode | Valeur transmise | Fonctionnement |
|---|---|---|
| Code | code |
Le membre a utilisé /cc link <code> |
| Courriel | email |
Le courriel vérifié du fournisseur correspondait à un utilisateur déjà membre de l’espace de travail |
Opérations RPC
Section intitulée “Opérations RPC”Toutes sont limitées à un espace de travail. Le serveur est sans état, donc chaque appel porte son propre workspace_id dans les arguments ; le répartiteur refuse une opération à portée d’espace de travail qui l’omet, avant même qu’une base de données ne soit ouverte. Les identifiants ne circulent que vers l’intérieur — les lectures d’état ne les renvoient jamais.
| Opération | Type | Rôle minimal | Classes d’action |
|---|---|---|---|
chat.providers |
lecture | guest |
— |
chat.status |
lecture | guest |
— |
chat.connect |
mutation | admin |
secretAccess, networkEgress |
chat.disconnect |
destructif | admin |
secretAccess |
chat.listUserLinks |
lecture | guest |
— |
chat.beginUserLink |
mutation | member |
— |
chat.unlinkUser |
mutation | member pour vous-même, admin pour quiconque d’autre |
— |
chat.createApp |
mutation | admin |
secretAccess, networkEgress |
chat.setupLink |
lecture | admin |
— |
chat.botProfile |
lecture | admin |
networkEgress |
chat.updateBotProfile |
mutation | admin |
networkEgress |
Les opérations de lecture héritent du plancher guest de leur type ; les mutations sans plancher explicite exigent member.
chat.setupLink compose une URL de création d’application du fournisseur à partir du profil du robot. Elle ne déclare aucune classe d’action parce qu’elle ne lit aucun secret et ne lance aucun appel — c’est le navigateur de l’utilisateur qui fait le seul appel.
Une requête réactive complète la surface :
| Requête | Type | Émet |
|---|---|---|
chat.watchUserLinks |
surveillance | La liste, renvoyée chaque fois qu’une liaison est créée ou supprimée |
Elle existe parce que la liaison se fait de l’autre côté : un membre tape le code à usage unique dans le robot, de sorte qu’aucune réponse de client ne peut rapporter le résultat. La liste des liaisons dans les paramètres et la boîte de dialogue « Lier mon compte » ouverte la suivent toutes les deux, et la boîte de dialogue bascule vers une confirmation dès que la liaison du membre arrive.
chat.beginUserLink crée toujours un code pour l’identité de l’appelant lui-même. chat.updateBotProfile ne renvoie un remaining_step que lorsqu’il reste une étape (typiquement une réinstallation) ; son absence signifie que la modification est active.
chat.providers renvoie une entrée par fournisseur offert — le descripteur plus, s’il existe, l’état de la connexion. Tout ce qu’affiche l’interface des paramètres provient de cette charge utile, et c’est pourquoi ajouter un fournisseur est un changement qui ne touche que le serveur.
Stockage
Section intitulée “Stockage”| Quoi | Où |
|---|---|
| Identifiants de connexion | <dataDir>/<workspaceId>/chat_credentials/<provider>.json |
| Configuration guidée inachevée | <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json |
| Fichiers Slack hérités | <dataDir>/<workspaceId>/slack_credentials.json et slack_app_setup.json, lus une fois et migrés sur place |
Les identifiants vivent à côté de la base de données de l’espace de travail, et non dedans : supprimer un espace de travail emporte ses jetons de clavardage, et workspace.export ou une sauvegarde copie workspace.db — un espace de travail transmis à quelqu’un d’autre ne porte donc aucun jeton de robot actif. Les écritures sont atomiques (fichier temporaire puis renommage) et restreintes au propriétaire.
Tables de la base de données
Section intitulée “Tables de la base de données”Les deux vivent dans la base de données de l’espace de travail, qui porte une base de référence v1 écrasée — une lecture inter-espaces ne compile donc pas.
chat_space_links — une conversation externe ↔ un espace Control Center :
| Colonne | Remarques |
|---|---|
id |
UUID v4 |
workspace_id |
Portée de l’espace de travail |
provider |
ChatProvider.wire |
external_team_id |
Équipe Slack T…, vide pour les fournisseurs qui n’ont pas ce concept |
external_channel_id |
Canal Slack C…, groupe G…, message privé D… |
external_thread_id |
Ancre de fil Slack, ou null quand toute la conversation est l’ancre (messages privés du robot) |
cc_space_id |
REFERENCES spaces(id) ON DELETE CASCADE |
created_by_user_id, created_at, last_activity_at |
Unique sur (workspace_id, provider, external_channel_id, external_thread_id), avec un index partiel qui impose la même chose pour les identifiants de fil nuls (SQLite considère les NULL comme distincts).
chat_user_links — un membre externe ↔ un utilisateur Control Center :
| Colonne | Remarques |
|---|---|
id |
UUID v4 |
workspace_id |
Portée de l’espace de travail |
provider |
ChatProvider.wire |
external_team_id, external_user_id |
Membre Slack U… |
user_id |
Utilisateur Control Center (global, donc aucune clé étrangère) |
method |
code ou email |
linked_at |
Unique sur (workspace_id, provider, external_team_id, external_user_id) et sur (workspace_id, provider, user_id) — une attribution à valeur unique dans les deux sens, alors qu’une même personne peut être liée sur plusieurs fournisseurs.
Une liaison est une résolution d’identité, pas un octroi d’accès : l’appartenance est revérifiée à chaque événement entrant, de sorte qu’une liaison vers un membre retiré depuis se résout en refus.
Type d’espace et métadonnées des messages
Section intitulée “Type d’espace et métadonnées des messages”Un espace relié est créé avec le SpaceKind du fournisseur (slack). Il se comporte comme un espace ordinaire destiné aux humains — notifié et classé comme topic — et isExternalChat est le contrôle par lequel passe chaque comportement (mise en miroir, badge, rétention).
Chaque message que la passerelle fait entrer porte une estampille metadata['chat'] :
{ "chat": { "provider": "slack", "teamId": "T024BE7LD", "channelId": "C0G9QF9GW", "threadId": "1700000000.000100", "messageId": "1700000000.000200", "userId": "U024BE7LH", "userLabel": "sam" }}L’estampille sert aussi de garde contre l’écho : le miroir sortant saute tout message qui la porte, ce qui empêche un message de clavardage entrant d’être republié aussitôt.
Comportement du relais
Section intitulée “Comportement du relais”| Comportement | Détail |
|---|---|
| Ce qui est relayé vers l’extérieur | Le texte de l’agent seulement, plus la ligne d’état de la carte de tâche. La prose de raisonnement et la sortie des outils restent dans Control Center |
| Cadence de diffusion | Limitée à une poussée par seconde pendant qu’un tour s’exécute ; la carte et le texte qu’elle explique voyagent dans le même appel |
| Messages humains tapés dans Control Center | Mis en miroir dans le fil lié, précédés du nom d’affichage de l’auteur |
| Tours vides | Signalés par une courte note (exécution échouée, interrompue ou arrêtée à sa limite de tours) plutôt que par le silence |
| Dédoublonnage | Fondé sur l’id d’événement propre au fournisseur ; les 512 derniers sont mémorisés |
| Agent de premier contact | Une nouvelle conversation reliée réveille l’agent CEO de l’espace de travail, sinon son premier agent, sinon personne |
| Nommage des espaces | #channel · <request> pour un fil de canal Slack, Slack DM · <request> pour un message privé, titres plafonnés à 48 caractères |
| Isolation des défaillances | Un mauvais événement ne fait jamais tomber la passerelle ; la connexion continue de servir toutes les autres conversations |
Voir aussi
Section intitulée “Voir aussi”- Configurer l’intégration Slack
- Lier votre compte Slack
- Personnaliser le robot de clavardage
- Passerelles de clavardage
- Garde-fous pour les classes d’action que déclarent les opérations