Lewati ke konten

Deployment dan klien

Control Center bukan satu program. Control Center adalah arsitektur thin-client: server headless yang memiliki data Anda dan sekumpulan klien yang menampilkan antarmuka melalui satu koneksi RPC ke server itu. Memahami pemisahan ini menjelaskan mengapa sebagian hal berjalan di tempat tertentu dan apa yang dapat dan tidak dapat dijangkau setiap klien.

Sebuah proses bernama cc_server memegang basis data — global.db kecil untuk status seluruh server plus satu file SQLite per ruang kerja, yang dibuka secara lazy — dan menjalankan pekerjaan berumur panjang: mesin pipeline, reconciler, permukaan alat MCP, dan listener orkestrasi. Semua yang perlu bertahan lebih lama dari sebuah jendela, atau yang harus dilihat identik oleh dua klien, berada di sana.

Tidak ada klien yang membuka file basis data sendiri. Sebaliknya, setiap klien memegang satu RemoteRpcClient dan mengirim operasi repo serta langganan melaluinya. Server mengautentikasi koneksi terhadap kunci pre-shared perangkat yang dipasangkan — lalu berhenti memedulikan koneksi itu. Sebuah sesi tidak terikat pada ruang kerja. Dispatcher bersifat stateless: setiap panggilan berlingkup ruang kerja membawa workspace_id-nya sendiri di argumennya dan panggilan yang menghilangkannya akan ditolak.

Hal itu disengaja dan itulah yang memungkinkan dua klien di satu server berada di ruang kerja berbeda tanpa “ruang kerja saat ini” yang dipegang server untuk mereka perselisihkan. Isolasi ditegakkan per panggilan, dalam empat langkah:

  1. Operasi mendeklarasikan apakah operasinya berlingkup ruang kerja; jika ya dan tidak ada workspace_id yang datang, panggilan ditolak.
  2. Id yang disebutkan harus merupakan ruang kerja yang terdaftar, diperiksa sebelum apa pun membuka basis data — jika tidak, id yang tidak dikenal akan mewujudkan workspace.db hantu yang kosong pada setiap permintaan. Pencarian yang meleset berarti tidak ditemukan.
  3. WorkspaceRole pemanggil harus memenuhi ambang minimum operasi, yang diturunkan dari jenisnya: baca butuh guest, mutasi butuh member, destruktif butuh admin.
  4. Operasi yang mengekspos konten repo juga memerlukan hibah per repo pada tingkat yang dideklarasikannya — none / read / review / write — sehingga keanggotaan ruang kerja tidak pernah diam-diam memberi hak istimewa melebihi forge.

Kalimat yang penting: memegang kunci pairing bukanlah batas akses; keanggotaanlah batasnya.

Tiga tekanan mendorong ke arah server:

  1. Satu sumber kebenaran. Desktop dan ponsel, atau laptop dan desktop, harus melihat status fleet yang sama. Jika masing-masing memiliki basis datanya sendiri, keduanya akan menyimpang. Satu server berarti setiap klien membaca data langsung yang sama.
  2. Web tidak bisa melayani dirinya sendiri. Peramban tidak dapat menjalankan subproses atau membuka file lokal, jadi build web tidak akan pernah bisa memiliki basis data atau menjalankan agen. Web harus berbicara dengan server yang melakukannya. Alih-alih membangun jalur data kedua yang hanya untuk web, desktop dibawa ke jalur yang sama.
  3. Operasi headless. Server yang dapat Anda jalankan di mesin kecil — dapat dijangkau dari ponsel di kereta atau peramban di mana pun — lebih berguna daripada GUI yang harus Anda biarkan terbuka.

Komprominya adalah server kini menjadi dependensi: jika server tidak berjalan, tidak ada yang berfungsi. Jawaban bawaan desktop untuk hal itu adalah menjalankan servernya sendiri.

Lima klien menjangkau server, masing-masing dengan bentuk dan profil kepercayaan yang berbeda.

Aplikasi desktop Flutter (macOS, Windows, Linux) adalah klien lengkap. Saat pertama kali dijalankan, aplikasi ini menanyakan bagaimana ia harus berjalan, lalu mengingat pilihannya:

  • Local: jalankan dan awasi cc_server di mesin ini — penyiapan bawaan, mandiri, dan untuk pengguna tunggal. Desktop menjalankan server, yang memiliki basis data di bawah root app-support dan berbicara dengannya melalui loopback.
  • Remote: hubungkan ke cc_server yang berjalan di tempat lain melalui WebSocket aman. Data berada di server itu; desktop ini murni sebuah perender.

Dalam kedua kasus, klien RPC yang dihasilkan menggantikan penyedia data aplikasi, sehingga seluruh UI — setiap layar, setiap fitur — membaca dan menulis melalui server. Desktop tidak pernah menyentuh file basis data secara langsung.

