Sari la conținut

Punte de chat

Provider Valoare serializată Transport Tip de spațiu
Slack slack Socket Mode (WSS spre exterior) slack

Slack este singurul provider. ChatProvider este un enum cu o singură valoare, iar cc_server înregistrează un singur plugin. Schema, fiecare căutare și fiecare index unic au cheia provider, așa că id-urile nu trec niciodată de la un provider la altul, iar adăugarea unuia este o schimbare doar pe server.

O aplicație de partea providerului per spațiu de lucru × provider.

Numele comenzii este propriu fiecărei aplicații (cc implicit, deci /cc). Un admin îl poate redenumi; puntea folosește apoi numele cu care a fost invocată ultima dată.

Comandă Efect
/cc help (sau /cc fără verb) Listează ce poate face botul
/cc link <code> Valorifică un cod de unică folosință și leagă identitatea de chat a expeditorului
/cc ticket <title> Înregistrează un tichet local raportat de membrul legat
/cc ticket <title> | <description> La fel, cu tot ce urmează după primul | ca descriere
Verb necunoscut Raportează verbul necunoscut plus textul de ajutor

Modalități de a ajunge la un agent:

Gest Comportament
@bot … într-un canal Deschide un fir Slack și un spațiu Control Center corespunzător; agentul răspunde în firul Slack
Răspuns într-un fir Slack legat Continuă același spațiu Control Center — nu este nevoie de mențiune
Mesaj obișnuit într-un canal Ignorat (nu este adresat botului și nu se află într-un fir Slack legat)
DM către bot Un spațiu Control Center per conversație DM; setează un titlu de fir Slack și un status „is thinking…” cât timp lucrează agentul

Declarate de descriptorul providerului, care este cel ce generează dialogul de conectare și validează trimiterea.

Id de câmp Etichetă Obligatoriu Prefix Unde se află
botToken Token OAuth al utilizatorului bot Da xoxb- OAuth & Permissions
appToken Token la nivel de aplicație Da xapp- Basic Information → App-Level Tokens, scope connections:write
configRefreshToken Token de reîmprospătare a configurației aplicației Nu xoxe App configuration tokens on api.slack.com/apps

configRefreshToken este credențialul de administrare a aplicației: fără el conexiunea funcționează, dar botul nu poate fi creat sau editat din Control Center. Se rotește la fiecare utilizare, iar valoarea rotită este stocată imediat.

Valorilor li se taie spațiile albe de la capete, cheile nedeclarate sunt eliminate, iar un prefix care nu se potrivește este refuzat cu aceeași frază pe client și pe server. Câmpurile obligatorii trebuie să fie prezente la fiecare conectare; un câmp opțional lăsat gol păstrează ce era deja stocat.

Scope-urile tokenului de bot:

app_mentions:read
assistant:write
channels:history
chat:write
commands
groups:history
im:history
users:read
users:read.email

Abonamentele la evenimente ale botului:

app_home_opened
app_mention
message.channels
message.groups
message.im

Setările structurale pe care Control Center le impune când creează sau editează aplicația: Socket Mode activat, niciun URL de cerere, fila Messages activată și cu drept de scriere, botul always_online, scope-urile și evenimentele necesare fuzionate cu tot ce are deja aplicația (niciodată scăzute) și o comandă slash care poartă descrierea File a ticket or link your Control Center account — markerul ce identifică propria comandă a Control Center într-un manifest exportat.

Stare Valoare serializată Semnificație
Neconectat disconnected Nu există credențiale stocate pentru acest spațiu de lucru × provider
Se conectează… connecting Credențiale stocate, transportul se conectează sau se reconectează
Activ connected Transportul este pornit și primește evenimente
Eroare de conexiune error Stocate, dar în eșec; eroarea proprie a providerului este afișată alături

