İçeriğe geç

Mimari

Control Center, portlar ve adaptörler deseniyle özellik öncelikli Clean Architecture’ı izler. Kod tabanı iş alanları (özellikler) etrafında düzenlenmiştir ve katı bağımlılık kuralları otomatik bir mimari testiyle uygulanır.

Control Center bir ince istemci mimarisidir. Hiçbir istemci veritabanını açmaz — verinin sahibi headless bir cc_server sürecidir (küçük bir global.db artı her çalışma alanı için tembelce açılan bir SQLite dosyası) ve veriyi WebSocket RPC üzerinden sunar. Masaüstü uygulaması, web derlemesi ve telefon yoldaşı, hepsi o tek RPC bağlantısı üzerinden çizen istemcilerdir.

Masaüstü, ilk açılışta sunucusuna nasıl ulaşacağını seçer: yerel (bu makinede bir cc_server başlatır ve loopback üzerinden konuşur — varsayılan budur) ya da uzak (başka bir yerdeki cc_server’a güvenli bir WebSocket üzerinden bağlanır). Web istemcisi her zaman uzaktır. Önyükleme çözümleyicisi (bootstrap/server_backend.dart), Riverpod var olmadan önce yolu seçer, ardından rpcClientProvider’ı bağlı istemciyle geçersiz kılar — böylece her özellik yerel bir veritabanı yerine sunucu üzerinden okur ve yazar.

RPC katmanının kendisi durumsuzdur: bir oturumun kimliği doğrulanır, ama oturum bir çalışma alanına bağlı değildir. Çalışma alanı kapsamlı her çağrı, kendi workspace_id değerini argümanlarında taşır ve gönderici böyle bir çağrıyı bu değer olmadan reddeder. İki istemcinin tek bir sunucuda farklı etkin çalışma alanları tutabilmesini sağlayan şey budur; sunucunun elinde anlaşmazlığa düşülecek bir “geçerli çalışma alanı” yoktur — ve bu, yalıtımın bağlantı başına değil, çağrı başına uygulandığı anlamına gelir.

Bu, yirmi bir üyeli — on beş paket artı altı uygulama — tek bir çözümlenmiş lockfile’a sahip bir Dart workspace’tir; çalışma alanı kökü masaüstü+web uygulamasıdır (control_center). Altı uygulama şunlardır: headless sunucu ikilisi (cc_server), headless filo yürütücüsü (cc_worker), telefon PWA’sı (cc_remote), WebSocket relay broker’ı (cc_signaling_server), Widgetbook galerisi (cc_gallery) ve genel demo barındırıcısı (cc_demo_server). On beş paket şunlardır: cc_ui (tasarım sistemi), cc_domain (saf Dart paylaşılan çekirdek: varlıklar, değer nesneleri, portlar, olaylar ve her özelliğin alan katmanı), cc_harness (yerleşik ajan çekirdeği: saf Dart bir ajan döngüsü — mesajlar, sağlayıcı portu, araçlar, sıkıştırma, yönlendirme, kancalar, alt ajanlar — dart:io olmadan ve başka bir cc_* bağımlılığı olmadan), cc_harness_runtime (yerleşik harness çalışma zamanı adaptörü: yalnızca VM akış sağlayıcıları, OAuth/PKCE kimlik bilgisi aracılığı, kimlik bilgisi depoları ve genel araç kümesi — harici CLI kurulu olmadan ajanları çalıştırır), cc_rpc (istemci taşıma katmanları), cc_host (sunucu çekirdeği), cc_data (uzak depolar), cc_persistence (sunucu tarafı DB), cc_server_core (uygulama-sunucu kompozisyonu + kimlik/mevcudiyet/filo/eval çalışma zamanı), cc_infra (yalnızca VM adaptörleri), cc_mcp (MCP araçları), cc_mcp_client (harici MCP sunucuları için istemci; araçlarını yerel kayıt defterine köprüler), cc_markdown (depo içi markdown motoru), cc_natives (FFI yaprağı) ve system_audio_capture. Tam istemci matrisi ve neyin nerede çalıştığı için bkz. Dağıtım ve istemciler.

Presentation → Application/Providers → Domain ← Infrastructure
  • Alan katmanı: saf Dart varlıklar, değer nesneleri, depo arayüzleri, portlar ve alan hizmetleri. Hiç altyapı import’u yok (dio, drift ya da ağ modeli yok).
  • Sunum katmanı: ekranlar, widget’lar, notifier’lar. Doğrudan drift/DAO/veri katmanı erişimi yok; her şey Riverpod provider’ları → depolar üzerinden gider.
  • Altyapı katmanı: alan portlarının ve depo arayüzlerinin somut uygulamaları. Alana bağımlıdır, tersi asla.

