Przejdź do głównej zawartości

Most czatu

Dostawca Wartość protokołu Transport Rodzaj przestrzeni
Slack slack Socket Mode (wychodzący WSS) slack

Slack to jedyny dostawca. ChatProvider to jednowartościowy enum, a cc_server rejestruje jedną wtyczkę. Schemat, każde wyszukiwanie i każdy indeks unikalny są kluczowane przez provider, więc identyfikatory nigdy nie przechodzą między dostawcami, a dodanie kolejnego to zmiana wyłącznie po stronie serwera.

Jedna aplikacja po stronie dostawcy na obszar roboczy × dostawcę.

Nazwa polecenia jest właściwa dla aplikacji (cc domyślnie, czyli /cc). Administrator może ją zmienić; most używa wtedy tej nazwy, pod którą polecenie wywołano ostatnio.

Polecenie Efekt
/cc help (lub /cc bez czasownika) Wyświetla, co potrafi bot
/cc link <code> Realizuje jednorazowy kod i łączy tożsamość czatu nadawcy
/cc ticket <title> Zakłada lokalne zgłoszenie przekazane przez połączonego członka
/cc ticket <title> | <description> To samo, z wszystkim po pierwszym | jako opisem
Nieznany czasownik Zgłasza nieznany czasownik wraz z tekstem pomocy

Sposoby dotarcia do agenta:

Działanie Zachowanie
@bot … na kanale Otwiera wątek Slacka i pasującą przestrzeń Control Center; agent odpowiada w wątku Slacka
Odpowiedź w połączonym wątku Slacka Kontynuuje tę samą przestrzeń Control Center — wzmianka nie jest potrzebna
Zwykła wiadomość na kanale Ignorowana (nie jest skierowana do bota i nie należy do połączonego wątku Slacka)
Wiadomość prywatna do bota Jedna przestrzeń Control Center na rozmowę prywatną; ustawia tytuł wątku Slacka i status „is thinking…“, gdy agent pracuje

Deklarowane przez deskryptor dostawcy, który generuje okno połączenia i waliduje zgłoszenie.

Identyfikator pola Etykieta Wymagane Prefiks Gdzie się znajduje
botToken Bot user OAuth token Tak xoxb- OAuth & Permissions
appToken App-level token Tak xapp- Basic Information → App-Level Tokens, scope connections:write
configRefreshToken App configuration refresh token Nie xoxe App configuration tokens on api.slack.com/apps

configRefreshToken to dane uwierzytelniające do zarządzania aplikacją: bez nich połączenie działa, ale bota nie można utworzyć ani edytować z Control Center. Jest rotowany przy każdym użyciu, a nowa wartość jest zapisywana natychmiast.

Wartości są przycinane z białych znaków, niezadeklarowane klucze są odrzucane, a niezgodny prefiks jest odrzucany tym samym komunikatem po stronie klienta i serwera. Pola wymagane muszą być podane przy każdym połączeniu; pole opcjonalne pozostawione puste zachowuje to, co już zapisano.

Zakresy tokenu bota:

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

Subskrypcje zdarzeń bota:

app_home_opened
app_mention
message.channels
message.groups
message.im

Ustawienia strukturalne, które Control Center wymusza, gdy tworzy lub edytuje aplikację: włączony Socket Mode, brak adresu URL żądań, zakładka Messages włączona i zapisywalna, bot always_online, wymagane zakresy i zdarzenia scalone z tym, co aplikacja już ma (nigdy nie odejmowane) oraz jedno polecenie slash niosące opis File a ticket or link your Control Center account — znacznik, który identyfikuje własne polecenie Control Center w wyeksportowanym manifeście.

Stan Wartość protokołu Znaczenie
Nie połączono disconnected Brak zapisanych danych uwierzytelniających dla tej pary obszar roboczy × dostawca
Łączenie… connecting Dane uwierzytelniające zapisane, transport się łączy lub łączy ponownie
Na żywo connected Transport działa i odbiera zdarzenia
Błąd połączenia error Zapisane, ale kończy się niepowodzeniem; obok pokazywany jest błąd samego dostawcy

