Csevegőhidak
A csevegőhíd egy külső csevegőterméket — ma a Slacket — első osztályú belépési úttá tesz egy Control Center-munkaterületre. Ha megemlíti a botot, felébred egy ügynök; ha a Slack szálában válaszol, folytatódik a beszélgetés; ha beír egy slash-parancsot, létrejön egy jegy. Alatta ugyanazok a terek, átiratok, jegyek és ugyanaz a hozzárendelés, amelyeket az asztali alkalmazás használ. A csevegés felület a munkaterületen, nem annak másolata.
A Slack az egyetlen szállított szolgáltató. A termékben sehol sincs Discord-, Telegram-, Teams- vagy Matrix-implementáció. A funkció szándékosan szolgáltatófüggetlenül íródott — egy második termék hozzáadása egyetlen enumérték, egy adapter és egy plugin —, ma azonban a „csevegés” a Slacket jelenti.
Két felület fedi le, és ezek különböző emberekhez tartoznak. A munkaterület Slackhez csatlakoztatása és a bot testreszabása admin művelet a Beállítások → Munkaterület → Általános útvonalon. A saját csevegőfiók összekapcsolása a Control Center-identitással tagi művelet a Beállítások → Munkaterület → Profil és identitás útvonalon.
Három döntés alakítja az egész funkciót: melyik irányban fut a kapcsolat, mi jogosít fel egy kívülről érkező üzenetet, és hol élhet a termékspecifikus tudás.
Szándékosan kifelé irányuló
Szekció neve “Szándékosan kifelé irányuló”A híd soha nem figyeli a szolgáltatót. A Slack Socket Mode-on keresztül érhető el: a szerver socket URL-t kér a Slacktől, és kifelé tárcsáz, majd ezen a kapcsolaton keresztül fogadja az említéseket, a közvetlen üzeneteket és a slash-parancsokat.
A következmény a lényeg. Egy NAT mögötti laptop, egy domain nélküli otthoni szerver, egy hálózatok között vándorló gép — mindegyik futtathatja a hidat bejövő port, alagút és TLS-tanúsítvány nélkül, és anélkül, hogy nyilvános URL-t kellene cserélni, amikor az megváltozik. Nincs biztosítandó webhookvégpont, mert nincs webhookvégpont. Amikor a Control Center összeállítja az alkalmazásdefiníciót, kikényszeríti a Socket Mode-ot, és eltávolít minden kérési URL-t, mert egy mindkettőre beállított alkalmazás félig működő alkalmazás lenne.
Az ára egy életben tartandó kapcsolat, ami a szerver dolga: az újracsatlakozás exponenciális visszakapcsolás (exponential backoff) jitterrel, legfeljebb 30 másodpercre korlátozva; a szolgáltató által kezdeményezett socket-rotáció újracsatlakozik, anélkül hogy fokozná ezt a visszakapcsolást; egy elutasított hitelesítő adat pedig végleges — egy visszavont token végtelen újrapróbálása csak sebességkorlátot hozna, ezért a híd leáll, és új tokenekre vár, miközben a szolgáltató saját hibáját szó szerint jelenti a beállításokban.
Egy csevegőalkalmazás nélküli munkaterület semmibe sem kerül: nincs socket, nincsenek időzítők, nincs forgalom. És az egész csak azután indul el, hogy a szerver jelzi a kész állapotot, mert egy lassan válaszoló csevegőalkalmazás soha nem késleltetheti az indítást.
A tagság jogosít fel, nem a token birtoklása
Szekció neve “A tagság jogosít fel, nem a token birtoklása”Az alkalmazást telepítő személy nem azonos az üzenetet küldő személlyel. Ezért minden bejövő esemény Principalra oldódik fel, mielőtt bármi megtörténne, és minden esetben elutasítással zárul, magyarázattal együtt:
- Egy nem összekapcsolt csevegőfiók abban a Slack szálban, amelybe írt, útmutatást kap az összekapcsolásra.
- Egy összekapcsolt fiók, amelynek felhasználója nem tagja ennek a munkaterületnek, arra kap útmutatást, hogy kérjen meghívást egy admintól.
- Egy tag, akinek a szerepköre csak olvasható (
viewer,guest), megtudja, hogy a szerepköre nem indíthat munkát.
Ezek egyike sem folytatódik. Egy csevegőüzenetet soha nem rendelünk olyasvalakihez, aki nem küldte el — ez tartja korrekt módon az auditnaplót, a Principalonkénti sebességkorlátokat és a jegyek bejelentőit. A tagsági modellt, amelyre ez támaszkodik, lásd a Többszereplős mód és jelenlét oldalon.
Az összekapcsolás identitásfeloldás, nem hozzáférési jogosultság megadása. A tagság minden eseménynél újraellenőrzésre kerül, így egy olyan valakihez tartozó kapcsolat, akit azóta eltávolítottak, elutasítást eredményez a régi kiváltságai helyett. Két útvonal hoz létre kapcsolatot: egy rövid életű, egyszer használatos kód, amelyet a tag a csevegésben beír, vagy automatikus egyezés a szolgáltató ellenőrzött e-mail-címére — és az e-mailes útvonal továbbra is megköveteli, hogy az egyező felhasználó már tag legyen, így egy idegen, akinek a címe véletlenül létezik a szerveren, semmihez sem kapcsolódik.
A kódok szándékosan a memóriában maradnak, 15 perces élettartammal. Egy kód tartósítása egy tartós, hitelesítő adatra hasonlító sort hozna létre valami olyasmihez, aminek el kell párolognia, és az, hogy a szerver újraindítása minden függőben lévő kódot érvénytelenít, helyes viselkedés, nem visszaesés — a tag ismét az „A Slack fiókom összekapcsolása” gombra kattint.
Az elutasításra abban a Slack szálban érkezik válasz, amelyet az üzenet megnyitott, valódi válaszként. A szálban küldött válasz megjelöli azt az üzenetet, amelyre válaszol, eléri az egyetlen személyt, akinek szüksége van rá, és közben a „nincs összekapcsolva” üzenetet is távol tartja magától a csatornától. Az üzenetküldés ráadásul az az egyetlen képesség, amellyel minden szolgáltató rendelkezik, így a magyarázat soha nem függ olyan funkciótól, amelyet az alkalmazás esetleg nem hordoz.
Eredet és az echóprobléma
Szekció neve “Eredet és az echóprobléma”Egy híd, amely mindkét irányba tükröz, hurkolódni fog, hacsak valami meg nem
különbözteti a két irányt. Minden üzenet, amelyet a híd behoz, metadata['chat']
bélyegzőt kap — szolgáltató, csapat, beszélgetés, szál, üzenet és küldő. A
kifelé irányuló tükrözés kihagy minden ilyen bélyegzőt viselő üzenetet.
Ez az egyetlen szabály teszi biztonságossá a kétirányú tükrözést: egy Control Centerben beírt emberi üzenet továbbítódik a csatolt szálba (az író nevével az élen), míg egy csevegésből érkező üzenet ott áll meg, ahol belépett.
Ami kifelé átmegy, szándékosan szűk. Az ügynök válasza közönséges
csevegőszövegként továbbítódik. Egy rövid gondolat megjelenhet a kártya
Thinking… sorában. Az eszközkimenet diffek falát alkotja, és a Control
Centerben marad, ahová egy csonkolt válasz is mutat.
A kártya a munkáról számol be; a szöveg továbbra is az üzenet
Szekció neve “A kártya a munkáról számol be; a szöveg továbbra is az üzenet”Ahol a szolgáltató megjeleníti őket, egy kör egyetlen feladatkártyát hordoz, amelyet a kör futásának teljes ideje alatt a helyén szerkesztenek, és amely az általa magyarázott válasz felett helyezkedik el.
A kártya egy csoportosított terv, nem összegyúrt bekezdés. A Slack ugyanahhoz a
feladathoz fűzi a details és az output mezőt, ezért egy sor részletei egyszer
mennek ki (a gondolat a Thinking… sorban), az output pedig soha nem
streamelődik. A beállítás egyetlen sor, amelynek címe cserélődik
(Working on it…, Cloning …). Az érvelés hozzáad egy Thinking… sort. Minden
eszköz a saját sorát kapja (Read lib/main.dart). A válasz közönséges szöveg a
kártya alatt — a Slack tervnézete egy későbbi markdown-hozzáfűzésből olvassa ki,
nem a feladatok közé keveredett darabból. Semmi más: eszközkimenet nem, és az
eszköz argumentumai közül is csak egy rövid engedélylistáról, mert egy ismeretlen
eszköz argumentumai egy egész fájl törzsét is jelenthetik, egy sor pedig egyetlen
sor.
A kártya a kör előtti várakozást is lefedi. Az első említés létrehozza a
beszélgetést, annak létrehozása pedig minden repót klónoz, amelyben az ügynök
dolgozni fog — ami egy valódi tárolónál perceket jelent, amikor az ügynöktől még
senki sem kért semmit, a szál pedig úgy néz ki, mintha a bot figyelmen kívül
hagyta volna a kérést. A kártya ezért abban a pillanatban nyílik meg, amikor az
említést elfogadják (Working on it…), majd ez a beállítási sor ugyanazokkal a
szavakkal frissül, amelyeket az asztali alkalmazás mutat —
Cloning acme/widgets…, Setting up Reviewer…, Starting the agent… —, aztán
egy Thinking… sor, amint az ügynök elkezd gondolkodni, majd soronként egy-egy
eszköz. Egy kártya, egy üzenet, a „hallottalak” pillanattól a válaszig, amely
alatta közönséges szövegként jelenik meg. A beállítás csak ott kerül
jelentésre, ahol valaki a csevegésben vár rá: egy alkalmazásban éppen
előkészített beszélgetés vagy egy szerverindításkor folytatott, elakadt
beszélgetés kártya nélkül provisionálódik.
Azt az egyetlen dolgot hordozza, amelyet a csevegés nem adhat meg az olvasónak:
egy View in Control Center hivatkozást a válasz mögötti beszélgetésre. A
csevegőtermékek egy hivatkozásban http(s)-t fogadnak el, mást nem, ezért a
gomb egy kis oldalra mutat, amelyet ez a szerver szolgál ki, és amely a
böngészőt az asztali alkalmazás control-center:// hivatkozására irányítja. Az
az oldal semmit nem olvas és semmit nem old fel — az URL-jében lévő azonosítók
egy deep linkbe csengenek vissza, máshová nem —, és egy elérhető nyilvános URL
nélküli szerver egyszerűen gomb nélküli kártyát állít elő, nem pedig egy
hibásat.
A kártyák olyan képességek, mint bármelyik másik. Egy szolgáltató nélkülük
pontosan úgy viselkedik, mint korábban, csak szöveggel, és egy szolgáltató,
amely egy kártyát a hívás pillanatában elutasít, megtartja a streamjét: a válasz
mindig megelőzi az őt leíró kártyát. Amikor a streamelés teljesen ki van
kapcsolva, a kész kártya helyette az egyetlen elküldött üzenetre kerül rá. Egy
létrehozott jegy ugyanezt a bánásmódot kapja — a /cc ticket visszaigazolása egy
befejezett kártya, amelynek létezésének oka a nemrég létrehozott jegyre mutató
hivatkozás.
A képességekről tárgyalunk, majd megjegyezzük őket
Szekció neve “A képességekről tárgyalunk, majd megjegyezzük őket”A csevegőtermékek abban különböznek, hogy mit tudnak megjeleníteni, ezért a híd a leggazdagabb felületre íródott — egy válasz, amely élőben növekszik egy szálban, egy átmeneti „gondolkodik…” állapot, egy címmel ellátott szál, egy efemer válasz egy slash-parancsra —, és képességenként egyesével degradálódik. Egy szolgáltató, amelyből mindez hiányzik, akkor is működik; a válaszok egyszerűen egyetlen üzenetként érkeznek meg.
Egy képesség hirdetése nem ígéret annak meglétére. A Slack streamelő válaszaihoz egy fizetős csomag funkciója kell, és a Slack csak a hívás pillanatában utasít el. A véglegesként megjelölt elutasítást a kapcsolat teljes élettartamára megjegyezzük: a híd felhagy a kéréssel, és onnantól teljes válaszokat tesz közzé, a beállítások pedig ezt egyértelműen kiírják, mert a „leállt a válaszaim streamelése” kérdésre napló olvasása nélkül is válaszolni kell. A feladatkártyákat ugyanígy elhagyjuk, ha a Slack elutasít egyet.
Minden korlát a szolgáltatóhoz tartozik, nem a továbbítóhoz: az üzenetplafont, a streamdarabok plafonját és azt, hogy egy streamhez kell-e szálhorgony, mind az adapter deklarálja, és egy hosszú válasz bekezdés-, sor- vagy szóhatáron tördelődik, nem szó közben.
Egy híd, termékenként egy adapter
Szekció neve “Egy híd, termékenként egy adapter”Minden termék alakú dolog egy port mögött él. Az általános mag ismeri a
markdownt, a csevegőbeszélgetéseket, a csevegőszálakat és a tagokat; semmit nem
tud a Slack borítékjairól, a mrkdwn-ről vagy a gateway-intentekről. Három
darab működteti ezt:
- Az adapter normalizál egy terméket: annak szállítási módját, API-hívásait,
szövegkodekjét és borítékjainak sajátosságait. A Slackéi tanulságosak — egy
csatornamegjelölés kétszer érkezik meg (mint
app_mentionés mintmessage), és a legtöbb üzenet-altípus díszítés, nem új kérés. Mindkettőt az adapter oldja fel, így a mag soha nem tudja meg, hogy a Slack kétszer kézbesít. - A deskriptor a szolgáltató nyilvános alakja: milyen hitelesítő adatokra van szüksége, milyen formátumúak azok, mely URL-ek segítenek a felhasználónak megtalálni őket, mire képes, és hogy az alkalmazása létrehozható vagy szerkeszthető-e innen. A beállítási kártya, a csatlakozási párbeszédablak és az argumentumok érvényesítése mind ebből generálódik — a kliens egy szolgáltatót küld vissza, valamint egy mezőazonosítóval kulcsolt leképezést.
- A plugin a szerveroldali összeállítás: hogyan lesz a hitelesítő adatokból ellenőrzött kapcsolat, hogyan épül fel egy adapter, és hogyan kezeljük a szolgáltató oldali alkalmazást.
Így egy termék hozzáadása egyetlen enumérték, egy adapter és egy plugin. Nincs új RPC-művelet, nincs sémaváltozás, nincs kliensváltozás — és a kliens korrekt marad, mert egy általa soha nem hallott mező is a szerver által szolgáltatott címkével jelenik meg.
Két részlet akadályozza meg, hogy az absztrakció felfelé szivárogjon. Azokat az alkalmazáskezelési lépéseket, amelyekhez a szolgáltatónak nincs API-ja (token generálása, az alkalmazás telepítése, újratelepítés jogosultságváltozás után), saját hivatkozásokkal rendelkező, elnevezett lépésekként adjuk vissza, így a beállítás befejezése képernyő anélkül jeleníti meg őket, hogy tudná, melyik szolgáltatóról van szó. A híd által írt minden utasítás megnevezi a szolgáltatót és az alkalmazás által ténylegesen használt parancsot, így egy átnevezett parancs vagy egy második szolgáltató is helyesen olvasható új szöveg hozzáadása nélkül.
Hol élnek a hitelesítő adatok
Szekció neve “Hol élnek a hitelesítő adatok”A munkaterület csevegési tokenjei a munkaterület saját könyvtárában, az
adatbázisa mellett találhatók — egyszerű JSON-ként itt:
<dataDir>/<workspaceId>/chat_credentials/<provider>.json, nem az adatbázisban,
és szándékosan nem az operációs rendszer kulcstárában. Két következmény könnyen
elvész később: egy munkaterület törlése leválasztja a könyvtárat, és a tokenek
vele mennek; egy munkaterület exportálása vagy mentése pedig átmásolja a
workspace.db-t, így egy másnak átadott munkaterület nem hordoz magában élő
bottokeneket. A fájl a szervert futtató hoszt fájlrendszerének bizalmi határát
örökli — bármi, aminek olvasási hozzáférése van az adatkönyvtárhoz, olvashat egy
bottokent, és ugyanezen határ mögött vannak az adatbázisok is.
Szolgáltatónként egy fájl, atomikusan és csak a tulajdonos számára olvashatóan írva: egy visszavont alkalmazás egyetlen fájl leválasztásával törlődik, egy sérült fájl pedig egyetlen szolgáltatóba kerül, nem az összesbe. A hitelesítő adatok a csatlakozási műveleten keresztül bemennek, és soha nem jönnek ki — az állapotlekérdezések csak kapcsolati metaadatokat adnak vissza.
Amit szándékosan nem kínálunk
Szekció neve “Amit szándékosan nem kínálunk”- Szerkezeti alkalmazásbeállítások. A szállítást, a hatóköröket és az eseményfeliratkozásokat nem tesszük közzé, hanem kikényszerítjük. Egy alkalmazás, amelyet egy beállítási képernyőről el lehet hallgattatni, támogatási jegy, nem funkció.
- Beszélgetés és slash-parancsok. A Slack-felület üzenetekből, szálválaszokból és slash-parancsokból áll.
- Egy második igazságforrás. A csevegés nem hely, ahol a munkát tárolják. A Slack szál egy tér, a válasz egy átirat, a jegy egy jegy — mindegyik a munkaterületen, minden klienstől elérhetően.
Lásd még
Szekció neve “Lásd még”- Többszereplős mód és jelenlét: a Principalok és szerepkörök, amelyeken keresztül a híd felold
- Munkaterületek és elszigetelés: miért munkaterületenkéntiek a kapcsolatok és a hitelesítő adatok
- Az ügynökmodell: mi ébred fel, amikor megérkezik egy említés
- Jegyek és delegálás: mit hoz létre a
/cc ticket - Architektúra: hol helyezkedik el a híd a szerver és a kliensek között
- Slack összekötése a munkaterülettel
- Slack-integráció beállítása
- Slack-fiók összekapcsolása
- Csevegőbot testreszabása
- PR-beszélgetések: ugyanaz a három döntés, kódhosztra alkalmazva