Tam özellik yapısı şudur:

feature_name/
├── data/ # Repository implementations, data sources, services, DTOs, mappers
├── domain/ # Entities, repository interfaces, ports, use cases
├── presentation/ # Screens, widgets, notifiers
└── providers/ # Riverpod providers

lib/features/ altında dördünü birden gerçekten taşıyan çok az özellik var ve bu boşluklar tesadüfi değil, bilgilendiricidir. Paylaşılan çekirdek her özelliğin domain/ dizinini cc_domain içine aldı ve ince istemci hiçbir depo uygulaması barındırmıyor; bu yüzden lib/ altındaki çoğu özellik presentation/ artı providers/ ve başka bir şey değil. İstemcide ayakta kalan bir data/ veya domain/ dizini, gerçekten istemci tarafı olan bir şeyi işaret eder.

Bilinmeye değer iki adlandırılmış istisna var:

  • mcp yalnızca provider’lardan oluşur. Ayarları ve durum arayüzü settings/ altında yaşar ve araç yüzeyinin kendisi bir özellik klasörü değil, bir pakettir (cc_mcp).
  • orchestration ve plan_studio yalnızca presentation/ ve providers/ taşır.

guardrails özelliği alan katmanını kök uygulama yerine cc_domain/features/guardrails/ içine koyar, çünkü bunu sunucu uygular: bir kural, bir kapsamda bir ActionClass’ı (ya da bir kabuk commandPrefix’ini) bir karara — allow, prompt ya da deny — eşler; kurallar çalışma alanı başına saklanır ve yönetilen her ajan eylemi çalışmadan önce politika zincirinden çözümlenir. Çözümleme space > agent > workspace > mode preset > built-in default sırasını izler ve eşleşen kurala sahip ilk kapsam karar verir; en kısıtlayıcı olma yalnızca tek bir kapsam içinde ve bir eylemin bildirdiği birkaç sınıfı birleştirmede beraberliği bozar. Operatöre bakan yüzey, /workspaces/<id>/settings/workspace/permissions adresindeki Ayarlar → Çalışma alanı → Ajan izinleri’dir. Model için bkz. Korumalar.

packages/cc_domain/lib/core/domain/, 3+ özellik arasında paylaşılan varlıkları ve depoları barındırır. Sıfır altyapı bağımlılığıyla saf Dart’tır; hem Flutter istemcisinin hem de Flutter’sız sunucu ikilisinin aynı tanımları import etmesini sağlayan da budur:

  • Çekirdek varlıklar: Agent, AgentRunLog, Workspace, Repo, ReviewSpaceAssociation
  • Kimlik ve çok oyunculu: User, Principal (sealed UserPrincipal | AgentPrincipal), WorkspaceMember, WorkspaceRole
  • Bellek varlıkları: MemoryFact, MemoryPolicy, AgentWorkingMemory, MemoryAccessGrant
  • Paylaşılan değer nesneleri: AgentSkills, AgentRole, Mode, SandboxBackend, SandboxSpec, RunCost
  • Paylaşılan portlar: SandboxPort, CredentialBrokerPort, WorkspaceFilesystemPort, GitRepoInspectorPort, EmbeddingPort
  • Alan hizmetleri: MemoryAccessPolicy, ActivityLogger, MentionResolver, RunLivenessClassifier
  • DomainEventBus + olay türleri (çalışma alanı/ajan, PR/inceleme, mesajlaşma, biletleme, pipeline, orkestrasyon, bellek, takvim/toplantılar, kimlik ve üyelik, gözlemlenebilirlik)

Control Center çok kullanıcılıdır: insanlar ve ajanlar, bir Principal ile birleştirilen eşit aktörlerdir. Bir çalışma alanı üyesi, bir Workspace’e bir WorkspaceRole ile bağlanan bir User’dır (owner/admin/member/viewer/guest). Erişim sınırı üyeliktir — bir eşleştirme anahtarı değil — ve depo başına izinler, çalışma alanı üyeliğinin forge’dan daha fazla yetki vermesini engeller. Yeni bir sunucuda oluşturulan ilk kullanıcı kurulumun sahibidir; diğerleri davetle ya da OIDC ile katılır. Bir çalışma alanı oluşturmak, o kullanıcıyı owner olarak damgalar.