Ponowne połączenie to wykładniczy backoff z jitterem, ograniczony do 30 sekund. Zainicjowana przez dostawcę rotacja gniazda łączy ponownie bez zwłoki i bez zwiększania backoffu. Odrzucone dane uwierzytelniające (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) lub brakujący zakres są ostateczne: most przestaje ponawiać próby, dopóki nie pojawią się nowe dane uwierzytelniające.

Most jest pisany pod najbogatszą powierzchnię i traci możliwości pojedynczo.

Możliwość Slack Efekt, gdy jej brak
streaming tak Cała odpowiedź jest publikowana raz, na końcu
streamingRequiresThread tak Cel odpowiedzi bez kotwicy wątku publikuje całe odpowiedzi
ephemeralMessages tak Odpowiedź na polecenie slash bez użytecznego uchwytu odpowiedzi jest porzucana (odmowy zawsze trafiają do wątku)
threadStatus tak Brak przemijającego wiersza „is thinking…”
threadTitle tak Wątek czatu zachowuje domyślny tytuł
slashCommands tak Brak powierzchni /cc
taskCards tak Tylko tekst — żadnej karty nad odpowiedzią ani linku „View in Control Center”
maxMessageLength 39,000 Dłuższe odpowiedzi są obcinane ze wskazaniem na Control Center
maxStreamChunkLength 3,800 Dołączane fragmenty są dzielone na granicy akapitu, wiersza lub spacji

Zgłoszenie strumieniowania to jeszcze nie obietnica: strumieniowanie Slacka wymaga funkcji Agents & AI Apps w płatnym planie. Odmowa w chwili wywołania (streaming_not_supported, feature_not_enabled, paid_only, …) jest zapamiętywana na cały czas życia połączenia i pokazywana jako streamingAvailable: false. Karty zadań Slacka korzystają z tego samego wywołania, więc Slack, który odrzuca tryb wyświetlania karty, zachowuje swój strumień i traci tylko kartę — raz, na cały czas życia połączenia.

Strumień musi też wskazać czytelnika, dla którego jest przeznaczony. Slack wymaga zarówno użytkownika będącego odbiorcą, jak i jego zespołu, aby otworzyć strumień na kanale (wiadomość prywatna tego nie wystarcza) i w przeciwnym razie odmawia z missing_recipient_user_id / missing_recipient_team_id — to odmowa dotycząca pojedynczego wywołania, więc most próbuje dalej, a tura dociera tymczasem jako jedna opublikowana odpowiedź. Odbiorcą jest ten, kto zapytał, wzięty z wiadomości przychodzącej (jego zespół, a nie zespół aplikacji, dzięki czemu kanał Slack Connect jest rozwiązywany poprawnie). W przypadku tury, której nikt nie rozpoczął z czatu — pracy uruchomionej w Control Center na już połączonej rozmowie — jest to członek, którego połączenie utworzyło most; jeśli od tego czasu odłączył on swoje konto czatu, ta tura jest publikowana w całości, a nie strumieniowana.

Jedna zgrupowana karta na turę agenta (task_display_mode: plan), z wierszem na każdy krok. Każdy wiersz to task_update złożony z id + title + status, plus jednorazowe details, gdy myśl jest gotowa. details i output o tym samym identyfikatorze są łączone bez separatora, więc details są wysyłane raz, a output nigdy nie jest strumieniowany. Tytuł planu to żądanie; odpowiedź to osobne, późniejsze dołączenie niosące fragment markdown_text.

Część Zawartość
Tytuł planu To, o co poproszono
Wiersz konfiguracji Working on it…, potem Cloning …, potem Starting the agent… — jeden wiersz, tytuł jest zastępowany
Wiersz rozumowania Thinking…, dodawany, gdy agent zaczyna rozumować; myśl to details tego wiersza, wysyłane raz po zakończeniu rozumowania
Wiersze narzędzi Po jednym na narzędzie (Bash which swift), unikalny identyfikator, dzięki czemu stoją obok siebie
Status W toku, gdy ten wiersz działa; ukończony, gdy zaczyna się następny wiersz albo tura się kończy; błąd w przypadku niepowodzenia, przerwania i limitu tur
Wezwanie do działania View in Control Center jako końcowy wiersz po zakończeniu tury — nieobecne, gdy praca wciąż trwa, i nieobecne, gdy serwer nie ma osiągalnego publicznego adresu URL

