Collaboration
Publier un message Cortex pour une exécution d'automatisation, dans le DM Cortex d'une personne ou dans un salon (un canal d'espace de travail ou le General d'un espace de travail), par une seule opération gouvernée
action.collaboration.post permet à une exécution d'automatisation (un
workflow, un flux de processus ou un tour Cortex en direct) d'envoyer un message Cortex à
une personne, dans son propre DM Cortex, ou à un salon : un canal d'espace de
travail ou le General d'un espace de travail.
Cortex en est l'expéditeur, et le message porte une trace de l'exécution qui
l'a publié. L'identité Run As de l'exécution, une
personne ou un compte de service, détermine où l'exécution peut publier : la
plateforme tire cette identité, l'organisation et la classification de
l'exécution elle-même, jamais de vos paramètres.
Votre code doit s'exécuter dans une exécution d'automatisation : une exécution
de workflow, de flux de processus ou de tour Cortex en direct. Un appel fait avec
un simple jeton porteur, en dehors de toute exécution, est refusé avec
FORBIDDEN : run_as_required lorsque le jeton ne désigne aucune identité Run
As, et not_an_automation_run sinon. L'exemple montre la forme de l'appel.
import { createHttpPlatformTransport, createScrydonSDK } from '@scrydon/sdk/platform'
const platform = createScrydonSDK(
createHttpPlatformTransport({
baseUrl: 'https://scrydon.com',
headers: () => ({ authorization: `Bearer ${accessToken}` }),
})
)
const { postId, status } = await platform.action.collaboration.post({
target: { kind: 'person', userId: 'user_123' },
markdown: 'Votre rapport hebdomadaire est prêt.',
idempotencyKey: 'weekly-report',
})| Opération | HTTP | Ce qu'elle fait |
|---|---|---|
action.collaboration.post | POST /api/platform/v1/action/collaboration/post | Publie un message Cortex pour l'exécution appelante. Requiert tools:write. La plateforme ne réessaie jamais l'appel à votre place. |
collaboration.recipients.list | POST /api/platform/v1/collaboration/recipients/list | Liste, page par page, les personnes ou les salons où un message Cortex peut être envoyé. Requiert tools:read. Elle n'envoie jamais rien. Voir Lister les destinataires possibles. |
status vaut published lorsque le message est dans le salon, ou queued
lorsque le premier envoi n'a pas abouti. La plateforme réessaie alors, à au
moins 30 secondes d'intervalle et en cinq tentatives au plus, toujours sous la
forme du même message, de sorte que le salon ne l'affiche qu'une fois. Si
aucune tentative n'aboutit, la publication est abandonnée. L'appel qui l'a mise
en file n'en est jamais informé : un appel ultérieur avec la même
idempotencyKey répond en général PROVIDER_UNAVAILABLE, comme décrit dans
Erreurs.
Disponibilité
Publier suppose que le service de messagerie Cortex s'exécute dans votre
déploiement, et que Cortex soit activé et prêt pour votre organisation.
L'opération répond CAPABILITY_NOT_CONFIGURED (HTTP 409) et ne publie rien
dans deux cas :
- Le service ne s'exécute pas dans ce déploiement. L'erreur n'a pas de
details.refusal. Elle dure jusqu'à ce que l'administrateur du déploiement active le service, hormis une courte fenêtre pendant le démarrage d'un déploiement. - Le service s'exécute, mais Cortex n'est pas activé pour votre organisation,
ou n'est pas encore prêt pour elle. L'erreur porte
details.refusalcollaboration_not_enabled. Elle dure normalement jusqu'à l'activation de Cortex pour l'organisation, mais elle apparaît aussi brièvement pendant que la connexion de Cortex à l'organisation démarre.
Écrivez votre code pour gérer les deux réponses.
Cibles
kind | Champ | Où arrive le message |
|---|---|---|
person | userId | Le propre DM Cortex de la personne. L'identité Run As n'y est jamais ajoutée. |
channel | channelId | Un canal d'espace de travail que l'identité Run As a rejoint. |
workspace_general | workspaceId | Le salon General de cet espace de travail, si l'identité Run As l'a rejoint. |
team | teamId | Non disponible pour l'instant. Une équipe n'a plus de salon : cette cible est refusée avec unknown_target. |
Une personne doit être un membre actif de votre organisation à qui Cortex peut
écrire. Si elle n'a pas encore de DM Cortex, la plateforme en ouvre un pour elle.
Un appel ne peut pas distinguer une personne inconnue d'une personne à qui Cortex
ne peut pas écrire : les deux reçoivent recipient_not_messageable.
Un salon doit appartenir à votre organisation et avoir un salon Cortex mis en
place, et le salon doit montrer l'identité Run As comme ayant rejoint, de sorte
qu'une exécution ne peut pas publier là où son identité Run As ne le pourrait
pas. Un canal doit en plus être actif : créé par un administrateur dans un
espace de travail, et non archivé. Chaque type de salon refuse une identité Run
As qui ne l'a pas rejoint avec le même not_a_channel_member.
Un compte de service rejoint les canaux et le General de chaque espace de travail auquel il appartient, et ceux d'aucun autre. Un compte de service peut aussi publier à une personne : Cortex envoie le message dans le propre DM Cortex de cette personne, et le compte n'est dans aucun salon pour cela.
Un message ne mentionne personne : @room, les identifiants et les liens
d'utilisateur présents dans markdown ne notifient personne, et un lien vers
une personne est affiché en texte brut.
Seule une exécution d'automatisation peut publier
Un appel qui ne fait pas partie d'une exécution de workflow, de flux de processus ou de
tour Cortex est refusé avec FORBIDDEN. Il en va de même d'une exécution sans
identité Run As, comme un workflow déployé par une extension sans propriétaire.
Lisez details.refusal pour savoir lequel des deux cas s'applique.
Idempotence
La même idempotencyKey au sein d'une exécution renvoie le résultat de la
première publication et ne publie jamais deux fois. La même clé avec un autre
texte ou une autre cible est refusée. Une clé appartient à son exécution :
l'exécution suivante du même workflow publie à nouveau sous la même clé. Si la
première publication a été abandonnée, la même clé répond en général cela à la
place (ou published, si son dernier envoi est confirmé plus tard) ; voir
Erreurs.
Limites
| Limite | Valeur |
|---|---|
markdown | 16 000 caractères au plus, et non vide |
idempotencyKey | De 1 à 200 caractères |
target.userId | De 1 à 256 caractères |
target.channelId | Un UUID |
target.workspaceId | Un UUID |
| Publications par organisation | 30 par minute |
| Publications par automatisation et destinataire | 10 par heure |
Un message Cortex a aussi une taille maximale une fois envoyé. Un texte riche en
caractères multi-octets peut l'atteindre avant les 16 000 caractères ; l'appel
est alors refusé avec over_budget.
Gouvernance
Le texte est contrôlé avant toute publication. Si la prévention des pertes de
données (DLP) de votre organisation devait le modifier, la publication est
refusée avec POLICY_BLOCKED et rien n'est publié : la plateforme ne publie
jamais un message réécrit à votre place. Une publication est également refusée
avec POLICY_BLOCKED lorsque sa classification n'est pas couverte par le salon
où le message arriverait. La publication a la classification que l'exécution
déclare, ou la classification des données non étiquetées de votre organisation
lorsque l'exécution n'en déclare aucune. Chaque décision, autorisée ou refusée,
est inscrite dans le journal d'audit avant le retour de l'appel, sans le texte.
Seule une lecture ou une écriture qui échoue interrompt la décision : si la
prévention des pertes de données ne peut pas décider, si le schéma de
classification de votre organisation ou l'état du salon ne peut pas être lu, ou
si l'enregistrement d'audit ne peut pas être écrit, l'appel est un
PROVIDER_UNAVAILABLE réessayable. Un salon sans
étiquette de classification est tenu à la classification des données non
étiquetées de votre organisation. Un salon dont l'étiquette a été supprimée ou ne
peut pas être lue est tenu à la classification la plus basse de votre schéma
(rang 0, PUBLIC sur le schéma par défaut ; voir
Classification et habilitation) : une
publication classifiée au-dessus est alors refusée avec POLICY_BLOCKED et details.policyCode
EGRESS_BLOCKED_BY_CLEARANCE, et un nouvel essai n'y change rien.
Erreurs
Chaque échec est une PlatformApiError portant un code stable. Un refus porte
aussi sa raison dans details.refusal, qui est stable et sur laquelle vous
pouvez vous appuyer pour brancher votre code.
| Code | HTTP | details.refusal | Quand |
|---|---|---|---|
FORBIDDEN | 403 | not_an_automation_run | L'appel ne fait pas partie d'une exécution de workflow, de flux de processus ou de tour Cortex. |
FORBIDDEN | 403 | run_as_required | L'exécution n'a pas d'identité Run As. |
FORBIDDEN | 403 | run_as_service_account_unavailable | Le compte de service Run As n'est pas activé, n'existe plus, ou n'a pas encore d'identité Cortex. |
FORBIDDEN | 403 | run_as_not_a_person | La personne Run As n'est pas un membre actif de l'organisation, ou n'apparaît pas encore dans l'annuaire Cortex de celle-ci. |
FORBIDDEN | 403 | recipient_not_messageable | La personne n'est pas quelqu'un à qui Cortex peut écrire dans cette organisation, ou n'existe pas. Les deux cas ne sont pas distingués. |
FORBIDDEN | 403 | not_a_channel_member | L'identité Run As n'a pas rejoint ce salon. Chaque type de salon répond avec ce code. |
INVALID_REQUEST | 400 | unknown_target | Le salon n'existe pas dans l'organisation : un canal qui n'existe pas ou a été archivé, ou un espace de travail dont le salon Cortex n'est pas en place ou a été retiré. Une cible team reçoit aussi ce code, car les cibles d'équipe ne sont pas disponibles pour l'instant. |
INVALID_REQUEST | 400 | over_budget | Le message est trop volumineux pour être envoyé comme un seul message Cortex. |
INVALID_REQUEST | 400 | idempotency_key_reused | La clé a déjà servi dans cette exécution pour un autre texte ou une autre cible. |
INVALID_REQUEST | 400 | aucune | Le corps ne respecte pas le contrat, par exemple un markdown vide. |
CAPABILITY_NOT_CONFIGURED | 409 | collaboration_not_enabled | Cortex n'est pas activé ou pas prêt pour l'organisation ; voir Disponibilité. |
CAPABILITY_NOT_CONFIGURED | 409 | channel_not_bound | Le canal est actif mais n'a pas encore de salon Cortex. Une cible workspace_general dans cet état reçoit unknown_target. |
CAPABILITY_NOT_CONFIGURED | 409 | cortex_not_in_room | Il manque au salon l'un des membres propres de Cortex, donc Cortex ne peut pas y publier. |
CAPABILITY_NOT_CONFIGURED | 409 | aucune | Le service de messagerie Cortex ne s'exécute pas dans ce déploiement ; voir Disponibilité. |
POLICY_BLOCKED | 422 | aucune | Une décision de gouvernance, comme décrit dans Gouvernance. details.policyCode vaut LLM_BLOCKED_BY_DLP lorsque la prévention des pertes de données a refusé le texte, et EGRESS_BLOCKED_BY_CLEARANCE lorsque la classification du salon ne couvre pas la publication. |
PROVIDER_UNAVAILABLE | 503 | aucune | Cortex n'a pas pu contrôler ou enregistrer la publication pour le moment : la prévention des pertes de données n'a pas pu décider, le schéma de classification de votre organisation n'a pas pu être lu, l'état du salon n'a pas pu être lu, le destinataire ou son DM Cortex n'a pas pu être consulté ou ouvert, ou l'enregistrement d'audit n'a pas pu être écrit. retryable vaut true. Lorsque la prévention des pertes de données n'a pas pu décider, details.unavailable en donne la raison : policy_not_provisioned dure jusqu'à ce qu'un administrateur provisionne une politique de prévention des pertes de données, tandis que policy_load_failed et dlp_scan_failed signifient que la politique n'a pas pu être chargée ou que l'analyse a échoué. Réessayez avec la même idempotencyKey : si le premier appel a bien enregistré la publication, vous récupérez celle-ci, pas une seconde. |
PROVIDER_UNAVAILABLE | 503 | aucune | La publication a été admise, mais chacune de ses cinq tentatives a échoué, et la dernière a pu atteindre le salon. retryable vaut false et details.postId la désigne. Elle peut être ou ne pas être dans le salon : vérifiez avant de publier à nouveau. Un nouvel essai sous la même idempotencyKey renvoie en général à nouveau cette réponse ; si l'envoi qui était encore en cours est confirmé plus tard, il répond published à la place. Une nouvelle clé crée une nouvelle publication, qui peut doubler celle-ci. |
PROVIDER_UNAVAILABLE | 503 | aucune | collaboration.recipients.list n'a pas pu lire l'annuaire Cortex, un stockage, le serveur d'accueil (homeserver), ou les noms des espaces de travail et des équipes de votre organisation. retryable vaut true. La liste ne répond jamais cela par une page vide. |
RATE_LIMITED | 429 | organization_rate, source_recipient_rate | La limite de l'organisation ou celle de l'automatisation et du destinataire est atteinte. retryable vaut true ; attendez retryAfter secondes. |
extension_permission_not_granted ne figure pas dans ce tableau, car aucun client ne la reçoit. Elle n'atteint qu'un outil d'extension qui appelle ctx.action.collaboration.post par le SDK de la plateforme, sous la forme d'une Error dont le message commence par extension_permission_not_granted:. L'extension n'a pas déclaré l'autorisation cortex.messages.send, ou le produit sous lequel son outil s'exécute n'est pas activé pour l'organisation. Un appelant de cette opération par @scrydon/sdk/platform ne la reçoit jamais. Voir Autorisations de la plateforme.
import { PlatformApiError } from '@scrydon/sdk/platform'
try {
await platform.action.collaboration.post({ target, markdown, idempotencyKey })
} catch (err) {
if (err instanceof PlatformApiError && err.code === 'FORBIDDEN') {
// err.details?.refusal donne la raison, par exemple 'not_a_channel_member'.
}
if (err instanceof PlatformApiError && err.code === 'PROVIDER_UNAVAILABLE' && !err.retryable) {
// err.details?.postId désigne une publication peut-être livrée, peut-être pas.
}
}Lister les destinataires possibles
Cette même page couvre aussi la lecture qui alimente les sélecteurs de l'éditeur
de workflow. collaboration.recipients.list renvoie, page par page, les
personnes à qui un message Cortex peut être envoyé, ou les salons que
l'appelant a rejoints. Les sélecteurs Person et Channel du bloc Cortex
l'utilisent, et Messages des automatisations les
présente dans l'éditeur. Elle requiert tools:read, n'envoie jamais rien, et
peut être réessayée sans risque.
const page = await platform.collaboration.recipients.list({
kind: 'people',
search: 'ada',
limit: 25,
})
// page.items : [{ id: 'user_123', label: 'Ada Lovelace' }]
// page.nextCursor : une chaîne tant qu'il peut y avoir une suite, sinon null| Champ | Signification |
|---|---|
kind | people, ou channels pour tous les types de salon. |
search | Restreint la liste. De 1 à 128 caractères une fois la saisie nettoyée de ses espaces. |
limit | De 1 à 50 éléments. 25 par défaut. |
cursor | Le nextCursor de la page précédente, renvoyé tel quel. |
ids | Recherche des destinataires nommés au lieu de paginer ; voir Rechercher des destinataires par identifiant. |
Chaque élément porte un id et un label. Pour une personne, l'id est le
userId que prend une publication. Pour un salon, l'id est la clé du salon,
qui en indique le type :
| Salon | id | Cible qu'il désigne |
|---|---|---|
| Un canal d'espace de travail | L'UUID du canal | { kind: 'channel', channelId } |
| Le General d'un espace de travail | general: suivi de l'UUID de l'espace de travail | { kind: 'workspace_general', workspaceId } |
parseCollaborationRoomSelection(id), de @scrydon/sdk/platform, transforme
l'id d'un salon en sa cible, et renvoie null pour une chaîne qui ne désigne
aucun salon. collaborationRoomSelectionKey(target) en est l'inverse. La clé
d'un canal est son identifiant nu, de sorte qu'une étape qui a enregistré un
canal avant que les General soient listés désigne toujours le même canal. Une
clé team: déjà enregistrée est toujours reconnue ; publier vers elle est
refusé avec unknown_target tant que les cibles de canal d'organisation ne
sont pas disponibles.
Le label est le nom à afficher : le nom d'affichage d'une personne, ou
Unnamed person si elle n'en a pas ; le nom d'un canal ; et General pour le
General d'un espace de travail. L'identifiant Matrix d'une personne n'est jamais listé, et deux
personnes qui portent le même nom ont le même label et ne diffèrent que par
leur id.
Un élément peut aussi porter un detail, à afficher à côté du label. Un canal
et le General d'un espace de travail portent le nom de leur espace de travail.
Une personne n'en porte aucun. Le detail est en anglais.
Qui est listé
- Les personnes. Les personnes actives de votre organisation à qui Cortex
peut écrire, c'est-à-dire celles qu'une publication accepte comme
userId. L'appelant figure dans la liste, de sorte qu'une étape peut écrire à la personne qui l'a rédigée. Les agents de Cortex et les identités extérieures à votre organisation en sont exclus. Figurer dans la liste ne garantit pas qu'une publication vers cette personne sera acceptée : la publication contrôle son unique destinataire à son exécution, et peut encore répondrerecipient_not_messageable. - Les salons (
channels). Les salons qu'une publication accepterait, listés dans cet ordre : les canaux d'espace de travail, puis les General des espaces de travail. Un canal ne doit pas être archivé, et chaque salon doit avoir un salon Cortex en place. La publication et la liste partagent un seul filtre, donc la liste propose ce qu'une publication accepte. Un salon n'est listé que si l'appelant l'a rejoint et que Cortex y est. Une recherchesearchporte sur le nom d'un canal, ou sur le nom d'un General ou celui de son espace de travail, sans tenir compte de la casse.
La liste est celle de l'appelant, pas celle de l'exécution. Elle est lue pour la
personne qui édite, alors qu'une exécution publie ensuite avec son identité Run
As. Un salon que l'appelant peut choisir peut encore être refusé avec
not_a_channel_member à l'exécution de l'étape, si l'identité Run As ne l'a pas
rejoint.
Paginer jusqu'à ce que le curseur soit null
Une page peut contenir moins d'éléments que limit, ou aucun, et porter quand
même un nextCursor. Le curseur suit la lecture des personnes ou des salons, et
une ligne que l'appelant ne doit pas se voir proposer est écartée après cette
lecture. Pour les salons, c'est fréquent : une page de 50 salons peut ne contenir
que les quelques salons que l'appelant a rejoints. Une page de salons peut aussi
s'arrêter là où un type de salon se termine et où le suivant commence. Continuez
à demander des pages jusqu'à ce que nextCursor soit null ; ne prenez pas une
page courte pour la fin. L'ordre est stable, de sorte que la pagination ne
répète ni ne saute jamais un salon qui n'a pas changé entre-temps.
Rechercher des destinataires par identifiant
Passez ids pour retrouver les destinataires qu'une étape stocke déjà, par
exemple pour afficher le nom derrière un identifiant enregistré. Il accepte au
plus 50 identifiants et ne se combine pas avec search ni cursor. La réponse
ne porte jamais de curseur : nextCursor vaut null. Un identifiant que la
liste ne proposerait pas, parce qu'il ne désigne personne, une personne à qui
Cortex ne peut pas écrire, ou un salon que l'appelant n'a pas rejoint, est omis
de la réponse, de sorte que rien n'est révélé à son sujet. Pour un salon, passez
l'id de l'élément (sa clé), pas un identifiant nu d'espace de travail.
Un compte de service ne liste rien
La liste est lue comme une personne. Un appel fait en tant que compte de service, sans utilisateur délégué, renvoie une page vide. Il en va de même d'un appel pour une personne qui n'est pas encore dans l'annuaire de Cortex. Lorsqu'un appel désigne à la fois un utilisateur et un compte de service, la liste est lue comme l'utilisateur.
Erreurs de la liste
La liste partage le tableau des Erreurs ci-dessus :
CAPABILITY_NOT_CONFIGUREDavecdetails.refusalcollaboration_not_enabledlorsque Cortex n'est pas activé ou pas prêt pour l'organisation.CAPABILITY_NOT_CONFIGUREDsansdetails.refusallorsque le service de messagerie Cortex ne s'exécute pas dans ce déploiement. Les deux cas sont décrits sous Disponibilité.PROVIDER_UNAVAILABLE, réessayable, lorsque l'annuaire, un stockage, le serveur d'accueil, ou les noms des espaces de travail et des équipes n'ont pas pu être lus. La liste ne répond jamais cela par une page vide, car une page vide se lit comme personne à qui écrire.INVALID_REQUESTpour un corps qui ne respecte pas le contrat, commeidscombiné avecsearch, ou uncursorqui n'a pas la forme que la liste émet.