Gerçek zamanlı işbirliği bir CRDT değil, yetkili sunucu + alan başına son yazan kazanır (LWW) yaklaşımıdır. Mevcudiyet (kim burada, nerede, imleçler, yazma, bir ajanın canlı durumu ve çalışma maliyeti) asla kalıcılaştırılmayan ayrı bir geçici hattır; kalıcı durum ise iyimser değişiklik + sunucu yeniden temellendirmesi + LWW omurgası üzerinde ilerler. İnsanlar ve ajanlar tek bir kadroyu paylaşır. Tek bir insan operatör varken mevcudiyet hattı boş durur ve hiçbir kadro arayüzü görünmez — çok oyunculu olmak yalnız kullanıcıya hiçbir maliyet çıkarmaz.

Portlar, alan katmanındaki soyut arayüzlerdir. Adaptörler, altyapıdaki somut uygulamalardır. Headless sunucu verinin sahibi olduğu ve Flutter’sız derlenmesi gerektiği için neredeyse her adaptör yalnızca VM’e özeldir ve packages/cc_infra/lib/src/ altında, ilgi alanına göre düzenlenmiş olarak yaşar:

Port Adaptör konumu
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/ (istemci tarafı: sunucu olaylarını yerel olarak işler)
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 ve GitHub Issues, cc_infra/lib/src/tickets/sync/ içindeki bilet eşitleme adaptörleridir; yerel biletler TicketProviderPort olmadan çalışma alanı veritabanında yaşar.
PipelineEnginePort cc_domain features/pipelines/domain/services/ (saf Dart bir alan hizmeti — hiçbir altyapıya ihtiyaç duymaz)

Uygulamanın kompozisyon kökü (di/providers.dart), istemci tarafı portları Riverpod provider’ları aracılığıyla uygulamalara bağlar; sunucu ise adaptörlerini cc_server_core içinde bir araya getirir.

Tüm istemci durumu için Riverpod:

  • Değiştirilebilir durum için Notifier<T> ve AsyncNotifier<T>
  • Asenkron veri için FutureProvider<T>
  • Listeler ve canlı satırlar, RPC abonelikleri üzerinden AsyncValue<List<T>> olarak gelir — sunucu içeride Drift .watch() kullanır; hiçbir istemci Drift açmaz
  • MCP araçları bağımlılıklarını tipli kurucu parametreleri olarak alır, asla Ref olarak değil

İki veritabanına bölünmüş Drift (SQLite); ikisi de cc_persistence içinde:

  • GlobalDatabase (<dataDir>/global.db) — gerçekten sunucu geneli olan az sayıda tablo: çalışma alanı kaydı, kullanıcılar/tercihler/eşleştirilmiş aygıtlar, SSO bağlantıları, kurulum geneli sunucu ayarları, yönetilen eylem politikaları (çalışma alanı korumalarını yalnızca sıkılaştırabilen kurulum geneli bir sınırlama), kullanıcı başına haber akışı, filo kuyruğu (worker’lar/işler/yerleştirme günlüğü), kimlik doğrulama öncesi workspace_routes dizini ve kurulum meta verileri. Önyükleme yalnızca bu dosyayı açar, böylece çalışma alanları ne kadar geçmiş biriktirirse biriktirsin başlangıç maliyeti sabit kalır. Buraya bir tablo eklemek, bir ratchet testinin gerekçelendirmenizi zorunlu kıldığı bir yalıtım kararıdır.
  • WorkspaceDatabase (<dataDir>/<workspaceId>/workspace.db) — her çalışma alanı için bir dizin; içinde bir SQLite dosyası artı yalnızca o çalışma alanına ait olan her şeyi barındırır, böylece bir konuşmanın worktree’leri ve ajan dosyaları veritabanının yanında durur ve onunla birlikte silinir. Neredeyse her şey burada yaşar: ajanlar, alanlar, biletler, bellek, pipeline’lar, toplantılar, kod grafiği, incelemeler, depolar. Dosyalar ilk dokunuşta WorkspaceDatabaseManager.of(workspaceId) üzerinden tembelce açılır ve o anda kendi quick_check, FTS/tetikleyici kurulumu ve vector_init işlemlerinin bedelini öder.

Her iki yarı da sıkıştırılmış bir v1 temelinden başladı — onCreate geçerli şemayı oluşturur — ve göç adımları yalnızca dağıtılmış bir veritabanının ileriye taşınması gerektiğinde eklenir.