Czego karta nigdy nie niesie: danych wyjściowych narzędzi, argumentu spoza tej listy dozwolonych ani niczego odczytanego z bazy danych. Proza rozumowania to wyłącznie details wiersza Thinking… (ograniczone do 256 znaków), a nie treść wiadomości. Karta, która nie powiedziałaby nic nowego, nie jest wysyłana ponownie. Wzmianka otwiera strumień natychmiast, aby wątek Slacka nie milczał; tura, która potem nie wygenerowała żadnego tekstu, kończy tę samą kartę krótką notatką, zamiast publikować drugą wiadomość. Tura bez słów na przestrzeni, która nigdy nie otworzyła strumienia (brak kart albo strumieniowanie już odrzucone), i tak publikuje jedną wiadomość niosącą ukończoną kartę i notatkę.

Karty strumieniowane i publikowane to różne ładunki:

Forma Kształt Proza
Strumieniowana plan_update plus fragmenty task_update kluczowane przez id Zwykły ciąg, ograniczony do 256 znaków na fragment
Publikowana Blok plan opakowujący task_card kluczowane przez task_id Tekst sformatowany

Obsługa niepowodzeń:

Sytuacja Zachowanie
Dostawca odrzuca kartę przy dołączaniu Dołączenie jest natychmiast ponawiane bez niej; proza wciąż dociera na żywo, a karty są porzucane na cały czas życia połączenia
Dostawca odrzuca również prozę Strumień jest zamykany, a odpowiedź publikowana w całości
Strumień urywa się po dotarciu części odpowiedzi Strumień jest zamykany i nic nie jest publikowane ponownie

Dlaczego ładunek jest dzielony w ten sposób, wyjaśnia sekcja Mosty czatu.

Karta otwiera się, gdy wzmianka zostanie przyjęta, przed klonowaniem i przed pierwszym tokenem; następująca tura przejmuje tę samą kartę i strumień. Narracja po tym pochodzi ze zdarzenia SpaceProvisioningChanged w magistrali zdarzeń domenowych — ten sam postęp, który baner konfiguracji w aplikacji desktopowej odczytuje z wiersza przestrzeni.

Stan konfiguracji Narracja
Wzmianka przyjęta Working on it…
Krok repo Cloning <repo>…
Krok prCheckout Checking out the pull request in <repo>…
Krok script Running the setup script for <repo>…
Krok agent Setting up <agent>…
provisioning, jeszcze bez kroku Preparing the workspace…
ready Starting the agent…
Agent rozumuje Thinking…
failed Workspace setup failed — details are in Control Center.
cancelled Workspace setup was stopped — details are in Control Center.

Narracja konfiguracji wymaga wszystkich czterech warunków:

Warunek
Dostawca renderuje karty
Strumieniowanie jest dostępne na tym połączeniu
Przestrzeń ma żądanie czatu w toku
Żadna karta nie raportuje już tam tury

Żądanie pozostaje w toku, dopóki nie zacznie się tura albo nie zawiedzie konfiguracja. Zdarzenie ready bez już otwartej karty oznacza ciepłą przestrzeń i pozostaje ciche, a konfiguracja, na którą nikt w czacie nie czeka — rozmowa otwarta w aplikacji, osierocona rozmowa wznowiona przy starcie — nie tworzy żadnej karty.

Produkty czatowe przyjmują w linku http(s) i nic więcej, więc wezwanie do działania nie może wprost nazwać schematu aplikacji desktopowej.

Właściwość Wartość
Trasa GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id>
Pochodzenie Wyprowadzane z publicznego adresu URL serwera (ws → http, wss → https); pętla zwrotna jest zachowywana
Odpowiedź Niewielka strona HTML bez buforowania, która przechodzi do control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> i pokazuje ręczny przycisk
Walidacja Identyfikatory muszą pasować do [A-Za-z0-9_.-]{1,128}; wszystko inne to 404
Dostęp Publiczny i bierny — nie czyta żadnej bazy danych, więc nie jest wyrocznią istnienia; autoryzację wykonuje aplikacja, którą otwiera
Pole Limit Uwagi
appName 1–35 znaków Domyślnie Control Center · <workspace name>
botDisplayName 1–80 znaków Domyślnie control-center
description ≤ 140 znaków
agentDescription ≤ 300 znaków Pokazywane nad wiadomością prywatną z botem
command ≤ 32 znaków Normalizowana: wiodące ukośniki usuwane, zamiana na małe litery, [^a-z0-9_-] usuwane, pusta wartość wraca do cc
agentEnabled — Czy doświadczenie wiadomości prywatnych i agenta jest udostępniane

