Přeskočit na obsah

Chatové mosty

Chatový most udělá z externího chatového produktu, dnes ze Slacku, plnohodnotnou cestu do pracovního prostoru Control Center. Zmíníš bota a agent se probudí, odpovíš ve vláknu Slacku a konverzace pokračuje, napíšeš příkaz s lomítkem a založí se ticket. Pod tím jsou stejné prostory, přepisy, tickety a přisouzení, které používá desktopová aplikace. Chat je plocha na pracovním prostoru, ne jeho kopie.

Dodává se jen Slack. Implementace Discordu, Telegramu, Teams ani Matrixu v produktu nikde není. Funkce je schválně napsaná tak, aby poskytovatele neznala. Přidat druhý produkt je jedna hodnota výčtu, jeden adaptér a jeden plugin. Dnes ale „chat“ znamená Slack.

Dvě plochy to pokryjí a každá patří někomu jinému. Připojení pracovního prostoru ke Slacku a úprava bota jsou akce admina v Nastavení → Pracovní prostor → Obecné. Propojení vlastního chatového účtu s identitou v Control Center je akce člena v Nastavení → Pracovní prostor → Profil a identita.

Celou funkci určují tři rozhodnutí: kterým směrem spojení běží, co autorizuje zprávu, která přijde zvenku, a kde smí žít znalost konkrétního produktu.

Most na poskytovatele nikdy neposlouchá. Ke Slacku se dostane přes Socket Mode: server si od Slacku vyžádá URL socketu a zavolá ven, pak po tom spojení přijímá zmínky, přímé zprávy a příkazy s lomítkem.

O ten důsledek jde. Notebook za NAT, domácí server bez domény, stroj, který střídá sítě. Všechny můžou most spustit bez příchozího portu, bez tunelu, bez certifikátu TLS a bez veřejné URL, která by se při změně musela střídat. Koncový bod webhooku, který by bylo třeba zabezpečit, neexistuje, protože žádný koncový bod webhooku není. Když Control Center skládá definici aplikace, vynutí Socket Mode a zahodí každou request URL. Aplikace nastavená na obojí by fungovala jen napůl.

Platí se za spojení, které musí zůstat naživu, a to je práce serveru. Opětovné připojení je exponenciální backoff s jitterem, nanejvýš 30 sekund. Když socket otočí sám poskytovatel, most se připojí znovu a ten backoff přitom nezvyšuje. Odmítnuté přihlašovací údaje jsou konečné. Opakovat zrušený token donekonečna by vedlo jen k limitu četnosti, takže most zastaví, počká na nové tokeny a v nastavení ukáže chybu poskytovatele doslovně.

Pracovní prostor bez chatové aplikace nestojí nic: žádný socket, žádné časovače, žádný provoz. A celé to startuje až po hlášce, že je server připravený. Chatová aplikace, která odpovídá pomalu, nesmí zdržet start.

Autorizuje členství, ne držení tokenu

Sekce “Autorizuje členství, ne držení tokenu”

Kdo aplikaci nainstaloval, není ten, kdo posílá zprávu. Každá příchozí událost se proto nejdřív převede na principála a když to nejde, selže uzavřeně, s vysvětlením:

  • Nepropojený chatový účet dostane ve vláknu Slacku, do kterého psal, návod, jak se propojit.
  • Propojený účet, jehož uživatel není členem tohoto pracovního prostoru, dostane pokyn, ať požádá admina o pozvánku.
  • Člen s rolí jen ke čtení (viewer, guest) se dozví, že jeho role nemůže začít práci.

Žádná z těch cest nepokračuje. Chatová zpráva se nikdy nepřiřadí někomu, kdo ji neposlal. Právě to drží v pravdě auditní záznam, limity četnosti na principála i nahlašovatele ticketů. Model členství, o který se to opírá, je v Multiplayer — identita, členství a přítomnost.

Propojení je určení identity, ne udělení přístupu. Členství se kontroluje znovu při každé události, takže propojení někoho, kdo byl mezitím odebrán, skončí odmítnutím, ne jeho starými oprávněními. Propojení vzniká dvěma cestami: krátkodobým jednorázovým kódem, který člen napíše do chatu, nebo automatickou shodou na ověřeném e-mailu poskytovatele. I cesta e-mailem vyžaduje, aby shodný uživatel už byl členem. Cizí člověk, jehož adresa na serveru náhodou existuje, se nepropojí s ničím.

Kódy zůstávají schválně jen v paměti, s platností 15 minut. Uložit takový kód by vytvořilo trvalý řádek ve tvaru přihlašovacích údajů pro něco, co má zmizet. Restart serveru, který zneplatní každý nevyužitý kód, je správné chování, ne regrese. Člen znovu stiskne „Propojit“.