Reconectarea folosește un backoff exponențial cu jitter, plafonat la 30 de secunde. O rotire de socket inițiată de provider se reconectează prompt, fără să escaladeze backoff-ul. Un credențial respins (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) sau un scope lipsă este definitiv: puntea nu mai reîncearcă până când nu sosesc credențiale noi.

Puntea este scrisă pentru cea mai bogată suprafață și degradează o capabilitate pe rând.

Capabilitate Slack Efectul în lipsa ei
streaming da Întregul răspuns este postat o dată, la final
streamingRequiresThread da O țintă de răspuns fără punct de ancorare în fir postează răspunsuri întregi
ephemeralMessages da Un răspuns la o comandă slash fără un identificator de răspuns utilizabil este aruncat (refuzurile sunt postate mereu în fir)
threadStatus da Nicio linie temporară „is thinking…”
threadTitle da Firul de chat își păstrează titlul implicit
slashCommands da Nicio suprafață /cc
taskCards da Doar text — niciun card deasupra răspunsului și niciun link „View in Control Center”
maxMessageLength 39.000 Răspunsurile mai lungi sunt trunchiate, cu o trimitere către Control Center
maxStreamChunkLength 3.800 Adăugările sunt împărțite la granița dintre paragrafe, linii sau spații

A anunța streaming nu înseamnă a-l promite: cel al Slack are nevoie de funcția Agents & AI Apps pe un plan plătit. Un refuz din momentul apelului (streaming_not_supported, feature_not_enabled, paid_only, …) este reținut pentru întreaga viață a conexiunii și expus ca streamingAvailable: false. Cardurile de sarcină ale Slack călătoresc în același apel, așa că un Slack care refuză modul de afișare al cardului își păstrează fluxul și pierde doar cardul, o singură dată, pentru întreaga viață a conexiunii.

Un flux trebuie să numească și cititorul căruia îi este destinat. Slack cere atât un utilizator destinatar, cât și echipa acestuia pentru a deschide unul într-un canal (un DM nu este suficient) și refuză cu missing_recipient_user_id / missing_recipient_team_id în caz contrar — un refuz per apel, așa că puntea continuă să încerce, iar între timp tura sosește ca un singur răspuns postat. Destinatarul este cine a întrebat, luat din mesajul de intrare (echipa acestuia, nu a aplicației, așa că un canal Slack Connect se rezolvă corect). Pentru o tură pe care nimeni nu a pornit-o din chat — muncă lansată în Control Center pe o conversație deja legată — acesta este membrul a cărui legătură a creat puntea; dacă între timp și-a dezlegat contul de chat, acea tură este postată întreagă, nu transmisă în flux.

Un card grupat per tură de agent (task_display_mode: plan), cu un rând per pas. Fiecare rând este un task_update cu id + title + status, plus un details trimis o singură dată când gândul este gata. details și output cu același id se concatenează fără separator, așa că details sunt trimise o singură dată, iar output nu este niciodată transmis în flux. Titlul planului este cererea; răspunsul este o adăugare separată, ulterioară, care poartă un fragment markdown_text.

Parte Conținut
Titlul planului Ce s-a cerut
Rândul de configurare Working on it…, apoi Cloning …, apoi Starting the agent… — un singur rând, cu titlul înlocuit
Rândul de gândire Thinking…, adăugat când agentul începe să raționeze; gândul sunt details ale acestui rând, trimise o singură dată când raționamentul se termină
Rândurile de instrumente Unul per instrument (Bash which swift), cu id unic, așa că stau unul lângă altul
Stare În curs cât rulează acel rând; complet când începe rândul următor sau se termină tura; eroare pentru eșec, întrerupere și limita de ture
Îndemn la acțiune View in Control Center ca rând final când se termină tura — absent cât timp munca încă rulează și absent când serverul nu are un URL public accesibil