Build web adalah aplikasi Flutter yang sama yang dikompilasi untuk berjalan di peramban, dihosting di app.usectrl.dev sehingga tidak ada yang perlu diinstal. Peramban tidak dapat menjalankan subproses, jadi klien web selalu remote: klien ini menghubungi cc_server melalui wss:// dan menampilkan antarmuka desktop lengkap. Setelah terhubung, klien ini menampilkan layar yang sama terhadap fleet yang sama, karena kodenya sama melalui RPC yang sama. Kesenjangannya adalah segelintir tempat yang memerlukan kemampuan native yang tidak dimiliki peramban — lapisan kualitas perekaman rapat di bawah adalah yang utama dan notifikasi OS adalah yang lain.

Inilah sebabnya “tanpa unduhan” dan “tanpa server” adalah klaim yang berbeda. Klien web menghilangkan instalasi; klien ini tidak menghilangkan server. Yang dihubunginya bisa berupa cc_server di laptop yang sama — ws://localhost:9030, yang tidak memerlukan sertifikat karena peramban menganggap loopback dapat dipercaya — atau server di mesin lain, yang harus dapat dijangkau melalui wss:// dengan sertifikat yang dipercaya peramban. cc_server menolak mengikat alamat non-loopback tanpa TLS, jadi server jarak jauh berarti entah sertifikatnya sendiri, proxy terminasi TLS, atau tunnel atau VPN yang menyediakannya.

Bundle itu sendiri dihosting terpisah dari server yang diajaknya bicara. cc_server mandiri menyajikan endpoint RPC, permukaan MCP, dan proksinya, tetapi tidak menyajikan bundle web — jalur file statis ada di server, tetapi tidak ada web root yang dikonfigurasi pada binary mandiri, jadi tidak ada yang bisa disajikannya di /. Image kontainer cc-webapp yang diterbitkan adalah yang menghosting bundle dan scripts/build_web.sh adalah yang membangunnya. Server hanya menerima origin peramban pada daftar --allowed-origins-nya, yang bawaannya https://app.usectrl.dev, jadi menghosting sendiri bundle berarti menambahkan origin-nya.

Media (avatar, gambar feed, tangkapan layar PR) dirutekan melalui endpoint proxy server alih-alih memanggil host hulu secara langsung, sehingga peramban tidak pernah mengambil origin sembarangan. Klien yang di-deploy menyematkan Content-Security Policy per permintaan dari cookie non-sensitif sehingga hanya origin server yang terhubung yang diizinkan.

Aplikasi Remote adalah klien terpisah yang lebih ringan — PWA web Flutter di remote.usectrl.dev. Aplikasi ini sengaja bukan aplikasi lengkapnya. Aplikasi ini dipasangkan melalui relay WebSocket yang di-broker dan disegel end-to-end, lalu berbicara dengan sebagian besar operasi baca dari permukaan alat: membaca tiket, pesan, dan umpan berita; mengirim balasan atau memperbarui tiket.

Ponsel adalah principal dengan hak istimewa lebih rendah daripada agen lokal. Kebijakan alat yang menolak secara bawaan, kunci pre-shared yang menyegel setiap frame, kapabilitas sesi yang mengunci operasi berhak istimewa (misalnya pairing) dari klien pendamping, pemeriksaan peran dan hibah repo per panggilan di atas, serta pembatasan laju mencegah pairing yang disetujui (atau bocor) menjadi kontrol jarak jauh penuh. Lihat Kontrol jarak jauh dan seluler untuk model keamanan selengkapnya.

Alat eksternal — editor, agen lain, CLI — yang berbicara Model Context Protocol terhubung ke permukaan MCP server dan mengonsumsi registri alat bertipe. Ini adalah jalur integrasi programatik; ini bukan GUI. Lihat Gunakan server MCP dan Referensi alat MCP.

Sebuah cc_worker adalah binary pure-Dart headless yang dipasangkan dengan cc_server, mendeklarasikan kapabilitas host-nya, dan menarik pekerjaan sewaan untuk dieksekusi. Worker ini mengalirkan peristiwa proses kembali melalui RPC yang sama dan tidak menyimpan state permanen: tidak ada basis data, tidak ada autentikasi, tidak ada persetujuan, tidak ada anggaran — semua itu tidak pernah meninggalkan cc_server. Worker adalah lengan, bukan otak kedua. Satu server otoritatif, N lengan bodoh; tidak ada konsensus, tidak ada lalu lintas antar-worker.

Eksekusi sewaan tetaplah perpipaan sewa. agentRun adalah satu-satunya jenis yang menjalankan perintah sungguhan (CC_JOB_COMMAND, atau prompt yang digemakan jika tidak ada yang diberikan). pipelineStep, codeIndex, goldenRender, benchmark dan evalBatch saat ini melakukan probe dengan git --version / rev-parse HEAD agar jalur streaming teruji tanpa berpura-pura pekerjaan tersebut benar-benar berjalan.

