Tovább a tartalomhoz

Telepítés és kliensek

A Control Center nem egyetlen program. Vékonykliens-architektúra: egy headless szerver, amely az Ön adatait birtokolja, és kliensek egy csoportja, amelyek egyetlen, hozzá vezető RPC-kapcsolat fölött jelenítenek meg felületet. Ennek a kettéválasztásnak a megértése megmagyarázza, miért ott futnak a dolgok, ahol, és hogy az egyes kliensek mihez férnek hozzá és mihez nem.

A cc_server nevű folyamat tartja az adatbázisokat — egy kicsi global.db-t a szerver szintű állapothoz, valamint munkaterületenként egy-egy SQLite-fájlt, amelyeket csak akkor nyit meg, amikor kell —, és a hosszú életű munkát végzi: pipeline-motorok, összehangolók, az MCP-eszközfelület és az orchestrációs figyelő. Minden, aminek túl kell élnie egy ablakot, vagy amit két kliensnek azonosan kell látnia, ott él.

Egyetlen kliens sem nyitja meg maga az adatbázisfájlt. Helyette minden kliens egyetlen RemoteRpcClient-et tart fenn, és azon keresztül indít repóműveleteket és feliratkozásokat. A szerver a kapcsolatot egy párosított eszközhöz tartozó, előre megosztott kulccsal hitelesíti — aztán többé nem törődik a kapcsolattal. Egy munkamenet nincs munkaterülethez kötve. A dispatcher állapotmentes: minden munkaterület-hatókörű hívás a saját workspace_id-jét hordozza az argumentumaiban, és az olyan hívás, amelyből ez hiányzik, elutasításra kerül.

Ez szándékos, és ez teszi lehetővé, hogy egy szerveren két kliens különböző munkaterületen üljön anélkül, hogy lenne egy szerveren tárolt „aktuális munkaterület”, amin összeveszhetnének. Az elszigetelést ehelyett hívásonként érvényesítjük, négy lépésben:

  1. A művelet deklarálja, hogy munkaterület-hatókörű-e; ha igen, és nem érkezett workspace_id, a hívás elutasításra kerül.
  2. A megnevezett azonosítónak regisztrált munkaterületnek kell lennie, ezt még az előtt ellenőrizzük, hogy bármi adatbázist nyitna — különben egy ismeretlen azonosító minden kérésnél megtestesítene egy üres, kísértetjárta workspace.db-t. Egy hiányzó találat nem található hibát ad.
  3. A hívó WorkspaceRole szerepének el kell érnie a művelet küszöbét, amely a művelet fajtájából adódik: az olvasáshoz vendég, a módosításhoz tag, a romboló művelethez admin szükséges.
  4. Az olyan művelet, amely repótartalmat tesz elérhetővé, emellett az általa deklarált szinten repónkénti engedélyt is megkövetel — none / read / review / write —, így a munkaterületi tagság soha nem előzi meg csendben a kódhoszt jogosultságait.

A lényeges mondat: a párosítási kulcs birtoklása nem a hozzáférési határ; a tagság az.

Három nyomás tolta a megoldást a szerver felé:

  1. Egyetlen igazságforrás. Az asztali gép és a telefon, illetve a laptop és az asztali gép ugyanazt a flottaállapotot látja. Ha mindegyik a saját adatbázisát birtokolná, eltávolodnának egymástól. Egyetlen szerverrel minden kliens ugyanazt az élő adatot olvassa.
  2. A web nem tudja magát kiszolgálni. A böngésző nem indíthat alfolyamatot és nem nyithat meg helyi fájlt, ezért egy webes build soha nem birtokolhatja az adatbázist, és nem futtathat ügynököt. Olyan szerverrel kell beszélnie, amely ezt megteszi. Ahelyett, hogy készült volna egy második, csak webes adatút, az asztali alkalmazást is ugyanerre az útra állítottuk.
  3. Headless működés. Egy szerver, amelyet egy kicsi gépen futtathat — elérhető a vonaton ülő telefonról vagy bárhonnan egy böngészőből —, hasznosabb, mint egy grafikus felület, amelyet nyitva kell hagynia.