Ce nu poartă niciodată un card: ieșirea instrumentelor, un argument din afara acelei liste de valori permise sau orice citit din baza de date. Textul raționamentului sunt doar details ale rândului Thinking… (plafonate la 256 de caractere), nu corpul mesajului. Un card care nu ar spune nimic nou nu este retrimis. O mențiune deschide fluxul imediat, ca firul Slack să nu rămână mut; o tură care apoi nu a produs niciun text încheie același card cu o notă scurtă, în loc să posteze un al doilea mesaj. O tură fără cuvinte pe un spațiu care nu a deschis niciodată un flux (fără carduri sau cu streamingul deja refuzat) postează totuși un mesaj care poartă cardul încheiat și nota.

Cardurile transmise în flux și cele postate sunt încărcături utile diferite:

Formă Structură Text
Transmis în flux plan_update plus fragmente task_update indexate după id Șir simplu, plafonat la 256 de caractere per fragment
Postat Un bloc plan care înfășoară task_card-uri indexate după task_id Text îmbogățit

Gestionarea eșecurilor:

Situație Comportament
Providerul refuză cardul la o adăugare Adăugarea este reîncercată imediat fără el; textul sosește în continuare în direct, iar cardurile sunt eliminate pentru întreaga viață a conexiunii
Providerul refuză și textul Fluxul este închis, iar răspunsul este postat întreg
Fluxul se rupe după ce o parte a răspunsului a ajuns Fluxul este închis și nimic nu este repostat

Vezi Punți de chat pentru motivul pentru care încărcătura utilă este împărțită astfel.

Cardul se deschide când o mențiune este acceptată, înainte de clonare și înainte de primul token; tura care urmează adoptă același card și același flux. Narațiunea de după vine de la SpaceProvisioningChanged de pe bus-ul de evenimente de domeniu — exact același progres pe care bannerul de pregătire al aplicației de desktop îl citește de pe rândul spațiului.

Stare de pregătire Narațiune
Mențiune acceptată Working on it…
Pasul repo Cloning <repo>…
Pasul prCheckout Checking out the pull request in <repo>…
Pasul script Running the setup script for <repo>…
Pasul agent Setting up <agent>…
provisioning, încă niciun pas Preparing the workspace…
ready Starting the agent…
Agentul raționează Thinking…
failed Workspace setup failed — details are in Control Center.
cancelled Workspace setup was stopped — details are in Control Center.

Narațiunea de configurare cere toate cele patru condiții:

Condiție
Providerul afișează carduri
Streamingul este disponibil pe această conexiune
Spațiul are o cerere de chat în curs
Niciun card nu raportează deja o tură acolo

Cererea rămâne în curs până când începe o tură sau configurarea eșuează. Un eveniment ready fără niciun card deja deschis este un spațiu deja cald și este lăsat în tăcere, iar o pregătire pe care nimeni din chat nu o așteaptă — o conversație deschisă în aplicație, una rămasă în pană, reluată la pornire — nu produce niciun card.

Produsele de chat acceptă http(s) într-un link și nimic altceva, așa că un îndemn la acțiune nu poate numi direct schema aplicației de desktop.

Proprietate Valoare
Rută GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id>
Origine Derivată din URL-ul public al serverului (ws → http, wss → https); adresa de loopback este păstrată
Răspuns O pagină HTML mică, fără stocare, care navighează către control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> și afișează un buton manual
Validare Id-urile trebuie să corespundă lui [A-Za-z0-9_.-]{1,128}; orice altceva este un 404
Acces Publică și inertă — nu citește nicio bază de date, deci nu este un oracol de existență; aplicația pe care o deschide face autorizarea
Câmp Limită Note
appName 1–35 caractere Implicit: Control Center · <workspace name>
botDisplayName 1–80 caractere Implicit: control-center
description ≤ 140 caractere
agentDescription ≤ 300 caractere Afișat deasupra unui DM cu botul
command ≤ 32 caractere Normalizat: barele oblice de la început sunt eliminate, textul este trecut în litere mici, [^a-z0-9_-] este eliminat, iar dacă rămâne gol se folosește cc
agentEnabled — Dacă experiența de DM/agent este expusă