Yang diiklankannya adalah sistem operasi, arsitektur CPU, jumlah inti, RAM, apakah Flutter SDK dapat dijangkau dan backend sandbox apa saja yang dimiliki host (tidak ada di Windows). Worker khusus juga selalu mengiklankan alwaysOn dan acceptsParallel, yang menjadi acuan penempatan oleh penjadwal pin → prefer → spill. Tidak ada probe GPU atau ML.

Desktop tunggal tidak menyadari semua ini, karena pengiriman biasa tidak melewati fleet sama sekali. Memang ada worker in-process implisit yang terdaftar untuk host server, tetapi itu hanyalah titik sambung pendaftaran tanpa runner yang terhubung ke dalamnya — pekerjaan yang benar-benar ditempatkan pada worker lokal akan gagal alih-alih dieksekusi. Saat ini, hanya cc_worker sungguhan yang mengeksekusi pekerjaan sewaan.

Perihal Server (cc_server) Eksekutor fleet (cc_worker) Klien desktop Klien web / ponsel
Basis data (Drift/SQLite) memilikinya — — —
Autentikasi, persetujuan, anggaran, identitas memilikinya — — —
Pipeline, reconciler, listener orkestrasi menjalankannya — — —
Permukaan alat MCP menyajikannya — — —
Eksekusi agen menjalankannya (atau menyewakannya) mengeksekusi pekerjaan sewaan — —
Perenderan UI — — lengkap lengkap (web) / sebagian besar baca (ponsel)
Perekaman rapat (mikrofon + audio rapat) — — perekaman native hanya web, melalui audio berbagi layar
OAuth kalender (Google) menyimpan token — — —

Kedua tingkat ini tidak melakukan sandbox dengan cara yang sama. cc_server membungkus jalan yang dieksekusinya sendiri dalam sandbox OS host di tempat backend tersedia — Seatbelt di macOS, bubblewrap di Linux dan WSL2 — dan jatuh kembali ke batas lain di Windows atau tanpa bwrap dan socat. cc_worker tidak melakukan sandbox sama sekali. Worker mengiklankan kapabilitas sandbox agar server dapat menempatkan pekerjaan berdasarkan platform, tetapi tidak ada apa pun di dalam worker yang menerapkannya: pekerjaan sewaan dijalankan langsung, dibatasi oleh lingkungan sewa dan worktree yang diberikan kepadanya. Menyewakan pekerjaan ke luar karena itu memindahkan eksekusi ke luar sandbox bahkan pada host yang memilikinya. Lihat Sandbox dan keamanan untuk apa yang membatasi sebuah jalan di setiap kasus.

Perekaman rapat terjadi di klien karena memerlukan mikrofon dan audio rapat — server di dalam lemari tidak dapat merekam panggilan Anda. Rekaman itu kemudian diringkas oleh pipeline server dan catatannya disimpan di tempat yang dapat dibaca setiap klien.

Klien web juga merekam, yang bisa terabaikan oleh bentuk tabel: klien ini menangkap mikrofon plus trek audio berbagi layar di peramban dan mengalirkan PCM 16 kHz ke server. Klien ini mewajibkan trek audio tersebut, jadi klien ini gagal secara eksplisit di tempat peramban tidak menyediakannya. Yang benar-benar khusus desktop adalah lapisan kualitas di sekitar perekaman — pembatalan gema tingkat sinyal melalui prosesor native, pengukur tingkat masukan dan peringatan mikrofon mati, menjalankan ulang ringkasan, membatalkan pemrosesan, dan tindakan rekam-dan-tautkan pada kalender.

Setiap klien membuktikan identitasnya dengan kunci pre-shared yang disediakan sekali:

  • Desktop dalam mode lokal membuat kunci baru setiap boot dan menyerahkannya ke server yang dijalankan melalui environment, sehingga tidak ada rahasia yang disimpan di desktop.
  • Klien remote (desktop-remote, web, ponsel) memakai kunci yang dibuat oleh perintah pair server dan disimpan di keychain OS (atau, di ponsel, secara lokal).

Pencabutan bersifat langsung, bukan tertunda. Server mengawasi tabel perangkat yang dipasangkan dan memutus sesi apa pun yang terbuka yang perangkatnya telah keluar dari kumpulan aktif, dalam hitungan detik — baik pada jalur WebSocket langsung maupun jalur relay. Anda tidak perlu menunggu perangkat menyambung ulang untuk memutusnya.

Kunci tidak pernah melewati URL yang dapat dilihat host statis. Saat kunci datang melalui tautan — di ponsel atau di peramban — kunci itu berada di fragmen URL, bagian setelah #, yang tidak pernah dikirim peramban ke server yang menghosting halaman. Itulah invarian yang perlu diingat dan berlaku untuk keduanya. Klien web memiliki dua titik masuk lain: formulir koneksi, tempat Anda menempelkan server dan kunci secara langsung, serta kode undangan, yang ditukarkannya dengan kredensial yang dibuat khusus untuk peramban itu saja.