A kompromisszum az, hogy a szerver mostantól függőség: ha nem fut, semmi sem működik. Az asztali alkalmazás alapértelmezett válasza erre, hogy maga indít egyet.

Öt kliens éri el a szervert, mindegyik más formával és bizalmi profillal.

A Flutter asztali alkalmazás (macOS, Windows, Linux) a teljes kliens. Első indításkor megkérdezi, hogyan működjön, majd megjegyzi a választást:

  • Helyi: egy cc_server indítása és felügyelete ezen a gépen — ez az alapértelmezett, önmagában megálló, egyfelhasználós beállítás. Az asztali alkalmazás elindítja a szervert, amely az alkalmazástámogatási könyvtár alatt birtokolja az adatbázist, és loopbacken keresztül beszél vele.
  • Távoli: csatlakozás egy máshol futó cc_server-hez biztonságos WebSocketen keresztül. Az adatok azon a szerveren élnek; ez az asztali alkalmazás pusztán megjelenítő.

Mindkét esetben az így létrejövő RPC-kliens felülírja az alkalmazás adatszolgáltatóját, így a teljes felület — minden képernyő, minden funkció — a szerveren keresztül olvas és ír. Az asztali alkalmazás soha nem nyúl közvetlenül az adatbázisfájlhoz.

A webes build ugyanaz a Flutter-alkalmazás böngészőben futóra fordítva, az app.usectrl.dev címen üzemeltetve, így nincs mit telepíteni. A böngésző nem indíthat alfolyamatot, ezért a webes kliens mindig távoli: wss://-en tárcsáz egy cc_server-t, és a teljes asztali felületet rendereli. Csatlakozás után ugyanazokat a képernyőket mutatja ugyanazon flotta ellenében, mert ugyanaz a kód fut ugyanazon az RPC-n. A hiányosságok azok a néhány hely, ahol olyan natív képesség kell, amivel a böngésző nem rendelkezik — ezek közül a legfontosabb a lentebb tárgyalt megbeszélésrögzítési minőségi réteg, és ide tartoznak az operációs rendszer értesítései is.

Ezért nem ugyanaz az állítás, hogy „nincs letöltés”, és hogy „nincs szerver”. A webes kliens a telepítést szünteti meg; a szervert nem. Amit tárcsáz, lehet egy ugyanazon a laptopon futó cc_server — ws://localhost:9030, amelyhez nem kell tanúsítvány, mert a böngészők a loopbacket megbízhatónak tekintik —, vagy egy másik gépen futó, amelynek wss://-en keresztül elérhetőnek kell lennie, a böngésző által megbízhatónak tartott tanúsítvánnyal. A cc_server megtagadja, hogy TLS nélkül nem loopback címre kössön, ezért egy távoli szerver vagy a saját tanúsítványát jelenti, vagy egy TLS-t lezáró proxyt, vagy egy alagutat vagy VPN-t, amely biztosít egyet.

Maga a csomag külön van üzemeltetve attól a szervertől, amellyel beszél. Egy önálló cc_server kiszolgálja az RPC-végpontot, az MCP-felületet és annak proxyit, de nem szolgál ki webes csomagot — a statikusfájl-útvonal létezik a szerverben, de az önálló binárison nincs webes gyökér beállítva, így nincs mit kiszolgálnia a / alatt. A közzétett cc-webapp konténerkép az, amely a csomagot üzemelteti, és a scripts/build_web.sh az, ami felépíti. A szerver csak az --allowed-origins listáján szereplő böngésző-origineket fogadja el, amely alapértelmezésben https://app.usectrl.dev, így a csomag saját üzemeltetése azt jelenti, hogy hozzá kell adni annak originjét.

A média (profilképek, hírfolyamképek, PR-képernyőképek) a szerver proxyvégpontján keresztül halad, ahelyett hogy közvetlenül a felsőbb hosztokat érné el, így a böngésző soha nem tölt le tetszőleges origint. A telepített kliens kérésenkénti Content-Security Policy-t üt rá egy nem érzékeny cookie-ból, így csak a csatlakozott szerver originje engedélyezett.