Odmítnutí přijde ve vláknu Slacku, které zpráva otevřela, jako skutečná odpověď. Odpověď ve vláknu označí zprávu, na kterou reaguje, dojde k tomu jednomu člověku, který to potřebuje, a „účet není propojený“ zůstane mimo samotný kanál. Odeslat zprávu je taky jediná schopnost, kterou má každý poskytovatel, takže vysvětlení nezávisí na funkci, kterou aplikace třeba nemá.

Původ a problém ozvěny

Sekce “Původ a problém ozvěny”

Most, který zrcadlí oběma směry, se zacyklí, pokud něco oba směry nerozliší. Každá zpráva, kterou most přinese, dostane razítko metadata['chat']: poskytovatel, tým, konverzace, vlákno, zpráva a odesílatel. Odchozí zrcadlo každou zprávu s tím razítkem přeskočí.

To jediné pravidlo dělá obousměrné zrcadlení bezpečné. Lidská zpráva napsaná v Control Center se předá do propojeného vlákna, s jménem autora na začátku. Zpráva, která přišla z chatu, skončí tam, kde vstoupila.

Ven jde schválně málo. Odpověď agenta se předá jako obyčejný chatový text. Krátká myšlenka se může objevit na řádku Thinking… karty. Výstup nástroje je zeď diffů a zůstane v Control Center, kam ukazuje i zkrácená odpověď.

Karta hlásí práci; text zůstává zprávou

Sekce “Karta hlásí práci; text zůstává zprávou”

Tam, kde je poskytovatel vykreslí, nese tah jednu kartu úkolu. Upravuje se na místě, dokud tah běží, a stojí nad odpovědí, kterou vysvětluje.

Karta je jeden seskupený plán, ne slité odstavce. Slack slepí details a output na stejný úkol, takže podrobnosti řádku se pošlou jednou (myšlenka na Thinking…) a output se nikdy nestreamuje. Příprava je jeden řádek, jehož nadpis se nahrazuje (Working on it…, Cloning …). Uvažování přidá řádek Thinking…. Každý nástroj má vlastní řádek (Read lib/main.dart). Odpověď je obyčejný text pod kartou. Pohled na plán ve Slacku ji čte z markdownu přidaného později, ne z kusu zamíchaného do úkolů. Nic dalšího: žádný výstup nástroje a z argumentů nástroje jen to, co je na krátkém seznamu povolených. Argumenty neznámého nástroje můžou být celé tělo souboru a řádek je jeden řádek.

Karta pokryje i čekání před tahem. První zmínka vytvoří konverzaci a její vytvoření naklonuje každý repozitář, ve kterém bude agent pracovat. Na skutečném repozitáři jsou to minuty, kdy se agenta ještě nikdo na nic neptal a vlákno vypadá, jako by bot požadavek ignoroval. Karta se proto otevře v okamžiku, kdy je zmínka přijata (Working on it…), a řádek přípravy se pak aktualizuje stejnými slovy, která ukazuje desktopová aplikace: Cloning acme/widgets…, Setting up Reviewer…, Starting the agent…, pak řádek Thinking…, jakmile agent začne uvažovat, a pak řádek na každý nástroj. Jedna karta, jedna zpráva, od „slyšel jsem tě“ až po odpověď jako obyčejný text pod ní. O přípravě se hlásí jen tam, kde na ni v chatu někdo čeká. Konverzace připravovaná v aplikaci, nebo přerušená a obnovená při startu serveru, se připraví úplně bez karty.

Karta nese jediné, co chat čtenáři dát neumí: odkaz View in Control Center na konverzaci za odpovědí. Chatové produkty v odkazu přijmou http(s) a nic jiného, takže tlačítko míří na malou stránku, kterou obsluhuje tento server. Ta prohlížeč přesměruje na odkaz control-center:// desktopové aplikace. Ta stránka nic nečte a nic nevyhledá. Identifikátory v její URL se jen propíšou do deep linku a nikam jinam. Server bez dosažitelné veřejné URL vyrobí kartu bez tlačítka, ne s rozbitým.

Karty jsou schopnost jako každá jiná. Poskytovatel bez nich se chová jako dřív, jen text. Poskytovatel, který jednu odmítne v okamžiku volání, si stream nechá. Odpověď má vždycky přednost před kartou, která ji popisuje. Když je streamování vypnuté úplně, hotová karta přijde s jedinou odeslanou zprávou. Založený ticket dostane totéž. Potvrzení /cc ticket je dokončená karta a důvod, proč existuje, je odkaz na právě vytvořený ticket.

Schopnosti se vyjednají a pak zapamatují

Sekce “Schopnosti se vyjednají a pak zapamatují”

Chatové produkty se liší v tom, co umí vykreslit, takže most je napsaný proti nejbohatší ploše. Odpověď, která ve vláknu roste živě, pomíjivý stav „is thinking…“, vlákno s nadpisem, efemérní odpověď na příkaz s lomítkem. Pak ubírá jednu schopnost po druhé. Poskytovatel, který z toho nemá nic, pořád funguje. Odpovědi prostě přijdou jako jedna zpráva.