O casetă goală înseamnă „las-o așa”, nu „golește-o”. Limitele sunt ale Slack, cele mai strânse dintre produsele modelate.

Proprietate Valoare
Lungime 6 caractere
Alfabet 23456789ABCDEFGHJKLMNPQRSTVWXYZ (fără 0/O, 1/I, U)
Durată de viață 15 minute
Utilizări Una
Stocare În memorie — o repornire a serverului invalidează codurile încă nefolosite
Domeniu de aplicare Un spațiu de lucru și un provider; generarea unui cod nou îl invalidează pe cel anterior al membrului

Utilizarea codului este potrivită fără a ține cont de majuscule. Codurile necunoscute, expirate, din alt spațiu de lucru și pentru alt provider eșuează toate identic, intenționat.

Metodă Valoare serializată Cum
Cod code Membrul a utilizat /cc link <code>
Email email Emailul verificat al providerului s-a potrivit cu un utilizator care este deja membru al spațiului de lucru

Toate sunt limitate la spațiul de lucru. Serverul este fără stare, așa că fiecare apel își poartă propriul workspace_id în argumente; dispecerul refuză o operație limitată la spațiul de lucru care îl omite, înainte de a se deschide vreo bază de date. Credențialele circulă doar spre interior — citirile de stare nu le returnează niciodată.

Operație Tip Rol minim Clase de acțiuni
chat.providers citire guest —
chat.status citire guest —
chat.connect mutare admin secretAccess, networkEgress
chat.disconnect distructivă admin secretAccess
chat.listUserLinks citire guest —
chat.beginUserLink mutare member —
chat.unlinkUser mutare member pentru tine, admin pentru oricine altcineva —
chat.createApp mutare admin secretAccess, networkEgress
chat.setupLink citire admin —
chat.botProfile citire admin networkEgress
chat.updateBotProfile mutare admin networkEgress

Operațiile de citire moștenesc pragul guest de la tipul lor; mutările fără un prag explicit cer member.

chat.setupLink compune un URL de creare a aplicației providerului pornind de la profilul botului. Nu declară nicio clasă de acțiuni pentru că nu citește niciun secret și nu apelează nimic — singurul apel este făcut de browserul utilizatorului.

O interogare reactivă completează suprafața:

Interogare Tip Emite
chat.watchUserLinks watch Conturile legate, retrimise ori de câte ori se creează sau se elimină o legătură

Există pentru că legarea se întâmplă de cealaltă parte: un membru tastează codul de unică folosință în bot, așa că niciun răspuns al clientului nu poate duce răspunsul înapoi. Rosterul din setări și dialogul deschis „leagă-mi contul” o urmăresc amândouă, iar dialogul trece într-o confirmare în momentul în care legătura membrului sosește.

chat.beginUserLink generează întotdeauna un cod pentru identitatea celui care apelează. chat.updateBotProfile returnează un remaining_step doar când mai rămâne un pas (de obicei o reinstalare); lipsa lui înseamnă că modificarea este deja activă.

chat.providers returnează o intrare per provider oferit — descriptorul plus, când există, starea conexiunii. Tot ce afișează interfața de setări vine din această încărcătură utilă, motiv pentru care adăugarea unui provider este o schimbare doar pe server.

Ce Unde
Credențialele conexiunii <dataDir>/<workspaceId>/chat_credentials/<provider>.json
Configurarea asistată neterminată <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json
Fișierele Slack vechi <dataDir>/<workspaceId>/slack_credentials.json și slack_app_setup.json, citite o dată și migrate pe loc

