Mosty czatu
Most czatu czyni zewnętrzny produkt czatowy — dziś Slacka — pełnoprawną drogę do obszaru roboczego Control Center. Wzmianka o bocie budzi agenta; odpowiedź w wątku Slacka kontynuuje rozmowę; wpisanie polecenia slash tworzy zgłoszenie. Pod spodem działają te same przestrzenie, transkrypcje, zgłoszenia i przypisanie, których używa aplikacja desktopowa. Czat to powierzchnia obszaru roboczego, a nie jego kopia.
Slack to jedyny dostawca, który jest dostarczany. W całym produkcie nie ma żadnej implementacji Discorda, Telegrama, Teams ani Matrix. Funkcja jest celowo napisana w sposób niezależny od dostawcy — dodanie drugiego produktu to jedna wartość enuma, jeden adapter i jedna wtyczka — ale dziś „czat” oznacza Slacka.
Dwie powierzchnie to obsługują i należą do różnych osób. Połączenie obszaru roboczego ze Slackiem oraz dostosowanie bota to działania administratora w Ustawienia → Obszar roboczy → Ogólne. Połączenie własnego konta czatu z tożsamością w Control Center to działanie członka w Ustawienia → Obszar roboczy → Profil i tożsamość.
Całą funkcję kształtują trzy decyzje: w jakim kierunku biegnie połączenie, co autoryzuje wiadomość przychodzącą z zewnątrz i gdzie może żyć wiedza specyficzna dla produktu.
Wychodzący z założenia
Dział zatytułowany „Wychodzący z założenia”Most nigdy nie nasłuchuje u dostawcy. Slack jest osiągany przez Socket Mode: serwer prosi Slacka o adres URL gniazda i łączy się na zewnątrz, a następnie odbiera wzmianki, wiadomości prywatne i polecenia slash przez to połączenie.
Właśnie ta konsekwencja jest sednem. Laptop za NAT, domowy serwer bez domeny, maszyna przenoszona między sieciami — każde z nich może prowadzić most bez portu przychodzącego, bez tunelu, bez certyfikatu TLS i bez publicznego adresu URL, który trzeba by rotować po każdej zmianie. Nie ma punktu końcowego webhooka do zabezpieczenia, bo nie ma punktu końcowego webhooka. Gdy Control Center składa definicję aplikacji, wymusza Socket Mode i usuwa wszelki adres URL żądań, bo aplikacja skonfigurowana do obu naraz działałaby połowicznie.
Ceną jest połączenie, które trzeba utrzymywać przy życiu, a to zadanie serwera: ponowne połączenie to wykładniczy backoff z jitterem, ograniczony do 30 sekund, zainicjowana przez dostawcę rotacja gniazda łączy ponownie bez zwiększania tego backoffu, a odrzucone dane uwierzytelniające są ostateczne — ponawianie odwołanego tokenu w nieskończoność przyniosłoby tylko limit szybkości, więc most zatrzymuje się i czeka na nowe tokeny, raportując w ustawieniach błąd dostawcy dosłownie.
Obszar roboczy bez aplikacji czatowej nie kosztuje nic: żadnego gniazda, żadnych timerów, żadnego ruchu. A całość startuje po banerze gotowości serwera, bo aplikacja czatowa, która odpowiada wolno, nie może opóźniać rozruchu.
Autoryzuje członkostwo, nie posiadanie tokenu
Dział zatytułowany „Autoryzuje członkostwo, nie posiadanie tokenu”Osoba, która zainstalowała aplikację, nie jest osobą wysyłającą wiadomość. Dlatego każde zdarzenie przychodzące zostaje sprowadzone do Principal, zanim cokolwiek się stanie, i kończy się bezpieczną odmową wraz z wyjaśnieniem:
- Niepołączone konto czatu dowiaduje się, jak je połączyć, w wątku Slacka, w którym napisało.
- Połączone konto, którego użytkownik nie jest członkiem tego obszaru roboczego, dowiaduje się, że ma poprosić administratora o zaproszenie.
- Członek, którego rola jest tylko do odczytu (
viewer,guest), dowiaduje się, że jego rola nie pozwala rozpocząć pracy.
Żaden z tych przypadków nie jest realizowany. Wiadomość z czatu nigdy nie zostaje przypisana komuś, kto jej nie wysłał — właśnie dzięki temu dziennik audytu, limity szybkości na Principal i autorzy zgłoszeń pozostają zgodne z prawdą. Model członkostwa, na którym to się opiera, opisuje sekcja Wieloosobowość — tożsamość, członkostwo i obecność.
Połączenie konta to ustalenie tożsamości, nie nadanie dostępu. Członkostwo jest sprawdzane od nowa przy każdym zdarzeniu, więc połączenie należące do kogoś, kto został w międzyczasie usunięty, kończy się odmową, a nie dawnymi uprawnieniami. Dwie ścieżki prowadzą do połączenia konta: krótkotrwały jednorazowy kod, który członek wpisuje na czacie, albo automatyczne dopasowanie po zweryfikowanym adresie e-mail dostawcy — a ścieżka e-mailowa nadal wymaga, by dopasowany użytkownik był już członkiem, więc obcy, którego adres przypadkiem istnieje na serwerze, nie zostaje z niczym połączony.
Kody są celowo trzymane w pamięci, z 15-minutowym czasem życia. Utrwalenie jednego z nich tworzyłoby trwały wiersz w kształcie danych uwierzytelniających dla czegoś, co ma wyparować, a unieważnienie wszystkich oczekujących kodów przez restart serwera jest zachowaniem poprawnym, nie regresją — członek ponownie naciska „Połącz”.
Odmowa jest udzielana w wątku Slacka, który otworzyła wiadomość, jako prawdziwa odpowiedź. Odpowiedź w wątku oznacza wiadomość, na którą odpowiada, dociera do jedynej osoby, która jej potrzebuje, a jednocześnie nie wpuszcza komunikatu „Twoje konto nie jest połączone” do samego kanału. Wysyłanie wiadomości to zarazem jedyna możliwość, którą ma każdy dostawca, więc wyjaśnienie nigdy nie zależy od funkcji, której aplikacja może nie mieć.
Pochodzenie i problem echa
Dział zatytułowany „Pochodzenie i problem echa”Most, który odzwierciedla ruch w obie strony, wpadnie w pętlę, jeśli nic nie
odróżni tych kierunków. Każda wiadomość wnoszona przez most jest opatrzona
stemplem metadata['chat'] — dostawca, zespół, rozmowa, wątek, wiadomość i
nadawca. Wychodzące odzwierciedlenie pomija każdą wiadomość noszącą ten
stempel.
Ta jedna reguła czyni dwukierunkowe odzwierciedlanie bezpiecznym: wiadomość napisana przez człowieka w Control Center jest przekazywana na zewnątrz, do połączonego wątku (z nazwą autora na początku), a wiadomość, która przyszła z czatu, zatrzymuje się tam, gdzie weszła.
To, co wychodzi na zewnątrz, jest celowo wąskie. Odpowiedź agenta jest
przekazywana jako zwykły tekst czatu. Krótka myśl może pojawić się w wierszu
Thinking… karty. Dane wyjściowe narzędzi to ściana diffów i zostają w Control
Center, gdzie również wskazuje skrócona odpowiedź.
Karta raportuje pracę; wiadomością pozostaje tekst
Dział zatytułowany „Karta raportuje pracę; wiadomością pozostaje tekst”Tam, gdzie dostawca je renderuje, tura niesie jedną kartę zadania, edytowaną w miejscu przez cały czas trwania tury i umieszczoną nad odpowiedzią, którą objaśnia.
Karta to jeden pogrupowany plan, a nie zlepiony akapit. Slack skleja details i
output w tym samym zadaniu, więc szczegóły wiersza są wysyłane raz (myśl w
wierszu Thinking…), a output nigdy nie jest strumieniowany. Przygotowanie to
jeden wiersz, którego tytuł jest podmieniany (Working on it…, Cloning …).
Rozumowanie dodaje wiersz Thinking…. Każde narzędzie ma własny wiersz
(Read lib/main.dart). Odpowiedź to zwykły tekst pod kartą — widok planu Slacka
odczytuje ją z późniejszego dopisku markdown, a nie z fragmentu wmieszanego w
zadania. Nic więcej: żadnych danych wyjściowych narzędzi i tylko argument
narzędzia z krótkiej listy dozwolonych, bo argumenty nieznanego narzędzia mogą
być całym ciałem pliku, a wiersz to jedna linia.
Karta obejmuje też oczekiwanie przed turą. Pierwsza wzmianka tworzy rozmowę,
a jej utworzenie klonuje każde repozytorium, w którym agent będzie pracować — co
na prawdziwym repozytorium oznacza minuty, w których agent nie został jeszcze o
nic zapytany, a wątek wygląda, jakby bot zignorował prośbę. Dlatego karta
otwiera się w chwili przyjęcia wzmianki (Working on it…), a potem ten wiersz
przygotowania aktualizuje się tymi samymi słowami, które pokazuje desktop —
Cloning acme/widgets…, Setting up Reviewer…, Starting the agent… —
następnie pojawia się wiersz Thinking…, gdy tylko agent zaczyna rozumować, a
potem wiersz na każde narzędzie. Jedna karta, jedna wiadomość, od „słyszę cię”
aż po odpowiedź jako zwykły tekst poniżej. Przygotowanie jest raportowane tylko
tam, gdzie ktoś na czacie na nie czeka: rozmowa przygotowywana w aplikacji albo
osierocona, wznawiana przy starcie serwera, jest przygotowywana bez żadnej
karty.
Karta niesie jedyne, czego czat nie może dać czytelnikowi: link View in
Control Center do rozmowy stojącej za odpowiedzią. Produkty czatowe
przyjmują w linku http(s) i nic więcej, więc przycisk wskazuje na niewielką
stronę, którą serwuje ten serwer, a ona przekierowuje przeglądarkę do linku
control-center:// aplikacji desktopowej. Ta strona niczego nie czyta i
niczego nie rozwiązuje — identyfikatory z jej adresu URL trafiają do linku
bezpośredniego i nigdzie indziej — a serwer bez osiągalnego publicznego adresu
URL po prostu tworzy kartę bez przycisku, a nie zepsutą.
Karty to możliwość jak każda inna. Dostawca bez nich zachowuje się dokładnie jak
wcześniej, tylko tekstowo, a dostawca, który odmówi karty w chwili wywołania,
zachowuje swój strumień: odpowiedź zawsze ma pierwszeństwo przed kartą, która
ją opisuje. Gdy strumieniowanie jest całkiem wyłączone, gotowa karta jedzie w
pojedynczej opublikowanej wiadomości. Utworzone zgłoszenie dostaje to samo
traktowanie — potwierdzenie /cc ticket to ukończona karta, której powodem
istnienia jest link do właśnie utworzonego zgłoszenia.
Możliwości są negocjowane, a potem zapamiętywane
Dział zatytułowany „Możliwości są negocjowane, a potem zapamiętywane”Produkty czatowe różnią się tym, co potrafią renderować, więc most jest pisany pod najbogatszą powierzchnię — odpowiedź, która rośnie na żywo w wątku, przemijający status „is thinking…”, wątek z tytułem, ulotna odpowiedź na polecenie slash — i traci po jednej możliwości naraz. Dostawca, który nie ma żadnej z nich, nadal działa; odpowiedzi docierają po prostu jako jedna wiadomość.
Zgłoszenie możliwości to jeszcze nie obietnica. Strumieniowe odpowiedzi Slacka wymagają funkcji płatnego planu, a Slack odmawia dopiero w chwili wywołania. Odmowa oznaczona jako trwała jest zapamiętywana na cały czas życia połączenia: most przestaje pytać i odtąd publikuje całe odpowiedzi, a ustawienia mówią o tym wprost, bo na pytanie „moje odpowiedzi przestały się strumieniować” trzeba umieć odpowiedzieć bez czytania logów. Karty zadań są odrzucane w ten sam sposób, jeśli Slack odmówi którejś.
Każdy limit należy do dostawcy, a nie do przekaźnika: pułap liczby wiadomości, pułap fragmentów strumienia i to, czy strumień potrzebuje kotwicy wątku, deklaruje adapter, a długa odpowiedź jest dzielona na granicy akapitu, wiersza lub słowa, nigdy w środku wyrazu.
Jeden most, jeden adapter na produkt
Dział zatytułowany „Jeden most, jeden adapter na produkt”Wszystko, co dotyczy konkretnego produktu, żyje za portem. Ogólny rdzeń zna
markdown, rozmowy czatowe, wątki czatu i członków; nie wie nic o kopertach
Slacka, mrkdwn czy Gateway Intents. Trzy elementy to umożliwiają:
- Adapter normalizuje jeden produkt: jego transport, wywołania API, kodek
tekstowy i osobliwości kopert. Przypadek Slacka jest pouczający — wzmianka na
kanale przychodzi dwa razy (jako
app_mentioni jakomessage), a większość podtypów wiadomości to ozdobnik, a nie nowe żądania. Jedno i drugie jest rozwiązywane w adapterze, więc rdzeń nigdy nie dowiaduje się, że Slack dostarcza podwójnie. - Deskryptor to publiczny kształt dostawcy: jakich danych uwierzytelniających potrzebuje, jakie są ich formaty, które adresy URL pomagają użytkownikowi je znaleźć, co potrafi i czy jego aplikację można utworzyć lub edytować stąd. Karta ustawień, okno połączenia i walidacja argumentów są w całości z niego generowane — klient odsyła dostawcę oraz mapę kluczowaną identyfikatorem pola.
- Wtyczka to kompozycja po stronie serwera: jak dane uwierzytelniające stają się zweryfikowanym połączeniem, jak budowany jest adapter i jak zarządzana jest aplikacja po stronie dostawcy.
Dodanie produktu to więc jedna wartość enuma, jeden adapter i jedna wtyczka. Żadnej nowej operacji RPC, żadnej zmiany schematu, żadnej zmiany klienta — a klient pozostaje uczciwy, bo pole, o którym nigdy nie słyszał, nadal renderuje się z etykietą podaną przez serwer.
Dwa szczegóły nie pozwalają abstrakcji przeciekać do góry. Kroki zarządzania aplikacją, dla których dostawca nie ma API (wygeneruj token, zainstaluj aplikację, zainstaluj ją ponownie po zmianie uprawnień), wracają jako nazwane kroki z własnymi linkami, więc ekran dokończenia konfiguracji pokazuje je, nie wiedząc, o jakiego dostawcę chodzi. A każda instrukcja, którą pisze most, wymienia dostawcę i polecenie, którego naprawdę używa aplikacja, więc zmieniona nazwa polecenia albo drugi dostawca czyta się poprawnie bez nowego tekstu.
Gdzie leżą dane uwierzytelniające
Dział zatytułowany „Gdzie leżą dane uwierzytelniające”Tokeny czatu obszaru roboczego leżą w jego własnym katalogu, obok jego bazy
danych — jako zwykły JSON w
<dataDir>/<workspaceId>/chat_credentials/<provider>.json, a nie w bazie danych
i celowo nie w pęku kluczy systemu operacyjnego. Dwie konsekwencje łatwo później
zgubić: usunięcie obszaru roboczego odłącza katalog i tokeny znikają wraz z nim,
a eksport lub kopia zapasowa obszaru roboczego kopiuje workspace.db, więc
obszar roboczy przekazany komuś innemu nie nosi w sobie aktywnych tokenów bota.
Plik dziedziczy granicę zaufania systemu plików hosta serwera — wszystko, co ma
dostęp do odczytu katalogu danych, może odczytać token bota, a za tą samą
granicą leżą bazy danych.
Jeden plik na dostawcę, zapisywany atomowo i tylko dla właściciela: odwołana aplikacja znika przez usunięcie jednego pliku, a uszkodzony plik kosztuje jednego dostawcę, a nie wszystkich. Dane uwierzytelniające wchodzą do środka przez operację połączenia i nigdy nie wychodzą na zewnątrz — odczyty statusu odpowiadają wyłącznie metadanymi połączenia.
Co świadomie nie jest oferowane
Dział zatytułowany „Co świadomie nie jest oferowane”- Strukturalne ustawienia aplikacji. Transport, zakresy i subskrypcje zdarzeń są narzucane, a nie udostępniane. Aplikacja, którą da się wyciszyć z ekranu ustawień, to zgłoszenie do wsparcia, a nie funkcja.
- Rozmowa i polecenia slash. Powierzchnia Slacka to wiadomości, odpowiedzi w wątku i polecenia slash.
- Drugie źródło prawdy. Czat nie jest miejscem, w którym przechowuje się pracę. Wątek Slacka to przestrzeń, odpowiedź to transkrypcja, zgłoszenie to zgłoszenie — wszystko w obszarze roboczym, osiągalne z każdego klienta.
Zobacz też
Dział zatytułowany „Zobacz też”- Wieloosobowość — tożsamość, członkostwo i obecność: Principal i role, przez które rozstrzyga most
- Obszary robocze i izolacja: dlaczego połączenia kont i dane uwierzytelniające są przypisane do obszaru roboczego
- Model agentów: co się budzi, gdy przychodzi wzmianka
- Zgłoszenia i delegowanie: co tworzy
/cc ticket - Architektura: gdzie most siedzi między serwerem a klientami
- Połącz Slack z obszarem roboczym
- Skonfiguruj integrację Slack
- Połącz konto Slack
- Dostosuj bota czatu
- Rozmowy w PR: te same trzy decyzje, podjęte dla forge’a