Bir çalışma alanı tek bir dosya olduğundan, bir çalışma alanını dışa aktarmak tablo tablo bir döküm değil, tek bir VACUUM INTO deyimidir (workspace.export); workspace.import böyle bir dosyayı sahiplenir ve gömülü workspace_metayı kullanarak kendi dosyasının yeniden mi sahiplenildiğini yoksa başka bir kurulumdan mı geldiğini ayırt eder.

Her iki veritabanının sahibi de cc_persistencetır ve yalnızca cc_server tarafından açılır; hiçbir istemci DB açmaz — her istemci RPC üzerinden okur/yazar. Tablolar packages/cc_persistence/lib/database/tables/ içinde, DAO’lar .../daos/ içinde tanımlanır; alan varlıkları saf Dart’tır, Drift tablo sınıflarından ayrıdır ve eşleme veri katmanlarında yapılır. FTS5 tam metin aramasını, sqlite_vector ise gömme vektörlerini işler; ikisi de yalnızca sunucuda çalışır; her iki veritabanı da WAL günlük kipinde PRAGMA foreign_keys=ON çalıştırır.

go_router; geri kalan her şeyi belirleyen tek bir olguyla: uygulama içindeki her hedef, /workspaces/:workspaceId/… biçiminde çalışma alanı öneki taşır. URL’deki çalışma alanı kimliği, etkin çalışma alanı için tek doğruluk kaynağıdır — activeWorkspaceIdProvider rotadan sürülür, tersi değil — bu yüzden önekli bir rota dışında çalışma alanı kapsamlı veriyi okumak, kimliğini alacak bir yer bulamaz.

  • router/routes.dart içindeki, her biri çalışma alanı kimliğini ilk argümanı olarak alan rota kurucu fonksiyonları, dizge sabitleri değil
  • Bağlam öncesi dört rota öneksizdir: /splash, /onboarding, /signed-out ve /workspaces (seçici)
  • ShellRoute uygulama kabuğunu sarar; bu dört yüzey onun dışında tam ekran işlenir
  • Kimlik doğrulama koruması: tamamlanmamış kurulum /onboarding adresine, forge kimlik bilgisi kaybolmuş tamamlanmış bir kurulum ise /signed-out adresine gider. Tamamlanmış olmak, oturum açmış kullanıcı için en az bir forge’un bağlı olması ve en az bir çalışma alanının var olması demektir — özellikle GitHub değil; ayrıca gh CLI bir kimlik doğrulama yöntemi değildir
  • features/auth/providers/ içindeki onboarding kapısı

Tüm harici HTTP sunucuda yaşar. İstemciler GitHub’a, Linear’a ya da Google’a asla doğrudan bağlanmaz — sunucu RPC işlemlerini çağırırlar ve uzak medya bile asıl kaynaktan değil, sunucunun /proxy/media uç noktası üzerinden getirilir.

  • packages/cc_infra/lib/src/ içindeki dio istemcileri: GitHubApiClient, GitHubPrClient, GitHubContentClient, GitHubGraphQLClient, GitLabApiClient, BitbucketApiClient, LinearGraphQlClient ve GoogleCalendarApiClient
  • Interceptor’lar aracılığıyla kimlik doğrulama token’ı enjeksiyonu; buna 401’de token’ları yenileyen, cc_server_core içindeki hesap başına bir Google OAuth interceptor’ı da dahildir
  • Tüm hatalar packages/cc_infra/lib/src/network/error_mapper.dart üzerinden eşlenir → cc_domain içinde tanımlı tipli AppException alt sınıflarına

Önemli kimlik bilgileri sunucuda durur: ajan başlatmaları için GitHub ve biletleme token’ları, LLM sağlayıcı anahtarları ve OAuth token’ları, hepsi sunucunun veri dizini altında ya da ortamında yaşar. İstemci anahtarlığında kalanlarsa kendi aygıt kimlik bilgisi ve uzak bir sunucu için eşleştirme anahtarıdır.

  • Sırlar flutter_secure_storage içinde (macOS anahtarlığı, Windows kimlik bilgisi yöneticisi, Linux libsecret) — istemcinin aygıt kimlik bilgisi ve uzak eşleştirme anahtarları, sağlayıcı token’ları değil
  • Yalnızca hassas olmayan tercihler için shared_preferences (tema, yazı tipi, yerleşim)
  • Eski kurulumlardan kalan sağlayıcı sırları, features/auth/providers/credential_migration.dart tarafından bir kez sunucuya devredilir ve ardından istemci anahtarlığından silinir

Mimari kısıtlar, test/core/architecture_constraints_test.dart tarafından doğrulanır; bu test, bağımlılık kuralları ihlal edilirse başarısız olur.