Ohlásit schopnost neznamená slíbit ji. Streamované odpovědi Slacku potřebují funkci placeného plánu a Slack odmítne až v okamžiku volání. Odmítnutí označené jako trvalé se pamatuje po celou dobu života spojení. Most přestane žádat a od té chvíle posílá celé odpovědi a nastavení to napíše jasně, protože na „přestaly mi streamovat odpovědi“ má jít odpovědět bez čtení logu. Karty úkolů se zahodí stejně, když Slack jednu odmítne.

Každý limit patří poskytovateli, ne vrstvě, která zprávy předává: strop zprávy, strop kusu streamu a jestli stream potřebuje kotvu vlákna. Všechno to deklaruje adaptér a dlouhá odpověď se dělí na hranici odstavce, řádku nebo slova, ne uprostřed slova.

Jeden most, jeden adaptér na produkt

Sekce “Jeden most, jeden adaptér na produkt”

Všechno specifické pro produkt žije za portem. Obecné jádro zná markdown, chatové konverzace, chatová vlákna a členy. O obálkách Slacku, o mrkdwn ani o gateway intents neví nic. Funguje to díky třem kusům:

  • Adaptér sjednotí jeden produkt: jeho přenos, volání API, textový kodek a zvláštnosti obálky. Zvláštnosti Slacku jsou poučné. Zmínka v kanálu přijde dvakrát, jako app_mention a jako message, a většina podtypů zpráv je obal, ne nové požadavky. Obojí se vyřeší v adaptéru, takže jádro se nikdy nedozví, že Slack doručuje dvakrát.
  • Deskriptor je veřejný tvar poskytovatele: jaké přihlašovací údaje potřebuje, v jakých formátech, které URL pomůžou je najít, co umí a jestli se jeho aplikace dá vytvořit nebo upravit odsud. Karta nastavení, dialog připojení i kontrola argumentů se z něj generují. Klient pošle zpátky poskytovatele a mapu s klíčem podle id pole.
  • Plugin je složení na straně serveru: jak se z přihlašovacích údajů stane ověřené spojení, jak se postaví adaptér a jak se spravuje aplikace u poskytovatele.

Přidat produkt je tedy jedna hodnota výčtu, jeden adaptér a jeden plugin. Žádná nová RPC operace, žádná změna schématu, žádná změna klienta. A klient zůstane přesný, protože pole, o kterém nikdy neslyšel, se stejně vykreslí s popiskem, který dodal server.

Dva detaily brání tomu, aby abstrakce prosakovala nahoru. Kroky správy aplikace, pro které poskytovatel nemá API (vygenerovat token, nainstalovat aplikaci, znovu nainstalovat po změně oprávnění), se vracejí jako pojmenované kroky s vlastními odkazy. Obrazovka dokončení nastavení je ukáže, aniž by věděla, který poskytovatel to je. A každá instrukce, kterou most napíše, jmenuje poskytovatele a příkaz, který aplikace opravdu používá. Přejmenovaný příkaz nebo druhý poskytovatel se tak přečte správně bez nového textu.

Kde leží přihlašovací údaje

Sekce “Kde leží přihlašovací údaje”

Chatové tokeny pracovního prostoru leží v jeho vlastním adresáři, vedle databáze, jako prosté JSON v <dataDir>/<workspaceId>/chat_credentials/<provider>.json, ne v databázi a schválně ne v systémové klíčence. Dva důsledky se později snadno ztratí. Smazání pracovního prostoru adresář odlinkuje a tokeny odcházejí s ním. Export nebo záloha pracovního prostoru kopíruje workspace.db, takže pracovní prostor předaný někomu jinému v sobě nenese živé tokeny bota. Soubor dědí hranici důvěry souborového systému stroje, na kterém server běží. Kdokoli s právem číst datový adresář přečte token bota, a za tou samou hranicí sedí i databáze.

Jeden soubor na poskytovatele, zápis je atomický a práva má jen vlastník. Zrušená aplikace zmizí odlinkováním jednoho souboru a poškozený soubor stojí jednoho poskytovatele, ne všechny. Přihlašovací údaje putují dovnitř operací připojení a nikdy se nevracejí ven. Čtení stavu odpoví jen metadaty spojení.

Co se záměrně nenabízí

Sekce “Co se záměrně nenabízí”
  • Strukturální nastavení aplikace. Přenos, scopes a odběry událostí se vynucují, nevystavují. Aplikace, kterou jde z obrazovky nastavení umlčet, je ticket na podporu, ne funkce.
  • Konverzace a příkazy s lomítkem. Plocha Slacku jsou zprávy, odpovědi ve vláknech a příkazy s lomítkem.
  • Druhý zdroj pravdy. Chat není místo, kde se práce ukládá. Vlákno Slacku je prostor, odpověď je přepis, ticket je ticket. Všechno v pracovním prostoru, dostupné z každého klienta.