Seni bina
Gambaran keseluruhan
Bahagian bertajuk “Gambaran keseluruhan”Control Center mengikut Clean Architecture berteraskan ciri dengan corak port dan penyesuai. Pangkalan kod disusun mengikut domain perniagaan (ciri), dengan peraturan kebergantungan yang ketat dikuatkuasakan oleh ujian seni bina automatik.
Klien nipis di atas pelayan tanpa kepala
Bahagian bertajuk “Klien nipis di atas pelayan tanpa kepala”Control Center ialah seni bina klien nipis. Tiada klien membuka pangkalan data — proses cc_server tanpa kepala memiliki data (satu global.db kecil serta satu fail SQLite bagi setiap ruang kerja, dibuka secara malas) dan menyajikannya melalui RPC WebSocket. Aplikasi desktop, binaan web dan aplikasi pendamping telefon semuanya merupakan pemapar di atas satu sambungan RPC itu.
Desktop memilih cara ia mencapai pelayannya pada pelancaran pertama: setempat (ia melancarkan cc_server pada mesin ini dan berkomunikasi melalui loopback — lalai) atau jauh (ia mendail cc_server di tempat lain melalui WebSocket yang selamat). Klien web sentiasa jauh. Penyelesai permulaan (bootstrap/server_backend.dart) memilih laluan sebelum Riverpod wujud, kemudian mengatasi rpcClientProvider dengan klien yang telah bersambung — jadi setiap ciri membaca dan menulis melalui pelayan dan bukan pangkalan data setempat.
Lapisan RPC itu sendiri tanpa keadaan: sesi disahkan, tetapi ia tidak terikat pada mana-mana ruang kerja. Setiap panggilan berskop ruang kerja membawa workspace_id sendiri dalam argumennya dan penghantar menolak panggilan sedemikian tanpanya. Itulah yang membolehkan dua klien pada satu pelayan memegang ruang kerja aktif yang berbeza tanpa “ruang kerja semasa” yang dipegang pelayan untuk diperselisihkan — dan ini bermakna pengasingan dikuatkuasakan bagi setiap panggilan, bukan setiap sambungan.
Ini ialah ruang kerja Dart dengan satu fail kunci yang telah diselesaikan, terdiri daripada dua puluh satu ahli — lima belas pakej serta enam aplikasi — dengan aplikasi desktop+web (control_center) sebagai akar ruang kerja. Enam aplikasi itu ialah binari pelayan tanpa kepala (cc_server), pelaksana armada tanpa kepala (cc_worker), PWA telefon (cc_remote), broker geganti WebSocket (cc_signaling_server), galeri Widgetbook (cc_gallery) dan hos demo awam (cc_demo_server). Lima belas pakej itu ialah cc_ui (sistem reka bentuk), cc_domain (kernel dikongsi Dart tulen: entiti, objek nilai, port, acara dan lapisan domain setiap ciri), cc_harness (kernel ejen terbina: gelung ejen Dart tulen — mesej, port pembekal, alat, pemadatan, pemanduan, cangkuk, subejen — tanpa dart:io dan tanpa kebergantungan cc_* yang lain), cc_harness_runtime (penyesuai masa jalan harness terbina: pembekal penstriman VM sahaja, perantaraan kelayakan OAuth/PKCE, stor kelayakan dan set alat generik — menjalankan ejen tanpa sebarang CLI luaran dipasang), cc_rpc (pengangkutan klien), cc_host (kernel pelayan), cc_data (repositori jauh), cc_persistence (DB sebelah pelayan), cc_server_core (komposisi pelayan aplikasi + masa jalan identiti/kehadiran/armada/evals), cc_infra (penyesuai VM sahaja), cc_mcp (alat MCP), cc_mcp_client (klien untuk pelayan MCP luaran, menjambatkan alat mereka ke dalam daftar setempat), cc_markdown (enjin markdown dalam repo), cc_natives (daun FFI) dan system_audio_capture. Lihat Penerapan dan klien untuk matriks klien penuh dan apa yang berjalan di mana.
Peraturan kebergantungan
Bahagian bertajuk “Peraturan kebergantungan”Presentation → Application/Providers → Domain ← Infrastructure- Lapisan domain: entiti Dart tulen, objek nilai, antara muka repositori, port dan perkhidmatan domain. Sifar import infrastruktur (tiada dio, drift atau model rangkaian).
- Lapisan persembahan: skrin, widget, pemberitahu. Tiada akses terus drift/DAO/lapisan data; semuanya melalui pembekal Riverpod → repositori.
- Lapisan infrastruktur: pelaksanaan konkrit bagi port domain dan antara muka repositori. Bergantung pada domain, tidak pernah sebaliknya.
Struktur ciri
Bahagian bertajuk “Struktur ciri”Bentuk penuh sesuatu ciri ialah:
feature_name/├── data/ # Repository implementations, data sources, services, DTOs, mappers├── domain/ # Entities, repository interfaces, ports, use cases├── presentation/ # Screens, widgets, notifiers└── providers/ # Riverpod providersSangat sedikit ciri bawah lib/features/ yang benar-benar membawa keempat-empatnya dan jurang itu bersifat bermaklumat dan bukan kebetulan. Kernel dikongsi telah menyerap domain/ setiap ciri ke dalam cc_domain dan klien nipis tidak memegang sebarang pelaksanaan repositori, jadi kebanyakan ciri bawah lib/ hanyalah presentation/ serta providers/ dan tiada yang lain. Direktori data/ atau domain/ yang masih kekal dalam klien menandakan sesuatu yang benar-benar milik klien.
Dua pengecualian yang dinamakan wajar diketahui:
mcphanya mempunyai pembekal. UI tetapan dan statusnya terletak bawahsettings/dan permukaan alat itu sendiri ialah pakej (cc_mcp), bukan folder ciri.orchestrationdanplan_studiohanya membawapresentation/danproviders/.
Ciri guardrails meletakkan lapisan domainnya dalam cc_domain/features/guardrails/ dan bukan dalam aplikasi akar, kerana pelayan yang menguatkuasakannya: sesuatu peraturan memetakan ActionClass (atau commandPrefix shell) kepada keputusan — allow, prompt atau deny — pada sesuatu skop, peraturan disimpan bagi setiap ruang kerja dan setiap tindakan ejen yang ditadbir diselesaikan melalui rantaian polisi sebelum ia dijalankan. Peleraian menelusuri space > agent > workspace > mode preset > built-in default dan skop pertama yang mempunyai peraturan sepadan yang membuat keputusan; yang paling ketat hanyalah penentu seri dalam satu skop dan semasa menggabungkan beberapa kelas yang diisytiharkan oleh satu tindakan. Permukaan yang berhadapan dengan pengendali ialah Tetapan → Ruang kerja → Kebenaran ejen, di /workspaces/<id>/settings/workspace/permissions. Lihat Guardrail untuk modelnya.
Kernel dikongsi
Bahagian bertajuk “Kernel dikongsi”packages/cc_domain/lib/core/domain/ memegang entiti dan repositori yang dikongsi merentas 3+ ciri. Ia Dart tulen dengan sifar kebergantungan infrastruktur, dan itulah yang membolehkan kedua-dua klien Flutter dan binari pelayan tanpa Flutter mengimport definisi yang sama:
- Entiti teras:
Agent,AgentRunLog,Workspace,Repo,ReviewSpaceAssociation - Identiti dan multipemain:
User,Principal(tersegelUserPrincipal|AgentPrincipal),WorkspaceMember,WorkspaceRole - Entiti memori:
MemoryFact,MemoryPolicy,AgentWorkingMemory,MemoryAccessGrant - Objek nilai dikongsi:
AgentSkills,AgentRole,Mode,SandboxBackend,SandboxSpec,RunCost - Port dikongsi:
SandboxPort,CredentialBrokerPort,WorkspaceFilesystemPort,GitRepoInspectorPort,EmbeddingPort - Perkhidmatan domain:
MemoryAccessPolicy,ActivityLogger,MentionResolver,RunLivenessClassifier DomainEventBus+ jenis acara (ruang kerja/ejen, PR/semakan, pemesejan, penetikan, pipeline, orkestrasi, memori, kalendar/mesyuarat, identiti dan keahlian, kebolehcerapan)
Identiti dan multipemain
Bahagian bertajuk “Identiti dan multipemain”Control Center ialah berbilang pengguna: manusia dan ejen merupakan pelaku sama taraf yang disatukan oleh Principal. Ahli ruang kerja ialah User yang terikat pada Workspace pada WorkspaceRole (owner/admin/member/viewer/guest). Keahlian ialah sempadan akses — bukan kunci penggandingan — dan geran setiap repo menghalang keahlian ruang kerja daripada memberi keistimewaan melebihi hos kod. Pengguna pertama yang dicipta pada pelayan baharu memiliki pemasangan itu; yang lain menyertai melalui jemputan atau OIDC. Mencipta ruang kerja menandakan pengguna itu sebagai owner.
Kerjasama masa nyata ialah pelayan berwibawa + penulis-terakhir-menang (LWW) bagi setiap medan, bukan CRDT. Kehadiran (siapa berada di sini, di mana mereka, kursor, penulisan, status langsung dan kos berjalan seseorang ejen) ialah lorong fana yang tidak pernah dikekalkan secara berasingan; keadaan kekal menaiki tulang belakang mutasi optimistik + rebase pelayan + LWW. Manusia dan ejen berkongsi satu senarai nama. Dengan seorang sahaja pengendali manusia, lorong kehadiran melahu dan tiada krom antara muka senarai nama muncul — multipemain tidak membebankan pengguna tunggal.
Port dan penyesuai
Bahagian bertajuk “Port dan penyesuai”Port ialah antara muka abstrak dalam lapisan domain. Penyesuai ialah pelaksanaan konkrit dalam infrastruktur. Kerana pelayan tanpa kepala memiliki data dan mesti dibina tanpa Flutter, hampir setiap penyesuai adalah VM sahaja dan terletak dalam packages/cc_infra/lib/src/, disusun mengikut bidang:
| Port | Lokasi penyesuai |
|---|---|
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/ (sebelah klien: memaparkan acara pelayan secara setempat) |
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 dan GitHub Issues ialah penyesuai penyegerakan tiket dalam cc_infra/lib/src/tickets/sync/; tiket setempat hidup dalam pangkalan data ruang kerja tanpa TicketProviderPort. |
PipelineEnginePort |
cc_domain features/pipelines/domain/services/ (perkhidmatan domain Dart tulen — ia tidak memerlukan infrastruktur) |
Akar komposisi aplikasi (di/providers.dart) mengikat port sebelah klien kepada pelaksanaan melalui pembekal Riverpod; pelayan menyusun penyesuainya dalam cc_server_core.
Pengurusan keadaan
Bahagian bertajuk “Pengurusan keadaan”Riverpod untuk semua keadaan klien:
Notifier<T>danAsyncNotifier<T>untuk keadaan boleh ubahFutureProvider<T>untuk data tak segerak- Senarai dan baris langsung tiba melalui langganan RPC sebagai
AsyncValue<List<T>>— pelayan menggunakan Drift.watch()secara dalaman; tiada klien membuka Drift - Alat MCP menerima kebergantungan sebagai parameter pembina bertip, tidak pernah
Ref
Pangkalan data
Bahagian bertajuk “Pangkalan data”Drift (SQLite), dipecahkan kepada dua pangkalan data, kedua-duanya dalam cc_persistence:
GlobalDatabase(<dataDir>/global.db) — senarai pendek jadual yang benar-benar merentas seluruh pelayan: daftar ruang kerja, pengguna/keutamaan/peranti berganding, sambungan SSO, tetapan pelayan seluruh pemasangan, polisi tindakan terurus (pengapit seluruh pemasangan yang hanya boleh mengetatkan guardrail ruang kerja), suapan berita setiap pengguna, baris gilir armada (pekerja/kerja/log penempatan), indeksworkspace_routespra-pengesahan dan metadata pemasangan. Permulaan hanya membuka fail ini, jadi kos permulaan kekal rata tidak kira berapa banyak sejarah yang dikumpulkan oleh ruang kerja. Menambah jadual di sini ialah keputusan pengasingan yang perlu anda pertahankan melalui ujian ratchet.WorkspaceDatabase(<dataDir>/<workspaceId>/workspace.db) — satu direktori bagi setiap ruang kerja, memegang satu fail SQLite serta segala yang lain yang hanya milik ruang kerja itu, jadi worktree sesuatu perbualan dan fail ejen duduk bersebelahan pangkalan data dan dipadam bersamanya. Hampir semuanya hidup di sini: ejen, ruang, tiket, memori, pipeline, mesyuarat, graf kod, semakan, repo. Fail dibuka secara malas pada sentuhan pertama melaluiWorkspaceDatabaseManager.of(workspaceId)dan membayarquick_check, pemasangan FTS/pencetus danvector_initsendiri pada masa itu.
Kedua-dua bahagian bermula daripada garis dasar v1 yang digabungkan — onCreate membina skema semasa — dengan langkah migrasi hanya ditambahkan apabila pangkalan data yang telah diterapkan perlu dibawa ke hadapan.
Kerana satu ruang kerja ialah satu fail, mengeksport ruang kerja ialah satu pernyataan VACUUM INTO (workspace.export), bukan lambakan jadual demi jadual; workspace.import menerima pakai fail sedemikian dan menggunakan workspace_meta terbenam untuk membezakan failnya sendiri yang diterima pakai semula daripada fail yang datang daripada pemasangan lain.
Kedua-dua pangkalan data dimiliki oleh cc_persistence dan dibuka hanya oleh cc_server; tiada klien membuka DB — setiap klien membaca/menulis melalui RPC. Jadual ditakrifkan dalam packages/cc_persistence/lib/database/tables/, DAO dalam .../daos/; entiti domain ialah Dart tulen, berasingan daripada kelas jadual Drift, dengan pemetaan dalam lapisan data. FTS5 mengendalikan carian teks penuh dan embed sqlite_vector, kedua-duanya hanya di pelayan; kedua-dua pangkalan data menjalankan PRAGMA foreign_keys=ON dalam mod jurnal WAL.
Penghalaan
Bahagian bertajuk “Penghalaan”go_router, dengan satu fakta yang mengawal selebihnya: setiap destinasi dalam aplikasi berawalan ruang kerja sebagai /workspaces/:workspaceId/…. Id ruang kerja dalam URL ialah satu-satunya sumber kebenaran bagi ruang kerja aktif — activeWorkspaceIdProvider dipandu daripada laluan, bukan sebaliknya — dan itulah sebabnya membaca data berskop ruang kerja di luar laluan berawalan tiada tempat untuk mendapatkan idnya.
- Fungsi pembina laluan dalam
router/routes.dart, setiap satu mengambil id ruang kerja sebagai argumen pertamanya, bukan pemalar rentetan - Empat laluan pra-konteks tidak berawalan:
/splash,/onboarding,/signed-outdan/workspaces(pemilih) ShellRoutemembalut cangkerang aplikasi; keempat-empat permukaan itu dipaparkan skrin penuh di luarnya- Pengawal pengesahan: persediaan yang tidak lengkap pergi ke
/onboarding; persediaan lengkap yang kelayakan hos kodnya telah hilang pergi ke/signed-out. Lengkap bermaksud sekurang-kurangnya satu hos kod disambungkan bagi pengguna yang telah daftar masuk dan sekurang-kurangnya satu ruang kerja wujud — bukan GitHub secara khusus, dan CLIghbukan kaedah pengesahan - Gerbang onboarding dalam
features/auth/providers/
Rangkaian (sebelah pelayan)
Bahagian bertajuk “Rangkaian (sebelah pelayan)”Semua HTTP luaran hidup dalam pelayan. Klien tidak pernah mendail GitHub, Linear atau Google secara langsung — mereka memanggil operasi RPC pelayan malah media jauh diambil melalui titik akhir /proxy/media pelayan dan bukan daripada asal huluan.
- Klien dio dalam
packages/cc_infra/lib/src/:GitHubApiClient,GitHubPrClient,GitHubContentClient,GitHubGraphQLClient,GitLabApiClient,BitbucketApiClient,LinearGraphQlClientdanGoogleCalendarApiClient - Suntikan token pengesahan melalui pencegat, termasuk pencegat Google OAuth bagi setiap akaun dalam
cc_server_coreyang menyegarkan token pada 401 - Semua ralat dipetakan melalui
packages/cc_infra/lib/src/network/error_mapper.dart→ subkelasAppExceptionbertip yang ditakrifkan dalamcc_domain
Keselamatan
Bahagian bertajuk “Keselamatan”Pelayan memegang kelayakan yang penting: token GitHub dan penetikan untuk pelancaran ejen, kunci pembekal LLM dan token OAuth semuanya hidup di bawah dir data pelayan atau persekitarannya. Apa yang masih dipegang oleh rantai kunci klien ialah kelayakan perantinya sendiri dan kunci penggandingan untuk pelayan jauh.
- Rahsia dalam
flutter_secure_storage(rantai kunci macOS, pengurus kelayakan Windows, libsecret Linux) — kelayakan peranti klien dan kunci penggandingan jauh, bukan token pembekal shared_preferencesuntuk keutamaan tidak sensitif sahaja (tema, fon, susun atur)- Rahsia pembekal yang tertinggal daripada pemasangan lama diserahkan kepada pelayan sekali oleh
features/auth/providers/credential_migration.dart, kemudian dipadam daripada rantai kunci klien
Penguatkuasaan seni bina
Bahagian bertajuk “Penguatkuasaan seni bina”Kekangan seni bina disahkan oleh test/core/architecture_constraints_test.dart, yang gagal jika peraturan kebergantungan dilanggar.
Konsep berkaitan
Bahagian bertajuk “Konsep berkaitan”- Ruang kerja dan pengasingan: bagaimana pengasingan dikuatkuasakan
- Kitaran hayat penghantaran ejen: aliran penghantaran
- Acara domain: komunikasi merentas ciri
- Penerapan dan klien: model pelayan klien nipis dan matriks klien