Ponte de chat
Fornecedores
Secção intitulada “Fornecedores”| Fornecedor | Valor serializado | Transporte | Tipo de espaço |
|---|---|---|---|
| Slack | slack |
Socket Mode (WSS de saída) | slack |
O Slack é o único fornecedor. ChatProvider é um enum de valor único e o cc_server regista um plugin. O esquema, todas as consultas e todos os índices únicos são indexados por provider, pelo que os ids nunca atravessam fornecedores e acrescentar um é uma alteração apenas no servidor.
Uma aplicação do lado do fornecedor por espaço de trabalho × fornecedor.
Comandos de chat
Secção intitulada “Comandos de chat”O nome do comando é por aplicação (cc por predefinição, logo /cc). Um administrador pode renomeá-lo; a ponte passa então a usar aquele com que foi invocado pela última vez.
| Comando | Efeito |
|---|---|
/cc help (ou /cc sem verbo) |
Lista o que o bot consegue fazer |
/cc link <code> |
Resgata um código de uso único e associa a identidade de chat do remetente |
/cc ticket <title> |
Abre um ticket local reportado pelo membro associado |
/cc ticket <title> | <description> |
O mesmo, com tudo o que vem depois do primeiro | como descrição |
| Verbo desconhecido | Reporta o verbo desconhecido juntamente com o texto de ajuda |
Formas de chegar a um agente:
| Gesto | Comportamento |
|---|---|
@bot … num canal |
Abre uma thread do Slack e um espaço correspondente no Control Center; o agente responde na thread do Slack |
| Resposta numa thread do Slack com ponte | Continua o mesmo espaço do Control Center — não é precisa nenhuma menção |
| Mensagem simples num canal | Ignorada (não é dirigida ao bot nem está numa thread do Slack com ponte) |
| Mensagem direta ao bot | Um espaço do Control Center por conversa por mensagem direta; define um título de thread do Slack e um estado «is thinking…» enquanto o agente trabalha |
Campos de credenciais (Slack)
Secção intitulada “Campos de credenciais (Slack)”Declarados pelo descritor do fornecedor, que é o que gera a caixa de diálogo de ligação e valida a submissão.
| Id do campo | Etiqueta | Obrigatório | Prefixo | Onde fica |
|---|---|---|---|---|
botToken |
Token OAuth de utilizador de bot | Sim | xoxb- |
OAuth & Permissions |
appToken |
Token ao nível da aplicação | Sim | xapp- |
Basic Information → App-Level Tokens, escopo connections:write |
configRefreshToken |
Token de atualização da configuração da aplicação | Não | xoxe |
App configuration tokens em api.slack.com/apps |
configRefreshToken é a credencial de gestão da aplicação: sem ela a ligação funciona, mas o bot não pode ser criado nem editado a partir do Control Center. Roda a cada utilização e o valor rodado é guardado de imediato.
Os valores são aparados, as chaves não declaradas são descartadas e uma incompatibilidade de prefixo é recusada com a mesma frase no cliente e no servidor. Os campos obrigatórios têm de estar presentes em cada ligação; um campo opcional deixado em branco mantém o que já estava guardado.
Requisitos da aplicação do Slack
Secção intitulada “Requisitos da aplicação do Slack”Escopos do token de bot:
app_mentions:readassistant:writechannels:historychat:writecommandsgroups:historyim:historyusers:readusers:read.emailSubscrições de eventos do bot:
app_home_openedapp_mentionmessage.channelsmessage.groupsmessage.imDefinições estruturais que o Control Center impõe quando cria ou edita a aplicação: Socket Mode ativado, nenhum URL de pedido, o separador Messages ativado e editável, o bot always_online, os escopos e eventos necessários fundidos no que a aplicação já tiver (nunca subtraídos) e um comando slash com a descrição File a ticket or link your Control Center account — o marcador que identifica o próprio comando do Control Center num manifesto exportado.
Estados da ligação
Secção intitulada “Estados da ligação”| Estado | Valor serializado | Significado |
|---|---|---|
| Não ligado | disconnected |
Nenhuma credencial guardada para este espaço de trabalho × fornecedor |
| A ligar… | connecting |
Credenciais guardadas, o transporte a estabelecer ou a restabelecer a ligação |
| Ao vivo | connected |
Transporte ativo e a receber eventos |
| Erro de ligação | error |
Guardadas mas a falhar; o erro do próprio fornecedor é mostrado ao lado |
A reconexão é um backoff exponencial com jitter limitado a 30 segundos. Uma rotação de socket iniciada pelo fornecedor reconecta prontamente sem agravar o backoff. Uma credencial rejeitada (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) ou um escopo em falta é terminal: a ponte deixa de tentar novamente até chegarem novas credenciais.
Capacidades do fornecedor (Slack)
Secção intitulada “Capacidades do fornecedor (Slack)”A ponte está escrita para a superfície mais rica e degrada capacidade a capacidade.
| Capacidade | Slack | Efeito quando ausente |
|---|---|---|
streaming |
sim | A resposta inteira é publicada uma só vez, no fim |
streamingRequiresThread |
sim | Um alvo de resposta sem âncora de thread publica respostas inteiras |
ephemeralMessages |
sim | Uma resposta a um comando slash sem um identificador de resposta utilizável é descartada (as recusas são sempre publicadas na thread) |
threadStatus |
sim | Nenhuma linha transitória «is thinking…» |
threadTitle |
sim | A thread de chat mantém o seu título predefinido |
slashCommands |
sim | Nenhuma superfície /cc |
taskCards |
sim | Apenas texto — nenhum cartão por cima da resposta e nenhuma ligação «View in Control Center» |
maxMessageLength |
39,000 | As respostas mais longas são truncadas com uma referência ao Control Center |
maxStreamChunkLength |
3,800 | Os acrescentos são divididos num limite de parágrafo, de linha ou de espaço |
Anunciar streaming não é prometê-lo: o do Slack precisa da funcionalidade Agents & AI Apps num plano pago. Uma recusa no momento da chamada (streaming_not_supported, feature_not_enabled, paid_only, …) é memorizada durante toda a vida da ligação e apresentada como streamingAvailable: false. Os cartões de tarefa do Slack viajam na mesma chamada, por isso um Slack que recuse o modo de apresentação do cartão mantém o seu fluxo e perde apenas o cartão, uma vez, durante toda a vida da ligação.
Um fluxo também tem de nomear o leitor a que se destina. O Slack exige tanto um utilizador destinatário como a respetiva equipa para abrir um fluxo num canal (uma mensagem direta não) e recusa com missing_recipient_user_id / missing_recipient_team_id caso contrário — uma recusa por chamada, pelo que a ponte continua a tentar e, entretanto, o turno chega como uma única resposta publicada. O destinatário é quem perguntou, retirado da mensagem recebida (a equipa do utilizador, não a da aplicação, para que um canal do Slack Connect seja resolvido corretamente). Para um turno que ninguém iniciou a partir do chat — trabalho lançado no Control Center sobre uma conversa que já tem ponte — é o membro cuja associação criou a ponte; se entretanto desassociou a sua conta de chat, esse turno é publicado inteiro em vez de ser transmitido em fluxo.
Cartões de tarefa
Secção intitulada “Cartões de tarefa”Um cartão agrupado por turno do agente (task_display_mode: plan), com uma linha por passo. Cada linha é um task_update de id + title + status, mais um details de uso único quando o pensamento está pronto. details e output com o mesmo id concatenam-se sem separador, pelo que os details são enviados uma vez e o output nunca é transmitido em fluxo. O título do plano é o pedido; a resposta é um acrescento posterior separado que transporta um fragmento markdown_text.
| Parte | Conteúdo |
|---|---|
| Título do plano | O que foi pedido |
| Linha de preparação | Working on it…, depois Cloning …, depois Starting the agent… — uma linha, com o título substituído |
| Linha de raciocínio | Thinking…, acrescentada quando o agente começa a raciocinar; o pensamento são os details desta linha, enviados uma vez quando o raciocínio termina |
| Linhas de ferramentas | Uma por ferramenta (Bash which swift), com id único para que fiquem lado a lado |
| Estado | Em curso enquanto essa linha corre; completo quando a próxima linha começa ou o turno termina; erro em caso de falha, interrupção e limite de turnos |
| Apelo à ação | View in Control Center como linha final quando o turno termina — ausente enquanto o trabalho ainda corre e ausente quando o servidor não tem nenhum URL público acessível |
O que um cartão nunca transporta: saída de ferramentas, um argumento fora dessa lista de autorizações, ou qualquer coisa lida da base de dados. A prosa de raciocínio são apenas os details da linha Thinking… (limitados a 256 caracteres), não o corpo da mensagem. Um cartão que não diria nada de novo não é reenviado. Uma menção abre o fluxo de imediato para que a thread do Slack não fique silenciosa; um turno que depois não produziu texto nenhum conclui esse mesmo cartão com uma nota curta em vez de publicar uma segunda mensagem. Um turno sem palavras num espaço que nunca abriu um fluxo (sem cartões, ou com o streaming já recusado) publica na mesma uma mensagem que transporta o cartão concluído e a nota.
Os cartões transmitidos em fluxo e os publicados são payloads diferentes:
| Forma | Estrutura | Prosa |
|---|---|---|
| Transmitido em fluxo | plan_update mais fragmentos task_update com a chave id |
Cadeia simples, limitada a 256 caracteres por fragmento |
| Publicado | Um bloco plan que envolve task_cards com a chave task_id |
Texto formatado |
Tratamento de falhas:
| Situação | Comportamento |
|---|---|
| O fornecedor recusa o cartão num acrescento | O acrescento é repetido de imediato sem ele; a prosa continua a chegar ao vivo e os cartões são descartados durante toda a vida da ligação |
| O fornecedor também recusa a prosa | O fluxo é fechado e a resposta é publicada inteira |
| O fluxo parte-se depois de parte da resposta ter chegado | O fluxo é fechado e não é republicado nada |
Ver Pontes de chat para saber por que razão o payload é dividido desta forma.
A preparação, antes de haver um turno
Secção intitulada “A preparação, antes de haver um turno”O cartão abre quando uma menção é aceite, antes do clone e antes do primeiro token; o turno que se segue adota o mesmo cartão e o mesmo fluxo. A narração a partir daí vem de SpaceProvisioningChanged no barramento de eventos de domínio — o mesmo progresso que a faixa de preparação da aplicação para computador lê da linha do espaço.
| Estado de preparação | Narração |
|---|---|
| Menção aceite | Working on it… |
Passo repo |
Cloning <repo>… |
Passo prCheckout |
Checking out the pull request in <repo>… |
Passo script |
Running the setup script for <repo>… |
Passo agent |
Setting up <agent>… |
provisioning, ainda sem passo |
Preparing the workspace… |
ready |
Starting the agent… |
| Agente a raciocinar | Thinking… |
failed |
Workspace setup failed — details are in Control Center. |
cancelled |
Workspace setup was stopped — details are in Control Center. |
A narração da preparação exige as quatro condições:
| Condição |
|---|
| O fornecedor apresenta cartões |
| O streaming está disponível nesta ligação |
| O espaço tem um pedido de chat em curso |
| Nenhum cartão está já a reportar um turno aí |
O pedido permanece em curso até começar um turno ou falhar a preparação. Um evento ready sem nenhum cartão já aberto é um espaço já preparado e é deixado em silêncio, e uma preparação que ninguém no chat está à espera — uma conversa aberta na aplicação, uma que ficou pendente e é retomada no arranque — não produz cartão nenhum.
A página de redirecionamento /open
Secção intitulada “A página de redirecionamento /open”Os produtos de chat aceitam http(s) numa ligação e nada mais, por isso um apelo à ação não pode nomear diretamente o esquema da aplicação para computador.
| Propriedade | Valor |
|---|---|
| Rota | GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id> |
| Origem | Derivada do URL público do servidor (ws → http, wss → https); o loopback é mantido |
| Resposta | Uma pequena página HTML no-store que navega para control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> e mostra um botão manual |
| Validação | Os ids têm de corresponder a [A-Za-z0-9_.-]{1,128}; tudo o resto é um 404 |
| Acesso | Pública e inerte — não lê nenhuma base de dados, por isso não é um oráculo de existência; é a aplicação que abre que faz a autorização |
Campos do perfil do bot
Secção intitulada “Campos do perfil do bot”| Campo | Limite | Notas |
|---|---|---|
appName |
1–35 caracteres | Por predefinição, Control Center · <workspace name> |
botDisplayName |
1–80 caracteres | Por predefinição, control-center |
description |
≤ 140 caracteres | |
agentDescription |
≤ 300 caracteres | Mostrado por cima de uma mensagem direta ao bot |
command |
≤ 32 caracteres | Normalizado: barras iniciais removidas, convertido em minúsculas, [^a-z0-9_-] removido, e se ficar vazio volta a cc |
agentEnabled |
— | Se a experiência de mensagem direta/agente é exposta |
Uma caixa vazia significa «deixar como está», não «limpar». Os limites são do Slack, que são os mais restritos dos produtos modelados.
Códigos de associação
Secção intitulada “Códigos de associação”| Propriedade | Valor |
|---|---|
| Comprimento | 6 caracteres |
| Alfabeto | 23456789ABCDEFGHJKLMNPQRSTVWXYZ (sem 0/O, 1/I, U) |
| Validade | 15 minutos |
| Utilizações | Uma |
| Armazenamento | Em memória — um reinício do servidor invalida os códigos pendentes |
| Âmbito | Um espaço de trabalho e um fornecedor; emitir um novo código invalida o anterior do membro |
O resgate é validado sem distinguir maiúsculas de minúsculas. Códigos desconhecidos, expirados, de outro espaço de trabalho e de outro fornecedor falham todos de forma idêntica, de propósito.
Métodos de associação
Secção intitulada “Métodos de associação”| Método | Valor serializado | Como |
|---|---|---|
| Código | code |
O membro resgatou /cc link <code> |
email |
O email verificado do fornecedor correspondeu a um utilizador que já é membro do espaço de trabalho |
Operações RPC
Secção intitulada “Operações RPC”Todas têm âmbito de espaço de trabalho. O servidor não tem estado, por isso cada chamada transporta o seu próprio workspace_id nos argumentos; o despachante recusa uma operação com âmbito de espaço de trabalho que o omita, antes de abrir qualquer base de dados. As credenciais só viajam para dentro — as leituras de estado nunca as devolvem.
| Operação | Tipo | Papel mínimo | Classes de ação |
|---|---|---|---|
chat.providers |
leitura | guest |
— |
chat.status |
leitura | guest |
— |
chat.connect |
mutação | admin |
secretAccess, networkEgress |
chat.disconnect |
destrutiva | admin |
secretAccess |
chat.listUserLinks |
leitura | guest |
— |
chat.beginUserLink |
mutação | member |
— |
chat.unlinkUser |
mutação | member para si próprio, admin para qualquer outra pessoa |
— |
chat.createApp |
mutação | admin |
secretAccess, networkEgress |
chat.setupLink |
leitura | admin |
— |
chat.botProfile |
leitura | admin |
networkEgress |
chat.updateBotProfile |
mutação | admin |
networkEgress |
As operações de leitura herdam o piso guest do seu tipo; as mutações sem um piso explícito exigem member.
chat.setupLink compõe um URL de criação de aplicação do fornecedor a partir do perfil do bot. Não declara nenhuma classe de ação porque não lê nenhum segredo nem estabelece nenhuma ligação — é o navegador do próprio utilizador que faz a única chamada.
Uma consulta reativa completa a superfície:
| Consulta | Tipo | Emite |
|---|---|---|
chat.watchUserLinks |
observação | A lista, reenviada sempre que uma associação é criada ou removida |
Existe porque a associação acontece no outro lado: um membro escreve o código de uso único no bot, por isso nenhuma resposta do cliente pode transportar a resposta de volta. A lista nas definições e a caixa de diálogo «associar a minha conta» aberta seguem ambas esta consulta, e a caixa de diálogo passa a uma confirmação no momento em que a associação do próprio membro chega.
chat.beginUserLink emite sempre um código para a própria identidade de quem chama. chat.updateBotProfile devolve um remaining_step apenas quando resta um passo (tipicamente uma reinstalação); a sua ausência significa que a edição já está em vigor.
chat.providers devolve uma entrada por fornecedor oferecido — o descritor e, quando existe, o estado da ligação. Tudo o que a interface de definições apresenta vem desse payload, e é por isso que acrescentar um fornecedor é uma alteração apenas no servidor.
Armazenamento
Secção intitulada “Armazenamento”| O quê | Onde |
|---|---|
| Credenciais da ligação | <dataDir>/<workspaceId>/chat_credentials/<provider>.json |
| Configuração guiada a meio | <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json |
| Ficheiros do Slack antigos | <dataDir>/<workspaceId>/slack_credentials.json e slack_app_setup.json, lidos uma vez e migrados no local |
As credenciais vivem ao lado da base de dados do espaço de trabalho, não dentro dela: eliminar um espaço de trabalho leva consigo os seus tokens de chat, e workspace.export ou uma cópia de segurança copiam workspace.db — por isso um espaço de trabalho entregue a outra pessoa não transporta tokens de bot ativos. As escritas são atómicas (ficheiro temporário mais renomeação) e ficam restritas ao proprietário.
Tabelas da base de dados
Secção intitulada “Tabelas da base de dados”Ambas vivem na base de dados do espaço de trabalho, que transporta uma linha de base v1 consolidada — por isso uma leitura entre espaços de trabalho não compila.
chat_space_links — uma conversa externa ↔ um espaço do Control Center:
| Coluna | Notas |
|---|---|
id |
UUID v4 |
workspace_id |
Âmbito do espaço de trabalho |
provider |
ChatProvider.wire |
external_team_id |
Equipa do Slack T…, vazio para fornecedores sem o conceito |
external_channel_id |
Canal do Slack C…, grupo G…, mensagem direta D… |
external_thread_id |
Âncora de thread do Slack, ou nulo quando a conversa inteira é a âncora (mensagens diretas ao bot) |
cc_space_id |
REFERENCES spaces(id) ON DELETE CASCADE |
created_by_user_id, created_at, last_activity_at |
Único em (workspace_id, provider, external_channel_id, external_thread_id), com um índice parcial que impõe o mesmo para ids de thread nulos (o SQLite trata os NULL como distintos).
chat_user_links — um membro externo ↔ um utilizador do Control Center:
| Coluna | Notas |
|---|---|
id |
UUID v4 |
workspace_id |
Âmbito do espaço de trabalho |
provider |
ChatProvider.wire |
external_team_id, external_user_id |
Membro do Slack U… |
user_id |
Utilizador do Control Center (global, logo sem chave externa) |
method |
code ou email |
linked_at |
Único em (workspace_id, provider, external_team_id, external_user_id) e em (workspace_id, provider, user_id) — atribuição de valor único nos dois sentidos, enquanto uma pessoa pode estar associada em vários fornecedores.
Uma associação é resolução de identidade, não uma concessão de acesso: a pertença é verificada de novo em cada evento recebido, por isso uma associação a um membro que entretanto foi removido resolve-se numa recusa.
Tipo de espaço e metadados das mensagens
Secção intitulada “Tipo de espaço e metadados das mensagens”Um espaço com ponte é criado com o SpaceKind do fornecedor (slack). Comporta-se como um espaço comum virado para pessoas — notificado e seccionado como topic — e isExternalChat é a verificação por que passa cada comportamento (espelhamento, distintivos, retenção).
Cada mensagem que a ponte traz para dentro transporta uma marca metadata['chat']:
{ "chat": { "provider": "slack", "teamId": "T024BE7LD", "channelId": "C0G9QF9GW", "threadId": "1700000000.000100", "messageId": "1700000000.000200", "userId": "U024BE7LH", "userLabel": "sam" }}A marca é também a guarda contra ecos: o espelho de saída ignora qualquer mensagem que a transporte, e é isso que impede que uma mensagem de chat recebida seja publicada logo de volta.
Comportamento do relé
Secção intitulada “Comportamento do relé”| Comportamento | Detalhe |
|---|---|
| O que é retransmitido para fora | Apenas o texto do agente, mais a linha de estado do cartão de tarefa. A prosa de raciocínio e a saída de ferramentas ficam no Control Center |
| Cadência da transmissão em fluxo | Limitada a um envio por segundo enquanto um turno corre; o cartão e o texto que ele explica viajam na mesma chamada |
| Mensagens humanas escritas no Control Center | Espelhadas para a thread associada, com o nome de apresentação do autor como prefixo |
| Turnos vazios | Reportados como uma nota curta (a execução falhou, foi interrompida ou parou no seu limite de turnos) em vez de silêncio |
| Deduplicação | Com a chave no próprio id de evento do fornecedor; os últimos 512 são memorizados |
| Agente de entrada | Uma nova conversa com ponte acorda o agente CEO do espaço de trabalho, senão o seu primeiro agente, senão ninguém |
| Nomeação do espaço | #channel · <request> para uma thread de canal do Slack, Slack DM · <request> para uma mensagem direta, títulos limitados a 48 caracteres |
| Isolamento de falhas | Um evento mau nunca deita a ponte abaixo; a ligação continua a servir todas as outras conversas |
Ver também
Secção intitulada “Ver também”- Configurar a integração com o Slack
- Associar a sua conta do Slack
- Personalizar o bot de chat
- Pontes de chat
- Barreiras para as classes de ação que as operações declaram