Architektura
Přehled
Sekce “Přehled”Control Center staví na Clean Architecture členěné podle domén, se vzorem portů a adaptérů. Kód je uspořádaný podle obchodních domén (v repozitáři features) a přísná pravidla závislostí vynucuje automatický test architektury.
Tenký klient nad bezhlavým serverem
Sekce “Tenký klient nad bezhlavým serverem”Control Center je architektura tenkého klienta. Žádný klient databázi neotevírá. Bezhlavý proces cc_server vlastní data (malý global.db a jeden soubor SQLite na pracovní prostor, otevíraný líně) a podává je přes WebSocket RPC. Aplikace pro počítač, webové sestavení a telefonní společník jsou vykreslovače nad tím jedním RPC spojením.
Počítač si při prvním spuštění vybere, jak se k serveru dostane: místně (na tomto stroji spustí cc_server a mluví přes loopback, to je výchozí) nebo vzdáleně (připojí se k cc_server jinde přes zabezpečený WebSocket). Webový klient je vždy vzdálený. Resolver při startu (bootstrap/server_backend.dart) vybere cestu ještě předtím, než existuje Riverpod, a pak přepíše rpcClientProvider připojeným klientem. Každá doména proto čte a zapisuje přes server, ne do místní databáze.
Vrstva RPC je bezstavová: relace je ověřená, ale není vázaná na pracovní prostor. Každé volání vázané na pracovní prostor nese ve svých argumentech vlastní workspace_id a dispečer volání bez něj odmítne. Díky tomu můžou dva klienti na jednom serveru držet různé aktivní pracovní prostory a server nedrží „aktuální pracovní prostor“, o kterém by se neshodli. Izolace se vynucuje u každého volání, ne u spojení.
Je to jedno Dart workspace s vyřešeným lockfilem a jednadvaceti členy, patnáct balíčků a šest aplikací, a kořenem je aplikace pro počítač a web (control_center). Šest aplikací tvoří bezhlavá serverová binárka (cc_server), bezhlavý vykonavatel flotily (cc_worker), telefonní PWA (cc_remote), zprostředkovatel WebSocket relay (cc_signaling_server), galerie Widgetbooku (cc_gallery) a hostitel veřejného dema (cc_demo_server). Patnáct balíčků je cc_ui (design systém), cc_domain (sdílené jádro v čistém Dartu: entity, hodnotové objekty, porty, události a doménová vrstva každé domény), cc_harness (vestavěné jádro agenta: smyčka agenta v čistém Dartu, tedy zprávy, port poskytovatele, nástroje, kompakce, řízení, hooky a subagenti, bez dart:io a bez jiné závislosti cc_*), cc_harness_runtime (vestavěný adaptér běhu harnessu: streamující poskytovatelé jen pro VM, zprostředkování přihlašovacích údajů OAuth/PKCE, úložiště přihlašovacích údajů a obecná sada nástrojů; agenti běží bez nainstalovaného cizího CLI), cc_rpc (klientské transporty), cc_host (jádro serveru), cc_data (vzdálené repozitáře), cc_persistence (databáze na straně serveru), cc_server_core (kompozice aplikačního serveru a běh identity, přítomnosti, flotily a evalů), cc_infra (adaptéry jen pro VM), cc_mcp (nástroje MCP), cc_mcp_client (klient externích MCP serverů, který jejich nástroje přemosťuje do místního registru), cc_markdown (markdownový engine v repozitáři), cc_natives (listový balíček FFI) a system_audio_capture. Celou matici klientů a to, co kde běží, najdeš v Nasazení a klienti.
Pravidlo závislostí
Sekce “Pravidlo závislostí”Presentation → Application/Providers → Domain ← Infrastructure- Doménová vrstva: entity v čistém Dartu, hodnotové objekty, rozhraní repozitářů, porty a doménové služby. Žádné importy infrastruktury (žádné dio, drift ani síťové modely).
- Prezentační vrstva: obrazovky, widgety, notifiery. Žádný přímý přístup k Driftu, DAO ani datové vrstvě; všechno jde přes providery Riverpodu → repozitáře.
- Infrastrukturní vrstva: konkrétní implementace doménových portů a rozhraní repozitářů. Závisí na doméně, a jen tímto směrem.
Struktura domén
Sekce “Struktura domén”Úplný tvar domény je:
feature_name/├── data/ # Repository implementations, data sources, services, DTOs, mappers├── domain/ # Entities, repository interfaces, ports, use cases├── presentation/ # Screens, widgets, notifiers└── providers/ # Riverpod providersPod lib/features/ má všechny čtyři části opravdu jen málo domén a chybějící části jsou výmluvné: vznikly záměrně. Sdílené jádro pohltilo domain/ každé domény do cc_domain a tenký klient nedrží implementace repozitářů, takže většina domén pod lib/ je presentation/ a providers/ a nic dalšího. Adresář data/ nebo domain/, který v klientovi zůstane, označuje něco skutečně klientského.
Dvě pojmenované výjimky:
mcpmá jen providery. Nastavení a stavové rozhraní žije podsettings/a samotná plocha nástrojů je balíček (cc_mcp), ne složka domény.orchestrationaplan_studiomají jenpresentation/aproviders/.
Doména guardrails má doménovou vrstvu v cc_domain/features/guardrails/, ne v kořenové aplikaci, protože ji vynucuje server: pravidlo mapuje ActionClass (nebo shellový commandPrefix) na rozhodnutí allow, prompt nebo deny v daném rozsahu. Pravidla se ukládají po pracovních prostorech a každá hlídaná akce agenta se před během vyřeší přes řetěz politik. Vyhodnocení jde po space > agent > workspace > mode preset > built-in default a rozhodne první rozsah se shodným pravidlem. Nejpřísnější pravidlo rozsekne jen remízu uvnitř jednoho rozsahu a slučování několika tříd, které jedna akce deklaruje. Plocha pro operátora je Nastavení → Pracovní prostor → Oprávnění agentů, na /workspaces/<id>/settings/workspace/permissions. Model popisují Guardraily.
Sdílené jádro
Sekce “Sdílené jádro”packages/cc_domain/lib/core/domain/ drží entity a repozitáře sdílené napříč třemi a více doménami. Je to čistý Dart bez závislostí na infrastruktuře, takže stejné definice importuje Flutter klient i binárka serveru bez Flutteru:
- Základní entity:
Agent,AgentRunLog,Workspace,Repo,ReviewSpaceAssociation - Identita a multiplayer:
User,Principal(sealedUserPrincipal|AgentPrincipal),WorkspaceMember,WorkspaceRole - Entity paměti:
MemoryFact,MemoryPolicy,AgentWorkingMemory,MemoryAccessGrant - Sdílené hodnotové objekty:
AgentSkills,AgentRole,Mode,SandboxBackend,SandboxSpec,RunCost - Sdílené porty:
SandboxPort,CredentialBrokerPort,WorkspaceFilesystemPort,GitRepoInspectorPort,EmbeddingPort - Doménové služby:
MemoryAccessPolicy,ActivityLogger,MentionResolver,RunLivenessClassifier DomainEventBusa typy událostí (pracovní prostor/agent, PR/revize, zprávy, tickety, pipeline, orchestrace, paměť, kalendář/schůzky, identita a členství, pozorovatelnost)
Identita a multiplayer
Sekce “Identita a multiplayer”Control Center je pro víc uživatelů: lidé a agenti jsou rovnocenní aktéři sjednocení přes Principal. Člen pracovního prostoru je User vázaný na Workspace v roli WorkspaceRole (owner/admin/member/viewer/guest). Hranicí přístupu je členství. Párovací klíč ji netvoří a oprávnění na úrovni repozitáře drží členství v pracovním prostoru v mezích práv forge. První uživatel vytvořený na čerstvém serveru vlastní instalaci; další se připojují pozvánkou nebo přes OIDC. Založení pracovního prostoru ho zapíše jako owner.
Spolupráce v reálném čase stojí na autoritativním serveru a last-writer-wins (LWW) po polích. CRDT to není. Přítomnost (kdo je tu a kde, kurzory, psaní, živý stav agenta a běžící náklady) je samostatný efemérní pruh, který se nikdy neukládá. Trvalý stav jede po páteři z optimistické mutace, rebase na serveru a LWW. Lidé a agenti sdílejí jeden seznam. Když je operátor jen jeden člověk, pruh přítomnosti stojí a rozhraní seznamu se neukáže. Multiplayer sólového uživatele nic nestojí.
Porty a adaptéry
Sekce “Porty a adaptéry”Porty jsou abstraktní rozhraní v doménové vrstvě. Adaptéry jsou konkrétní implementace v infrastruktuře. Bezhlavý server vlastní data a musí se sestavit bez Flutteru, takže skoro každý adaptér je jen pro VM a žije v packages/cc_infra/lib/src/, rozdělený podle oblasti:
| Port | Umístění adaptéru |
|---|---|
SandboxPort |
cc_infra/lib/src/sandboxing/ (native_sandbox_adapter.dart) |
CredentialBrokerPort |
cc_infra/lib/src/sandboxing/ (env_credential_broker.dart, github_fine_grained_broker.dart) |
GitRepoInspectorPort |
cc_infra/lib/src/git/ (git_repo_inspector.dart) |
EmbeddingPort |
cc_infra/lib/src/embedding/ (embedding_service.dart) |
NotificationPort |
lib/core/notifications/ (na straně klienta: vykresluje události serveru lokálně) |
AgentBackend |
cc_infra/lib/src/dispatch/backends/ (acp_backend.dart, harness_backend.dart, cli_backends.dart) |
TicketProviderPort |
cc_infra/lib/src/tickets/linear/ (LinearTicketAdapter). Jira, ClickUp a GitHub Issues jsou adaptéry synchronizace ticketů v cc_infra/lib/src/tickets/sync/; místní tickety žijí v databázi pracovního prostoru a TicketProviderPort nemají. |
PipelineEnginePort |
cc_domain features/pipelines/domain/services/ (doménová služba v čistém Dartu, infrastrukturu nepotřebuje) |
Kořen kompozice aplikace (di/providers.dart) váže klientské porty na implementace přes providery Riverpodu; server skládá své adaptéry v cc_server_core.
Správa stavu
Sekce “Správa stavu”Riverpod pro veškerý stav klienta:
Notifier<T>aAsyncNotifier<T>pro měnitelný stavFutureProvider<T>pro asynchronní data- Seznamy a živé řádky přicházejí přes odběry RPC jako
AsyncValue<List<T>>. Server uvnitř používá Drift.watch(); Drift neotevírá žádný klient - Nástroje MCP dostávají závislosti jako typované parametry konstruktoru.
Refse jim nepředává
Databáze
Sekce “Databáze”Drift (SQLite), rozdělený do dvou databází, obě v cc_persistence:
GlobalDatabase(<dataDir>/global.db) drží krátký seznam tabulek, které jsou opravdu celoserverové: registr pracovních prostorů, uživatelé, předvolby a spárovaná zařízení, připojení SSO, nastavení serveru pro celou instalaci, spravované politiky akcí (zámek na úrovni instalace, který smí guardraily pracovního prostoru jen zpřísnit), kanál novinek pro každého uživatele, fronta flotily (pracovníci, úlohy, záznam umístění), indexworkspace_routespřed ověřením a metadata instalace. Start otevře jen tento soubor, takže cena startu zůstává stejná, ať mají pracovní prostory historie kolik chtějí. Tabulka přidaná sem je rozhodnutí o izolaci a západkový test tě nutí ho obhájit.WorkspaceDatabase(<dataDir>/<workspaceId>/workspace.db) je jeden adresář na pracovní prostor. Drží jeden soubor SQLite a všechno ostatní, co patří jen tomu prostoru, takže worktree rozhovoru a soubory agenta leží vedle databáze a mažou se s ní. Tady žije skoro všechno: agenti, prostory, tickety, paměť, pipelines, schůzky, graf kódu, revize, repozitáře. Soubory se otevírají líně při prvním sáhnutí přesWorkspaceDatabaseManager.of(workspaceId)a teprve tehdy zaplatí vlastníquick_check, instalaci FTS a triggerů avector_init.
Obě poloviny vyšly ze sloučeného základu v1. onCreate staví aktuální schéma a migrační kroky se připojují, jen když je nasazenou databázi potřeba dovést dál.
Pracovní prostor je jeden soubor, takže export je jeden příkaz VACUUM INTO (workspace.export). Tabulku po tabulce se nevypisuje. workspace.import takový soubor převezme a podle vloženého workspace_meta pozná vlastní soubor převzatý znovu od souboru, který přišel z jiné instalace.
Obě databáze vlastní cc_persistence a otevírá je jen cc_server. Žádný klient DB neotevírá a každý klient čte a zapisuje přes RPC. Tabulky jsou v packages/cc_persistence/lib/database/tables/, DAO v .../daos/. Doménové entity jsou čistý Dart, oddělené od tříd tabulek Driftu, a mapování je v datových vrstvách. FTS5 řeší fulltextové hledání a embeddingy sqlite_vector, obojí jen na serveru. Obě databáze jedou s PRAGMA foreign_keys=ON v žurnálovém režimu WAL.
Směrování
Sekce “Směrování”go_router a jedna skutečnost, která řídí zbytek: každá destinace v aplikaci má prefix pracovního prostoru ve tvaru /workspaces/:workspaceId/…. Id pracovního prostoru v URL je jediný zdroj pravdy pro aktivní pracovní prostor. activeWorkspaceIdProvider vychází z trasy, trasa z něj ne. Čtení dat vázaných na pracovní prostor mimo trasu s prefixem proto nemá odkud id vzít.
- Funkce pro stavbu tras v
router/routes.dart. Každá bere id pracovního prostoru jako první argument, ne řetězcové konstanty - Čtyři trasy před kontextem jsou bez prefixu:
/splash,/onboarding,/signed-outa/workspaces(výběr) ShellRouteobaluje shell aplikace; ty čtyři plochy se kreslí přes celou obrazovku mimo něj- Stráž autentizace: nedokončené nastavení jde na
/onboarding; dokončené nastavení, kterému zmizel přihlašovací údaj forge, jde na/signed-out. Dokončené znamená, že přihlášený uživatel má připojenou aspoň jednu forge a že existuje aspoň jeden pracovní prostor. GitHub to být nemusí a CLIghnení způsob autentizace - Brána onboardingu v
features/auth/providers/
Síť (na straně serveru)
Sekce “Síť (na straně serveru)”Veškeré vnější HTTP žije na serveru. Klienti se na GitHub, Linear ani Google neobracejí přímo. Volají RPC operace serveru a i vzdálená média se berou z koncového bodu serveru /proxy/media, ne z původního zdroje.
- Klienti dio v
packages/cc_infra/lib/src/:GitHubApiClient,GitHubPrClient,GitHubContentClient,GitHubGraphQLClient,GitLabApiClient,BitbucketApiClient,LinearGraphQlClientaGoogleCalendarApiClient - Auth token se vkládá přes interceptory, včetně Google OAuth interceptoru po účtech v
cc_server_core, který při 401 tokeny obnoví - Všechny chyby se mapují přes
packages/cc_infra/lib/src/network/error_mapper.dart→ typované podtřídyAppExceptiondefinované vcc_domain
Zabezpečení
Sekce “Zabezpečení”Server drží přihlašovací údaje, na kterých záleží: tokeny GitHubu a ticketovacích služeb pro spouštění agentů, klíče poskytovatelů LLM a tokeny OAuth žijí v datovém adresáři serveru nebo v jeho prostředí. V klíčence klienta zůstává jeho vlastní přihlašovací údaj zařízení a párovací klíč ke vzdálenému serveru.
- Tajemství ve
flutter_secure_storage(klíčenka macOS, Správce pověření Windows, libsecret na Linuxu): přihlašovací údaj zařízení klienta a párovací klíče ke vzdálenému serveru, ne tokeny poskytovatelů shared_preferencesjen pro necitlivé předvolby (motiv, písmo, rozložení)- Zbytky tajemství poskytovatelů ze starších instalací jednou předá serveru
features/auth/providers/credential_migration.darta potom je z klíčenky klienta smaže
Vynucení architektury
Sekce “Vynucení architektury”Architektonická omezení ověřuje test/core/architecture_constraints_test.dart. Test selže, když se poruší pravidla závislostí.
Související koncepty
Sekce “Související koncepty”- Pracovní prostory a izolace: jak se vynucuje izolace
- Životní cyklus odeslání agenta: tok odeslání
- Doménové události: komunikace napříč doménami
- Nasazení a klienti: model tenkého klienta se serverem a matice klientů