A Remote alkalmazás külön, könnyebb kliens — egy Flutter webes PWA a remote.usectrl.dev címen. Szándékosan nem a teljes alkalmazás. Egy brókeren keresztüli, végponttól végpontig lezárt WebSocket-relén párosít, és az eszközfelületnek egy túlnyomórészt olvasásra szolgáló szeletét beszéli: jegyek, üzenetek és a hírfolyam olvasása; válasz küldése vagy jegy frissítése.

A telefon alacsonyabb jogosultságú szereplő, mint egy helyi ügynök. Az alapértelmezés szerint tiltó eszközszabályzat, a minden keretet lezáró előre megosztott kulcs, egy munkamenet-képesség, amely a kiemelt műveleteket (például a párosítást) kizárja a társügyfélből, a fenti hívásonkénti szerep- és repóengedély-ellenőrzések, valamint a sebességkorlátozás együtt megakadályozzák, hogy egy jóváhagyott (vagy kiszivárgott) párosítás teljes körű távoli vezérléssé váljon. A teljes biztonsági modellt lásd a Távoli vezérlés és mobil oldalon.

A külső eszközök — szerkesztők, más ügynökök, CLI-k —, amelyek a Model Context Protocolt beszélik, a szerver MCP-felületéhez csatlakoznak, és a típusos eszközregisztert használják. Ez a programozott integráció útja; nem grafikus felület. Lásd az Az MCP-szerver használata és az MCP-eszközök oldalt.

A cc_worker egy headless, tisztán Dart bináris, amely párosít egy cc_server-rel, deklarálja a gazdagép képességeit, és lízingelt feladatokat húz le végrehajtásra. Ugyanazon az RPC-n keresztül streameli vissza a folyamateseményeket, és nem tart fenn tartós állapotot: nincs adatbázis, nincs hitelesítés, nincsenek jóváhagyások, nincsenek költségkeretek — ezek soha nem hagyják el a cc_server-t. A worker egy végtag, nem második agy. Egy mérvadó szerver, N buta végtag; nincs konszenzus, nincs worker–worker közötti forgalom.

A lízingelt végrehajtás még mindig csak lízing-infrastruktúra. Az agentRun az egyetlen fajta, amely valódi parancsot futtat (CC_JOB_COMMAND, vagy egy visszhangzott prompt, ha nem adtak meg egyet). A pipelineStep, a codeIndex, a goldenRender, a benchmark és az evalBatch jelenleg a git --version / rev-parse HEAD parancsokkal próbálkozik, hogy a streamelési útvonal működjön, anélkül hogy úgy tennénk, mintha azok a feladatok lefutottak volna.

Amit hirdet: az operációs rendszer, a CPU-architektúra, a magok száma, a RAM, hogy elérhető-e egy Flutter SDK, és hogy a gazdagép mely homokozó-háttérrendszerekkel rendelkezik (Windowson egyikkel sem). Egy dedikált worker emellett mindig hirdeti az alwaysOn és acceptsParallel tulajdonságokat is, amelyekhez a pin → prefer → spill ütemező helyez. Nincs GPU- vagy ML-próba.

Egy önálló asztali telepítés ebből semmit sem vesz észre, mert a szokásos feladatkiosztás egyáltalán nem a flottán keresztül megy. Van ugyan egy implicit, folyamaton belüli worker regisztrálva a szerver gazdagépéhez, de ez egy regisztrációs illesztési pont, amelybe nincsenek futtatók bekötve — a helyi workerre ténylegesen ráhelyezett feladat végrehajtás helyett meghiúsul. Ma csak egy valódi cc_worker hajt végre lízingelt feladatokat.