Puste pole oznacza „nie zmieniaj”, a nie „wyczyść”. Limity należą do Slacka i są najostrzejsze spośród modelowanych produktów.

Właściwość Wartość
Długość 6 znaków
Alfabet 23456789ABCDEFGHJKLMNPQRSTVWXYZ (bez 0/O, 1/I, U)
Czas życia 15 minut
Użycia Jedno
Przechowywanie W pamięci — restart serwera unieważnia niewykorzystane kody
Zakres Jeden obszar roboczy i jeden dostawca; wygenerowanie nowego kodu unieważnia poprzedni kod członka

Realizacja kodu jest dopasowywana bez rozróżniania wielkości liter. Nieznane, wygasłe kody oraz kody z obcego obszaru roboczego lub od złego dostawcy zawodzą celowo w identyczny sposób.

Metoda Wartość protokołu Sposób
Code code Członek zrealizował /cc link <code>
Email email Zweryfikowany adres e-mail dostawcy pasował do użytkownika, który jest już członkiem obszaru roboczego

Wszystkie są ograniczone do obszaru roboczego. Serwer jest bezstanowy, więc każde wywołanie niesie własny workspace_id w argumentach; dyspozytor odrzuca operację ograniczoną do obszaru roboczego, która go pomija, jeszcze przed otwarciem jakiejkolwiek bazy danych. Dane uwierzytelniające podróżują wyłącznie do środka — odczyty statusu nigdy ich nie zwracają.

Operacja Rodzaj Minimalna rola Klasy akcji
chat.providers read guest —
chat.status read guest —
chat.connect mutate admin secretAccess, networkEgress
chat.disconnect destructive admin secretAccess
chat.listUserLinks read guest —
chat.beginUserLink mutate member —
chat.unlinkUser mutate member dla siebie, admin dla każdego innego —
chat.createApp mutate admin secretAccess, networkEgress
chat.setupLink read admin —
chat.botProfile read admin networkEgress
chat.updateBotProfile mutate admin networkEgress

Operacje odczytu dziedziczą próg guest ze swojego rodzaju; mutacje bez jawnego progu wymagają member.

chat.setupLink składa adres URL tworzenia aplikacji u dostawcy z profilu bota. Nie deklaruje żadnej klasy akcji, bo nie czyta żadnego sekretu i niczego nie wywołuje — jedyne wywołanie wykonuje przeglądarka użytkownika.

Powierzchnię dopełnia jedno reaktywne zapytanie:

Zapytanie Rodzaj Emituje
chat.watchUserLinks watch Listę członków, wysyłaną ponownie za każdym razem, gdy połączenie powstanie lub zostanie usunięte

Istnieje, ponieważ połączenie następuje po drugiej stronie: członek wpisuje jednorazowy kod do bota, więc żadna odpowiedź klienta nie może przenieść odpowiedzi z powrotem. Lista w ustawieniach i otwarte okno „Połącz moje konto” śledzą je oba, a okno przechodzi do potwierdzenia w chwili, gdy dotrze połączenie samego członka.

chat.beginUserLink zawsze generuje kod dla tożsamości samego wywołującego. chat.updateBotProfile zwraca remaining_step tylko wtedy, gdy pozostaje krok (zwykle ponowna instalacja); jego brak oznacza, że zmiana już działa.

chat.providers zwraca jeden wpis na każdego oferowanego dostawcę — deskryptor, a gdy istnieje, także status połączenia. Wszystko, co renderuje interfejs ustawień, pochodzi z tego ładunku — dlatego dodanie dostawcy to zmiana wyłącznie po stronie serwera.

Co Gdzie
Dane uwierzytelniające połączenia <dataDir>/<workspaceId>/chat_credentials/<provider>.json
Niedokończona konfiguracja z przewodnikiem <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json
Starsze pliki Slacka <dataDir>/<workspaceId>/slack_credentials.json oraz slack_app_setup.json, odczytane raz i zmigrowane na miejscu

