Implementare și clienți
Control Center nu este un singur program. Este o arhitectură thin-client: un server headless care deține datele tale și un set de clienți care randează o interfață peste o singură conexiune RPC la acesta. Înțelegerea acestei separări explică de ce unele lucruri rulează acolo unde rulează și la ce poate, respectiv la ce nu poate ajunge fiecare client.
Serverul deține datele
Secțiune intitulată „Serverul deține datele”Un proces numit cc_server deține bazele de date — un global.db mic pentru
starea la nivel de server plus câte un fișier SQLite per
spațiu de lucru, deschis leneș — și
rulează munca de durată: motoarele de pipeline, procesele de reconciliere,
suprafața de instrumente MCP și listenerul de orchestrare. Tot ce trebuie să
supraviețuiască închiderii unei ferestre sau ce trebuie să arate identic pentru
doi clienți se află acolo.
Niciun client nu deschide singur un fișier de bază de date. În schimb, fiecare
client deține un RemoteRpcClient și emite prin el operațiuni pe repo-uri și
abonamente. Serverul autentifică conexiunea pe baza unei chei prepartajate de
dispozitiv asociat — iar apoi nu se mai preocupă de conexiune. O sesiune nu
este legată de un spațiu de lucru. Dispecerul este fără stare: fiecare apel
care vizează un spațiu de lucru își transmite propriul workspace_id în
argumente, iar un apel care îl omite este respins.
Acest lucru este intenționat și tocmai de aceea doi clienți de pe același server pot lucra în spații de lucru diferite fără un „spațiu de lucru curent” ținut de server, despre care să nu fie de acord. Izolarea este impusă în schimb la fiecare apel, în patru pași:
- Operațiunea declară dacă vizează un spațiu de lucru; dacă da și nu a sosit
niciun
workspace_id, apelul este respins. - Identificatorul numit trebuie să fie un spațiu de lucru înregistrat,
verificat înainte ca ceva să deschidă o bază de date — altfel un id necunoscut
ar materializa la fiecare cerere un
workspace.dbfantomă, gol. O ratare înseamnă „negăsit”. WorkspaceRoleal apelantului trebuie să atingă pragul minim al operațiunii, derivat din tipul acesteia: citirea cere guest, modificarea cere member, distrugerea cere admin.- O operațiune care expune conținut din repo necesită în plus o autorizare per
repo, la nivelul pe care îl declară —
none/read/review/write— astfel încât apartenența la spațiul de lucru să nu acorde niciodată în mod silențios mai multe privilegii decât hostul de cod.
Propoziția care contează: deținerea unei chei de asociere nu reprezintă granița de acces; apartenența o reprezintă.
De ce este împărțit astfel
Secțiune intitulată „De ce este împărțit astfel”Trei presiuni au împins către un server:
- O singură sursă de adevăr. Un desktop și un telefon, sau un laptop și un desktop, ar trebui să vadă aceeași stare a flotei. Dacă fiecare ar deține propria bază de date, ar diverge. Un singur server înseamnă că fiecare client citește aceleași date live.
- Webul nu se poate descurca singur. Un browser nu poate lansa un subproces și nu poate deschide un fișier local, așa că o versiune web nu poate deține niciodată baza de date și nu poate rula un agent. Trebuie să comunice cu un server care face asta. În loc să construiască o a doua cale de date, doar pentru web, desktopul a fost adus pe aceeași cale.
- Operare headless. Un server pe care îl poți rula pe o cutie mică — accesibil de pe un telefon din tren sau dintr-un browser de oriunde — este mai util decât o interfață grafică pe care trebuie să o lași deschisă.
Compromisul este că serverul devine acum o dependență: dacă nu rulează, nimic nu funcționează. Răspunsul implicit al desktopului la asta este să lanseze el însuși unul.
Clienții
Secțiune intitulată „Clienții”Cinci clienți ajung la server, fiecare cu o formă și un profil de încredere diferite.
Aplicația desktop
Secțiune intitulată „Aplicația desktop”Aplicația desktop Flutter (macOS, Windows, Linux) este clientul complet. La prima pornire întreabă cum ar trebui să ruleze, apoi reține alegerea:
- Local: lansează și supraveghează un
cc_serverpe această mașină — configurația implicită, autonomă, pentru un singur utilizator. Desktopul pornește serverul, care deține baza de date în rădăcina de suport a aplicației și comunică cu el prin loopback. - Remote: conectează-te la un
cc_servercare rulează în altă parte printr-un WebSocket securizat. Datele se află pe acel server; acest desktop este pur și simplu un renderer.
În ambele cazuri, clientul RPC rezultat înlocuiește furnizorul de date al aplicației, astfel încât întreaga interfață — fiecare ecran, fiecare funcție — citește și scrie prin server. Desktopul nu atinge niciodată direct fișierul bazei de date.
Clientul web
Secțiune intitulată „Clientul web”Versiunea web este aceeași aplicație Flutter compilată pentru a rula într-un
browser, găzduită la app.usectrl.dev, așa că nu ai
nimic de instalat. Un browser nu poate lansa un subproces, așa că clientul web
este întotdeauna remote: apelează un cc_server prin wss:// și randează
întreaga interfață desktop. Odată conectat, afișează aceleași ecrane pentru
aceeași flotă, pentru că este același cod peste același RPC. Lipsurile sunt cele
câteva locuri care au nevoie de o capabilitate nativă pe care un browser nu o are
— stratul de calitate al capturii întâlnirilor, descris mai jos, este principalul,
iar notificările sistemului de operare sunt altul.
De aceea „fără descărcare” și „fără server” sunt afirmații diferite. Clientul web
elimină instalarea; nu elimină serverul. Ceea ce apelează poate fi un cc_server
pe același laptop — ws://localhost:9030, care nu are nevoie de certificat,
deoarece browserele consideră loopback-ul demn de încredere — sau unul de pe altă
mașină, care trebuie să fie accesibil prin wss:// cu un certificat în care
browserul are încredere. cc_server refuză să lege o adresă non-loopback fără
TLS, așa că un server remote înseamnă fie propriul certificat, fie un proxy care
termină TLS, fie un tunel sau un VPN care furnizează unul.
Pachetul în sine este găzduit separat de serverul cu care comunică. Un cc_server
standalone servește punctul final RPC, suprafața MCP și proxy-urile sale, dar
nu și un pachet web — calea pentru fișiere statice există în server, însă
nicio rădăcină web nu este configurată pe binarul standalone, așa că nu are ce să
servească la /. Imaginea de container publicată cc-webapp este cea care
găzduiește pachetul, iar scripts/build_web.sh este cea care îl construiește. Un
server acceptă origini de browser doar din lista sa --allowed-origins, care
implicit este https://app.usectrl.dev, așa că auto-găzduirea pachetului înseamnă
adăugarea originii acestuia.
Conținutul media (avataruri, imagini din flux, capturi de ecran din PR-uri) trece prin punctul final de proxy al serverului în loc să lovească direct gazdele din amonte, astfel încât un browser nu preia niciodată o origine arbitrară. Clientul implementat aplică, pentru fiecare cerere, o politică de securitate a conținutului generată dintr-un cookie nesensibil, astfel încât este permisă doar originea serverului conectat.
Companionul pentru telefon (cc_remote)
Secțiune intitulată „Companionul pentru telefon (cc_remote)”Aplicația Remote este un client separat, mai ușor — o PWA Flutter web la remote.usectrl.dev. Intenționat, nu este aplicația completă. Se asociază printr-un releu WebSocket intermediat și sigilat cap la cap și vorbește o felie preponderent de citire a suprafeței de instrumente: citește tichete, mesaje și fluxul de știri; trimite un răspuns sau actualizează un tichet.
Un telefon este un principal cu privilegii mai reduse decât un agent local. O politică de instrumente cu refuz implicit, o cheie prepartajată care sigilează fiecare cadru, o capabilitate de sesiune care scoate operațiunile privilegiate (asocierea, de exemplu) din aria unui client companion, verificările de rol și de autorizare per repo la fiecare apel, descrise mai sus, și limitarea ratei împiedică o asociere aprobată (sau scursă) să devină control remote complet. Vezi Control remote și mobil pentru modelul complet de securitate.
Clienți MCP
Secțiune intitulată „Clienți MCP”Instrumentele externe — editoare, alți agenți, CLI-uri — care vorbesc Model Context Protocol se conectează la suprafața MCP a serverului și consumă registrul de instrumente tipizate. Aceasta este calea de integrare programatică; nu este o interfață grafică. Vezi Folosește serverul MCP și Referința instrumentelor MCP.
Executorii din flotă (cc_worker)
Secțiune intitulată „Executorii din flotă (cc_worker)”Un cc_worker este un binar headless, scris în pur Dart, care se asociază cu
un cc_server, își declară capabilitățile gazdă și trage joburi concesionate
pentru a le executa. Transmite înapoi evenimente de proces prin același RPC și
nu păstrează nicio stare durabilă: fără bază de date, fără autentificare,
fără aprobări, fără bugete — acestea nu părăsesc niciodată cc_server. Un worker
este un membru, nu un al doilea creier. Un singur server autoritar, N membre
simple; nu există consens, nu există trafic între workeri.
Execuția concesionată rămâne totuși un mecanism de concesionare. agentRun este
singurul tip care rulează o comandă reală (CC_JOB_COMMAND, sau un prompt ecou,
dacă nu a fost furnizat niciunul). pipelineStep, codeIndex, goldenRender,
benchmark și evalBatch sondează momentan cu git --version / rev-parse HEAD
astfel încât calea de streaming este exercitată fără a pretinde că acele joburi au
rulat.
Ce anunță este sistemul de operare, arhitectura CPU, numărul de nuclee, memoria
RAM, dacă un SDK Flutter este accesibil și ce backend-uri de sandbox are gazda
(niciunul pe Windows). Un worker dedicat anunță de asemenea întotdeauna alwaysOn
și acceptsParallel, iar pe acestea se bazează planificatorul pin → prefer →
spill când plasează joburi. Nu există nicio sondă pentru GPU sau ML.
Un desktop folosit în regim solo nu observă nimic din toate acestea, pentru că
trimiterea obișnuită nu trece deloc prin flotă. Există totuși un worker implicit
în proces, înregistrat pentru gazda serverului, dar este doar o cusătură de
înregistrare, fără niciun runner conectat la ea — un job plasat efectiv pe
workerul local eșuează în loc să se execute. Astăzi, doar un cc_worker real
execută joburi concesionate.
Ce rulează unde
Secțiune intitulată „Ce rulează unde”| Aspect | Server (cc_server) |
Executor din flotă (cc_worker) |
Client desktop | Client web / telefon |
|---|---|---|---|---|
| Baze de date (Drift/SQLite) | le deține | — | — | — |
| Autentificare, aprobări, bugete, identitate | le deține | — | — | — |
| Pipeline-uri, procese de reconciliere, listener de orchestrare | le rulează | — | — | — |
| Suprafața de instrumente MCP | o servește | — | — | — |
| Execuția agenților | o rulează (sau o concesionează) | execută joburi concesionate | — | — |
| Randarea interfeței | — | — | completă | completă (web) / preponderent citire (telefon) |
| Captura întâlnirilor (microfon + audio din întâlnire) | — | — | captură nativă | doar web, prin audio de partajare a ecranului |
| Calendar OAuth (Google) | stochează tokenuri | — | — | — |
Cele două niveluri nu izolează la fel. cc_server înfășoară rulările pe care le
execută el însuși în sandbox-ul de sistem de operare al gazdei, acolo unde există
un backend — Seatbelt pe macOS, bubblewrap pe Linux și WSL2 — și revine la
celelalte granițe pe Windows sau fără bwrap și socat. cc_worker nu
izolează deloc. Anunță o capabilitate sandbox pentru ca serverul să poată
plasa joburi în funcție de platformă, dar nimic din worker nu aplică vreuna: un
job concesionat este lansat direct, delimitat de mediul concesiunii și de
worktree-ul care i-a fost dat. Externalizarea muncii prin concesionare mută astfel
execuția în afara sandbox-ului, chiar și pe o gazdă care are unul. Vezi
Sandbox și securitate pentru ce
delimitează o rulare în fiecare caz.
Captura întâlnirilor are loc pe un client, pentru că are nevoie de un microfon și de audio-ul întâlnirii — un server într-un dulap nu poate înregistra apelul tău. Înregistrarea este apoi rezumată de pipeline-ul serverului, iar notele sunt stocate acolo unde fiecare client le poate citi.
Clientul web înregistrează și el, ceea ce forma tabelului poate ascunde: captează microfonul plus o pistă audio de partajare a ecranului în browser și trimite în flux PCM la 16 kHz către server. Cere acea pistă audio, așa că eșuează explicit acolo unde browserul nu o oferă. Ce este cu adevărat exclusiv pentru desktop este stratul de calitate din jurul capturii — anularea ecoului la nivel de semnal prin procesorul nativ, indicatorul de nivel al intrării și avertismentul de microfon mort, reluarea unui rezumat, anularea procesării și acțiunea de înregistrare și legare din calendar.
Asociere și încredere
Secțiune intitulată „Asociere și încredere”Fiecare client își dovedește identitatea cu o cheie prepartajată, provisionată o singură dată:
- Desktopul în modul local generează o cheie nouă la fiecare pornire și o transmite serverului lansat prin variabile de mediu, astfel încât nimic confidențial nu este persistat pe desktop.
- Clienții remote (desktop remote, web, telefon) folosesc o cheie emisă de comanda
paira serverului și stocată în keychain-ul sistemului de operare (sau, pe telefon, local).
Revocarea este live, nu întârziată. Serverul supraveghează tabelul dispozitivelor asociate și închide orice sesiune deschisă al cărei dispozitiv a părăsit setul activ, în câteva secunde — atât pe calea directă WebSocket, cât și pe calea releului. Nu trebuie să aștepți ca dispozitivul să se reconecteze pentru a-l deconecta.
Cheia nu călătorește niciodată printr-un URL pe care o gazdă statică îl poate
vedea. Când ajunge printr-un link — pe telefon sau într-un browser — ea
călătorește în fragmentul URL, partea de după #, pe care browserul nu o
trimite niciodată serverului care găzduiește pagina. Aceasta este invarianța care
merită reținută și se aplică în ambele cazuri. Clientul web are alte două puncte
de intrare: formularul de conectare, unde lipești direct un server și o cheie, și
un cod de invitație, pe care îl răscumpără pentru o acreditare emisă doar pentru
acel browser.
Concepte conexe
Secțiune intitulată „Concepte conexe”- Spații de lucru și izolare: de ce fiecare apel RPC își poartă propriul id de spațiu de lucru și cum separarea bazelor de date face izolarea structurală
- Arhitectură: cum este structurată baza de cod în jurul serverului și al clienților săi
- Control remote și mobil: modelul de asociere a telefonului și de securitate
Ghiduri conexe
Secțiune intitulată „Ghiduri conexe”- Rulează un server headless: pornește
cc_serverși asociază un client - Conectează-te la un server remote: îndreaptă clientul desktop sau web către un server aflat în altă parte
- Asociază un dispozitiv: adaugă un telefon, un al doilea browser sau un alt desktop
- Rulează un worker din flotă: adaugă un
cc_workercare trage joburi concesionate