Deployment e client
Control Center non è un unico programma. È un’architettura thin client: un server headless che possiede i tuoi dati e un insieme di client che renderizzano un’interfaccia su un’unica connessione RPC verso di esso. Capire questa separazione spiega perché alcune cose girano dove girano e a cosa ogni client può e non può accedere.
Il server possiede i dati
Sezione intitolata “Il server possiede i dati”Un processo chiamato cc_server custodisce i database — un piccolo
global.db per lo stato globale del server più un file SQLite per ogni
spazio di lavoro, aperto solo quando
serve — ed esegue il lavoro di lunga durata: motori di pipeline, riconciliatori,
la superficie degli strumenti MCP e il listener di orchestrazione. Tutto ciò che
deve sopravvivere alla chiusura di una finestra, o che due client devono vedere
in modo identico, risiede lì.
Nessun client apre da sé un file di database. Ogni client, invece, tiene un solo
RemoteRpcClient e attraverso di esso esegue operazioni di repository e
sottoscrizioni. Il server autentica la connessione contro una chiave precondivisa
di dispositivo associato — e poi smette di preoccuparsi della connessione.
Una sessione non è vincolata a uno spazio di lavoro. Il dispatcher è privo di
stato: ogni chiamata con ambito di spazio di lavoro porta con sé il proprio
workspace_id nei suoi argomenti, e una chiamata che lo omette viene rifiutata.
È una scelta deliberata, ed è ciò che permette a due client su un unico server di stare in spazi di lavoro diversi senza che il server mantenga un «spazio di lavoro corrente» sul quale possano essere in disaccordo. L’isolamento viene invece applicato a ogni chiamata, in quattro passaggi:
- L’operazione dichiara se ha ambito di spazio di lavoro; se ce l’ha e non è
arrivato alcun
workspace_id, la chiamata viene rifiutata. - L’id indicato deve corrispondere a uno spazio di lavoro registrato,
verificato prima che qualsiasi cosa apra un database: altrimenti un id
sconosciuto materializzerebbe un
workspace.dbfantasma e vuoto a ogni richiesta. Una mancata corrispondenza restituisce un errore di tipo non trovato. - Il
WorkspaceRoledi chi chiama deve raggiungere la soglia dell’operazione, derivata dal suo tipo: leggere richiede ospite, modificare richiede membro, distruggere richiede admin. - Un’operazione che espone contenuto di repository richiede inoltre una
concessione per repository al livello che dichiara —
none/read/review/write— così l’appartenenza a uno spazio di lavoro non concede mai silenziosamente più privilegi della forge.
La frase che conta: possedere una chiave di abbinamento non è il confine di accesso; lo è l’appartenenza.
Perché separarlo così
Sezione intitolata “Perché separarlo così”Tre pressioni hanno spinto verso un server:
- Un’unica fonte di verità. Un desktop e un telefono, o un portatile e un desktop, devono vedere lo stesso stato della flotta. Se ciascuno possedesse il proprio database, divergerebbero. Un unico server significa che ogni client legge gli stessi dati in tempo reale.
- Il web non può servire sé stesso. Un browser non può avviare un sottoprocesso né aprire un file locale, quindi una build web non potrà mai possedere il database né eseguire un agente. Deve parlare con un server che può farlo. Invece di costruire un secondo percorso dati solo per il web, il desktop è stato portato sullo stesso percorso.
- Funzionamento headless. Un server che puoi eseguire su una macchina piccola — raggiungibile da un telefono in treno o da un browser ovunque — è più utile di un’interfaccia grafica che devi lasciare aperta.
Il compromesso è che ora il server è una dipendenza: se non è in esecuzione, nulla funziona. La risposta predefinita del desktop è avviarne uno da sé.
I client
Sezione intitolata “I client”Cinque client raggiungono il server, ciascuno con una forma e un profilo di fiducia diversi.
App desktop
Sezione intitolata “App desktop”L’applicazione desktop Flutter (macOS, Windows, Linux) è il client completo. Al primo avvio chiede come deve essere eseguita, poi ricorda la scelta:
- Locale: avvia e supervisiona un
cc_serversu questa macchina — la configurazione predefinita, autocontenuta e per un singolo utente. Il desktop avvia il server, che possiede il database sotto la radice di supporto dell’app e comunica con esso tramite loopback. - Remota: si connette a un
cc_serverin esecuzione altrove tramite un WebSocket sicuro. I dati risiedono su quel server; questo desktop è puramente un renderer.
In entrambi i casi il client RPC risultante sostituisce il provider di dati dell’app, così l’intera interfaccia — ogni schermata, ogni funzione — legge e scrive attraverso il server. Il desktop non tocca mai direttamente il file del database.
Client web
Sezione intitolata “Client web”La build web è la stessa app Flutter compilata per girare in un browser,
ospitata su app.usectrl.dev, quindi non c’è nulla da
installare. Un browser non può avviare un sottoprocesso, perciò il client web è
sempre remoto: contatta un cc_server tramite wss:// e renderizza l’intera
interfaccia desktop. Una volta connesso mostra le stesse schermate sulla stessa
flotta, perché è lo stesso codice sullo stesso RPC. Le lacune sono quel pugno di
punti che richiedono una capacità nativa che un browser non ha: il livello di
qualità della cattura delle riunioni descritto più avanti è il principale, e le
notifiche del sistema operativo sono un altro.
Ecco perché «nessun download» e «nessun server» sono affermazioni diverse. Il
client web elimina l’installazione; non elimina il server. Ciò che contatta può
essere un cc_server sullo stesso portatile — ws://localhost:9030, che non
richiede certificati perché i browser considerano affidabile il loopback —
oppure uno su un’altra macchina, che deve essere raggiungibile tramite wss://
con un certificato di cui il browser si fida. cc_server rifiuta di associare un
indirizzo non loopback senza TLS, quindi un server remoto implica il suo
certificato, oppure un proxy che termina il TLS, oppure un tunnel o una VPN che
ne fornisca uno.
Il bundle stesso è ospitato separatamente dal server con cui parla. Un
cc_server standalone serve l’endpoint RPC, la superficie MCP e i suoi proxy, ma
non un bundle web: il percorso dei file statici esiste nel server, però sul
binario standalone non è configurata alcuna root web, quindi non ha nulla da
servire su /. L’immagine container pubblicata cc-webapp è ciò che ospita il
bundle, e scripts/build_web.sh è ciò che lo compila. Un server accetta origini
dei browser solo nella sua lista --allowed-origins, che per impostazione
predefinita è https://app.usectrl.dev, quindi ospitare il bundle per conto
proprio significa aggiungere la sua origine.
I media (avatar, immagini del feed, screenshot delle PR) passano attraverso l’endpoint proxy del server invece di contattare direttamente gli host a monte, così un browser non scarica mai un’origine arbitraria. Il client distribuito applica a ogni richiesta una Content-Security Policy presa da un cookie non sensibile, in modo che sia consentita solo l’origine del server connesso.
App complementare per il telefono (cc_remote)
Sezione intitolata “App complementare per il telefono (cc_remote)”L’app Remote è un client separato e più leggero: una PWA web Flutter su remote.usectrl.dev. Di proposito non è l’app completa. Si associa tramite un relay WebSocket negoziato e sigillato end-to-end, e parla una porzione prevalentemente di lettura della superficie degli strumenti: leggere ticket, messaggi e notizie; inviare una risposta o aggiornare un ticket.
Un telefono è un principal con privilegi inferiori rispetto a un agente locale. Una policy degli strumenti che nega per impostazione predefinita, una chiave precondivisa che sigilla ogni frame, una capacità di sessione che esclude un client complementare dalle operazioni privilegiate (l’associazione, ad esempio), i controlli di ruolo e di concessione per repository a ogni chiamata descritti sopra e il rate limiting impediscono che un’associazione approvata (o trapelata) diventi un controllo remoto completo. Vedi Controllo remoto e mobile per il modello di sicurezza completo.
Client MCP
Sezione intitolata “Client MCP”Gli strumenti esterni — editor, altri agenti, CLI — che parlano il Model Context Protocol si connettono alla superficie MCP del server e consumano il registro tipizzato degli strumenti. È il percorso di integrazione programmatico; non è un’interfaccia grafica. Vedi Usa il server MCP e il Riferimento degli strumenti MCP.
Esecutori di flotta (cc_worker)
Sezione intitolata “Esecutori di flotta (cc_worker)”Un cc_worker è un binario headless in puro Dart che si associa a un
cc_server, dichiara le capacità del suo host e preleva lavori in lease da
eseguire. Trasmette gli eventi di processo attraverso lo stesso RPC e non
conserva alcuno stato durevole: niente database, niente autenticazione,
niente approvazioni, niente budget: nulla di tutto ciò lascia mai cc_server. Un
worker è un arto, non un secondo cervello. Un server autoritativo, N arti
stupidi; non c’è consenso né traffico tra worker.
L’esecuzione in lease resta comunque impalcatura da lease. agentRun è l’unico
tipo che esegue un comando reale (CC_JOB_COMMAND, oppure un prompt restituito
tale e quale se non ne è stato fornito nessuno). pipelineStep, codeIndex,
goldenRender, benchmark e evalBatch attualmente fanno una sonda con
git --version / rev-parse HEAD, così il percorso di streaming viene
esercitato senza fingere che quei lavori siano stati eseguiti.
Ciò che annuncia sono il sistema operativo, l’architettura della CPU, il numero
di core, la RAM, se un SDK Flutter è raggiungibile e quali backend sandbox ha
l’host (nessuno su Windows). Un worker dedicato annuncia inoltre sempre
alwaysOn e acceptsParallel, ed è su questo che si basa lo scheduler
pin → prefer → spill per il posizionamento. Non c’è alcuna sonda GPU o ML.
Un desktop isolato non nota nulla di tutto questo, perché l’invio ordinario non
passa affatto per la flotta. Un worker implicito in-process registrato per l’host
del server esiste davvero, ma è una cucitura di registrazione senza alcun
runner collegato: un lavoro effettivamente assegnato al worker locale fallisce
invece di essere eseguito. Oggi solo un vero cc_worker esegue lavori in lease.
Cosa gira dove
Sezione intitolata “Cosa gira dove”| Ambito | Server (cc_server) |
Esecutore di flotta (cc_worker) |
Client desktop | Client web / telefono |
|---|---|---|---|---|
| Database (Drift/SQLite) | li possiede | — | — | — |
| Autenticazione, approvazioni, budget, identità | la possiede | — | — | — |
| Pipeline, riconciliatori, listener di orchestrazione | li esegue | — | — | — |
| Superficie degli strumenti MCP | la serve | — | — | — |
| Esecuzione degli agenti | la esegue (o la cede in lease) | esegue i lavori in lease | — | — |
| Rendering dell’interfaccia | — | — | completo | completo (web) / prevalentemente in lettura (telefono) |
| Cattura delle riunioni (microfono + audio della riunione) | — | — | cattura nativa | solo web, tramite l’audio di condivisione schermo |
| OAuth del calendario (Google) | conserva i token | — | — | — |
I due livelli non applicano lo stesso sandbox. cc_server avvolge le esecuzioni
che esegue da sé nel sandbox del sistema operativo dell’host dove esiste un
backend — Seatbelt su macOS, bubblewrap su Linux e WSL2 — e ricorre agli altri
confini su Windows o in assenza di bwrap e socat. cc_worker non applica
alcun sandbox. Annuncia una capacità sandbox così il server può posizionare i
lavori in base alla piattaforma, ma nulla nel worker ne applica uno: un lavoro in
lease viene avviato direttamente, limitato dall’ambiente del lease e dal worktree
che gli è stato assegnato. Cedere lavoro all’esterno sposta quindi l’esecuzione
fuori dal sandbox anche su un host che ne ha uno. Vedi
Sandbox e sicurezza per ciò che
delimita un’esecuzione in ciascun caso.
La cattura delle riunioni avviene su un client perché richiede un microfono e l’audio della riunione: un server chiuso in un armadio non può registrare la tua chiamata. La registrazione viene poi riassunta dalla pipeline del server e le note vengono salvate dove ogni client può leggerle.
Anche il client web registra, cosa che la forma della tabella può nascondere: cattura il microfono più una traccia audio di condivisione schermo nel browser e trasmette PCM a 16 kHz al server. Richiede quella traccia audio, quindi fallisce esplicitamente dove il browser non ne fornisce una. Ciò che è davvero esclusivo del desktop è il livello di qualità attorno alla cattura: la cancellazione dell’eco a livello di segnale tramite il processore nativo, il misuratore di livello d’ingresso e l’avviso di microfono muto, rieseguire un riassunto, annullare l’elaborazione e l’azione di registra e collega del calendario.
Associazione e fiducia
Sezione intitolata “Associazione e fiducia”Ogni client prova la propria identità con una chiave precondivisa fornita una volta sola:
- Il desktop in modalità locale genera una chiave nuova a ogni avvio e la passa al server avviato tramite l’ambiente, così sul desktop non viene persistito nulla di segreto.
- I client remoti (desktop remoto, web, telefono) usano una chiave emessa dal
comando
pairdel server e conservata nel portachiavi del sistema operativo (o, sul telefono, localmente).
La revoca è immediata, non pigra. Il server tiene sotto controllo la sua tabella dei dispositivi associati e chiude qualsiasi sessione aperta il cui dispositivo abbia lasciato l’insieme attivo, entro pochi secondi: sia sul percorso WebSocket diretto sia su quello del relay. Non devi aspettare che il dispositivo si riconnetta per tagliarlo fuori.
La chiave non viaggia mai in un URL che un host statico possa vedere. Quando
arriva tramite link — sul telefono o in un browser — viaggia nel frammento
dell’URL, la parte dopo #, che il browser non invia mai al server che ospita
la pagina. Questa è l’invariante da ricordare, e vale per entrambi. Il client web
ha altri due punti di ingresso: il modulo di connessione, dove incolli
direttamente server e chiave, e un codice di invito, che riscatta con una
credenziale emessa solo per quel browser.
Concetti correlati
Sezione intitolata “Concetti correlati”- Spazi di lavoro e isolamento: perché ogni chiamata RPC porta il proprio id di spazio di lavoro e come la separazione dei database rende strutturale l’isolamento
- Architettura: come è strutturato il codice attorno al server e ai suoi client
- Controllo remoto e mobile: l’associazione del telefono e il modello di sicurezza
Guide correlate
Sezione intitolata “Guide correlate”- Esegui un server headless: avvia
cc_servere associa un client - Connettiti a un server remoto: punta il client desktop o web su un server altrove
- Associa un dispositivo: aggiungi un telefono, un secondo browser o un altro desktop
- Esegui un worker della flotta: aggiungi un
cc_workerche preleva lavori in lease