Dane uwierzytelniające leżą obok bazy danych obszaru roboczego, a nie w niej: usunięcie obszaru roboczego zabiera ze sobą jego tokeny czatu, a workspace.export lub kopia zapasowa kopiuje workspace.db — obszar roboczy przekazany komuś innemu nie nosi więc aktywnych tokenów bota. Zapisy są atomowe (plik tymczasowy plus zmiana nazwy) i ograniczone do samego właściciela.

Obie leżą w bazie danych obszaru roboczego, która zawiera skondensowaną bazę odniesienia v1 — odczyt między obszarami roboczymi się więc nie skompiluje.

chat_space_links — jedna rozmowa zewnętrzna ↔ jedna przestrzeń Control Center:

Kolumna Uwagi
id UUID v4
workspace_id Zakres obszaru roboczego
provider ChatProvider.wire
external_team_id Zespół Slacka T…, pusty dla dostawców bez tego pojęcia
external_channel_id Kanał Slacka C…, grupa G…, wiadomość prywatna D…
external_thread_id Kotwica wątku Slacka albo null, gdy cała rozmowa jest kotwicą (wiadomości prywatne do bota)
cc_space_id REFERENCES spaces(id) ON DELETE CASCADE
created_by_user_id, created_at, last_activity_at

Unikalny dla (workspace_id, provider, external_channel_id, external_thread_id), z indeksem częściowym wymuszającym to samo dla pustych identyfikatorów wątku (SQLite traktuje NULL-e jako odrębne).

chat_user_links — jeden członek zewnętrzny ↔ jeden użytkownik Control Center:

Kolumna Uwagi
id UUID v4
workspace_id Zakres obszaru roboczego
provider ChatProvider.wire
external_team_id, external_user_id Członek Slacka U…
user_id Użytkownik Control Center (globalny, więc bez klucza obcego)
method code lub email
linked_at

Unikalny dla (workspace_id, provider, external_team_id, external_user_id) i dla (workspace_id, provider, user_id) — przypisanie jednowartościowe w obu kierunkach, przy czym jedna osoba może być połączona z kilkoma dostawcami.

Połączenie to ustalenie tożsamości, nie nadanie dostępu: członkostwo jest sprawdzane od nowa przy każdym zdarzeniu przychodzącym, więc połączenie należące do członka, który został w międzyczasie usunięty, kończy się odmową.

Połączona przestrzeń jest tworzona z SpaceKind dostawcy (slack). Zachowuje się jak zwykła przestrzeń dla ludzi — powiadamia i grupuje się jak topic — a isExternalChat to sprawdzenie, przez które przechodzi każde zachowanie (odbicie, plakietki, retencja).

Każda wiadomość, którą most wnosi, nosi stempel metadata['chat']:

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

Stempel jest także zabezpieczeniem przed echem: odbicie wychodzące pomija każdą wiadomość, która go niesie, co powstrzymuje wiadomość przychodzącą z czatu przed natychmiastowym opublikowaniem z powrotem.

Zachowanie Szczegóły
Co jest przekazywane na zewnątrz Wyłącznie tekst agenta plus wiersz statusu karty zadania. Proza rozumowania i dane wyjściowe narzędzi zostają w Control Center
Tempo strumieniowania Ograniczane do jednego wysłania na sekundę w trakcie tury; karta i tekst, który ją objaśnia, podróżują w tym samym wywołaniu
Wiadomości pisane przez ludzi w Control Center Odbijane do połączonego wątku, z nazwą wyświetlaną autora jako prefiksem
Puste tury Raportowane jako krótka notatka (uruchomienie nie powiodło się, zostało przerwane albo zatrzymane na limicie tur), a nie cisza
Deduplikacja Kluczowana identyfikatorem zdarzenia samego dostawcy; zapamiętywane jest ostatnie 512
Agent pierwszego kontaktu Nowa połączona rozmowa budzi agenta CEO obszaru roboczego, w przeciwnym razie jego pierwszego agenta, a w ostateczności nikogo
Nazywanie przestrzeni #channel · <request> dla wątku kanału Slacka, Slack DM · <request> dla wiadomości prywatnej, tytuły ograniczone do 48 znaków
Izolacja awarii Jedno złe zdarzenie nigdy nie przewraca mostu; połączenie obsługuje dalej każdą inną rozmowę