Credențialele stau lângă baza de date a spațiului de lucru, nu în ea: ștergerea unui spațiu de lucru ia cu el și tokenurile de chat, iar workspace.export sau o copie de siguranță copiază workspace.db — așa că un spațiu de lucru înmânat altcuiva nu duce cu el tokenuri de bot active. Scrierile sunt atomice (fișier temporar plus redenumire) și restrânse la doar proprietar.

Ambele se află în baza de date a spațiului de lucru, care are o bază v1 comprimată — așa că o citire din alt spațiu de lucru nu se compilează.

chat_space_links — o conversație externă ↔ un spațiu Control Center:

Coloană Note
id UUID v4
workspace_id Domeniul spațiului de lucru
provider ChatProvider.wire
external_team_id Echipa Slack T…, gol pentru providerii fără acest concept
external_channel_id Canal Slack C…, grup G…, DM D…
external_thread_id Punctul de ancorare al firului Slack sau null când întreaga conversație este ancorajul (DM-urile botului)
cc_space_id REFERENCES spaces(id) ON DELETE CASCADE
created_by_user_id, created_at, last_activity_at

Unic pe (workspace_id, provider, external_channel_id, external_thread_id), cu un index parțial care impune același lucru pentru id-urile de fir null (SQLite tratează NULL-urile ca distincte).

chat_user_links — un membru extern ↔ un utilizator Control Center:

Coloană Note
id UUID v4
workspace_id Domeniul spațiului de lucru
provider ChatProvider.wire
external_team_id, external_user_id Membrul Slack U…
user_id Utilizator Control Center (global, deci fără cheie străină)
method code sau email
linked_at

Unic pe (workspace_id, provider, external_team_id, external_user_id) și pe (workspace_id, provider, user_id) — atribuire cu o singură valoare în ambele direcții, în timp ce o persoană poate fi legată la mai mulți provideri.

O legătură este o rezolvare de identitate, nu o acordare de acces: apartenența este reverificată la fiecare eveniment de intrare, așa că o legătură către un membru care a fost între timp eliminat se rezolvă într-un refuz.

Un spațiu legat este creat cu SpaceKind al providerului (slack). Se comportă ca un spațiu obișnuit pentru oameni — notificat și încadrat la fel ca topic — iar isExternalChat este verificarea prin care trece fiecare comportament (oglindire, ecusoane, păstrare).

Fiecare mesaj pe care puntea îl aduce poartă o marcă metadata['chat']:

{
"chat": {
"provider": "slack",
"teamId": "T024BE7LD",
"channelId": "C0G9QF9GW",
"threadId": "1700000000.000100",
"messageId": "1700000000.000200",
"userId": "U024BE7LH",
"userLabel": "sam"
}
}

Marca este și paza anti-ecou: oglinda spre exterior sare peste orice mesaj care o poartă, și exact asta împiedică un mesaj de chat sosit din exterior să fie postat imediat înapoi.

Comportament Detaliu
Ce este transmis spre exterior Doar textul agentului, plus linia de stare a cardului de sarcină. Textul raționamentului și ieșirea instrumentelor rămân în Control Center
Cadența streamingului Limitată la o singură golire pe secundă cât rulează o tură; cardul și textul pe care îl explică călătoresc în același apel
Mesajele scrise de oameni în Control Center Oglindite în firul legat, cu numele afișat al autorului ca prefix
Turele fără conținut Raportate ca o notă scurtă (rularea a eșuat, a fost întreruptă sau s-a oprit la limita de ture), nu ca tăcere
Deduplicare Indexată după id-ul de eveniment propriu al providerului; ultimele 512 sunt reținute
Agentul de întâmpinare O conversație legată nouă trezește agentul CEO al spațiului de lucru, altfel primul său agent, altfel nimeni
Denumirea spațiului #channel · <request> pentru un fir de canal Slack, Slack DM · <request> pentru un DM, titluri plafonate la 48 de caractere
Izolarea eșecurilor Un singur eveniment defect nu doboară niciodată puntea; conexiunea continuă să deservească toate celelalte conversații