Terület Szerver (cc_server) Flotta-végrehajtó (cc_worker) Asztali kliens Webes / telefonos kliens
Adatbázisok (Drift/SQLite) ezeket birtokolja — — —
Hitelesítés, jóváhagyások, költségkeretek, identitás ezt birtokolja — — —
Pipeline-ok, összehangolók, orchestrációs figyelő futtatja — — —
MCP-eszközfelület kiszolgálja — — —
Ügynökvégrehajtás végrehajtja (vagy lízingbe adja) lízingelt feladatokat hajt végre — —
Felület renderelése — — teljes teljes (web) / túlnyomórészt olvasás (telefon)
Megbeszélés rögzítése (mikrofon + megbeszélés hangja) — — natív rögzítés csak weben, képmegosztás hangján keresztül
Naptár OAuth (Google) tokeneket tárol — — —

A két szint homokozása nem egyforma. A cc_server az általa maga végrehajtott futásokat a gazdagép operációs rendszerének homokozójába csomagolja, ahol van ilyen háttérrendszer — Seatbelt macOS-en, bubblewrap Linuxon és WSL2-n —, és Windowson vagy bwrap és socat hiányában a többi korlátra esik vissza. A cc_worker egyáltalán nem homokozó. Hirdet egy sandbox képességet, hogy a szerver platform szerint helyezhesse el a feladatokat, de a workerben semmi nem alkalmaz ilyet: egy lízingelt feladat közvetlenül indul, csak a lízing környezete és a kapott worktree szab határt neki. A munka lízingbe adása ezért akkor is a homokozón kívülre viszi a végrehajtást, ha a gazdagépen van homokozó. Hogy mi szab határt egy futásnak az egyes esetekben, azt lásd a Homokozó és biztonság oldalon.

A megbeszélés rögzítése egy kliensen történik, mert mikrofonra és a megbeszélés hangjára van szükség — egy szekrényben álló szerver nem tudja rögzíteni az Ön hívását. A felvételt ezután a szerver pipeline-ja összegzi, a jegyzeteket pedig ott tárolja, ahol minden kliens el tudja olvasni.

A webes kliens is rögzít, amit a táblázat formája elfedhet: a böngészőben megfogja a mikrofont és egy képmegosztás hangpályát is, és 16 kHz-es PCM-et streamel a szervernek. Ez a hangpálya kötelező számára, ezért ott, ahol a böngésző nem ad ilyet, explicit módon meghiúsul. Ami valóban csak asztali gépen érhető el, az a rögzítés körüli minőségi réteg — a natív processzoron keresztüli, jelszintű visszhangszűrés, a bemeneti szintmérő és a néma mikrofon figyelmeztetése, az összefoglaló újrafuttatása, a feldolgozás megszakítása és a naptár rögzítés és hivatkozás művelete.

Minden kliens egy előre megosztott kulccsal igazolja magát, amelyet egyszer kell kiadni:

  • Az asztali alkalmazás helyi módban minden indításkor friss kulcsot generál, és környezeti változókon keresztül adja át az elindított szervernek, így az asztali gépen nem tárolódik titok.
  • A távoli kliensek (távoli asztali, web, telefon) a szerver pair parancsa által kibocsátott kulcsot használják, amelyet az operációs rendszer kulcstárában (a telefonon pedig helyben) tárolnak.

A visszavonás azonnali, nem halasztott. A szerver figyeli a párosított eszközök tábláját, és másodperceken belül eldobja minden olyan nyitott munkamenetet, amelynek eszköze kikerült az aktív halmazból — a közvetlen WebSocket-úton és a reléúton egyaránt. Nem kell megvárnia, hogy az eszköz újracsatlakozzon ahhoz, hogy elvágja.

A kulcs soha nem halad át olyan URL-en, amelyet egy statikus hoszt láthat. Amikor hivatkozással érkezik — a telefonon vagy egy böngészőben —, az URL-töredékben utazik, a # utáni részben, amelyet a böngésző soha nem küld el az oldalt üzemeltető szervernek. Ez az az invariancia, amit érdemes megjegyezni, és mindkettőre érvényes. A webes kliensnek két másik belépési pontja van: a csatlakozási űrlap, ahol közvetlenül beilleszti a szervert és a kulcsot, valamint egy meghívókód, amelyet egy kizárólag ahhoz a böngészőhöz kibocsátott hitelesítő adatra vált be.