Розгортання та клієнти
Control Center — не одна програма. Це архітектура тонкого клієнта: headless-сервер, який володіє вашими даними, і набір клієнтів, які рендерять інтерфейс поверх одного RPC-з’єднання з ним. Розуміння цього розділення пояснює, чому деякі речі виконуються саме там, де виконуються, і чого кожен клієнт може досягти, а чого ні.
Сервер володіє даними
Розділ із назвою «Сервер володіє даними»Процес cc_server тримає бази даних — невеликий global.db для стану
всього сервера плюс один файл SQLite на кожен
робочий простір, який
відкривається ліниво, — і виконує довготривалу роботу: рушії конвеєрів,
узгоджувачі, поверхню інструментів MCP та слухач оркестрації. Усе, що має
пережити вікно або що два клієнти повинні бачити однаково, живе там.
Жоден клієнт не відкриває файл бази даних сам. Натомість кожен клієнт тримає
один RemoteRpcClient і виконує через нього операції з репозиторіями та
підписки. Сервер автентифікує з’єднання за попередньо спільним ключем
спарованого пристрою — а тоді перестає опікуватися з’єднанням. Сеанс не
прив’язаний до робочого простору. Диспетчер безстановий: кожен виклик у
межах робочого простору несе власний workspace_id у своїх аргументах, а
виклик, у якому його немає, відхиляють.
Це навмисно, і саме це дає двом клієнтам на одному сервері тримати різні робочі простори без серверного «поточного робочого простору», щодо якого вони могли б розійтися. Натомість ізоляцію забезпечують на кожен виклик, у чотири кроки:
- Операція заявляє, чи вона належить до робочого простору; якщо так, а
workspace_idне надійшов, виклик відхиляють. - Названий ідентифікатор має бути зареєстрованим робочим простором, і це
перевіряють до того, як щось відкриє базу даних — інакше невідомий
ідентифікатор створював би порожній фантомний
workspace.dbна кожен запит. Промах — це «не знайдено». WorkspaceRoleтого, хто викликає, має досягати нижньої межі операції, виведеної з її виду: читання потребує гостя, зміна — учасника, руйнівна дія — адміна.- Операція, яка розкриває вміст репозиторію, додатково вимагає дозволу на
кожен репозиторій на тому рівні, який вона заявляє —
none/read/review/write— тож членство в робочому просторі ніколи не дає тихо більше прав, ніж хост коду.
Речення, яке має значення: володіння ключем спарювання не є межею доступу; нею є членство.
Чому саме таке розділення
Розділ із назвою «Чому саме таке розділення»До сервера вели три сили:
- Одне джерело істини. Настільний застосунок і телефон, або ноутбук і настільний застосунок, мають бачити той самий стан флоту. Якби кожен володів власною базою даних, вони б розходилися. Єдиний сервер означає, що кожен клієнт читає ті самі живі дані.
- Веб не може обслужити себе сам. Браузер не може запустити підпроцес чи відкрити локальний файл, тож вебзбірка ніколи не зможе володіти базою даних або запускати агента. Вона мусить говорити із сервером, який це робить. Замість будувати другий, суто вебовий шлях до даних, настільний застосунок перевели на той самий шлях.
- Робота без графічного інтерфейсу. Сервер, який можна запустити на маленькій машинці — доступний із телефона в потязі чи з браузера будь-де, — корисніший за графічний інтерфейс, який доводиться тримати відкритим.
Компроміс у тому, що тепер сервер є залежністю: якщо він не працює, не працює нічого. Типова відповідь настільного застосунку на це — запустити власний.
Клієнти
Розділ із назвою «Клієнти»До сервера доходять п’ять клієнтів, кожен з іншою формою та профілем довіри.
Настільний застосунок
Розділ із назвою «Настільний застосунок»Настільний застосунок на Flutter (macOS, Windows, Linux) — це повний клієнт. Під час першого запуску він запитує, як має працювати, а потім запам’ятовує вибір:
- Локально: запустити й наглядати за
cc_serverна цій машині — типовий, самодостатній однокористувацький варіант. Настільний застосунок запускає сервер, який володіє базою даних у корені підтримки застосунку, а спілкується з ним через loopback. - Віддалено: під’єднатися до
cc_server, що працює деінде, через захищене WebSocket-з’єднання. Дані живуть на тому сервері; цей настільний застосунок — лише рендерер.
В обох випадках отриманий RPC-клієнт перевизначає постачальника даних застосунку, тож весь інтерфейс — кожен екран, кожна функція — читає й пише через сервер. Настільний застосунок ніколи не торкається файлу бази даних напряму.
Вебклієнт
Розділ із назвою «Вебклієнт»Вебзбірка — це той самий застосунок Flutter, скомпільований для роботи в
браузері й розміщений на app.usectrl.dev, тож
встановлювати нічого не треба. Браузер не може запустити підпроцес, тож
вебклієнт завжди віддалений: він під’єднується до cc_server через wss://
і рендерить повний настільний інтерфейс. Після підключення він показує ті самі
екрани проти того самого флоту, бо це той самий код поверх того самого RPC.
Прогалини — це кілька місць, яким потрібна нативна можливість, якої браузер не
має: головна з них — описаний нижче шар якості запису зустрічей, а друга —
сповіщення ОС.
Ось чому «без завантаження» і «без сервера» — різні твердження. Вебклієнт
прибирає встановлення; він не прибирає сервер. Те, до чого він під’єднується,
може бути cc_server на тому самому ноутбуці — ws://localhost:9030, який не
потребує сертифіката, бо браузери вважають loopback надійним, — або на іншій
машині, яка має бути доступною через wss:// із сертифікатом, якому браузер
довіряє. cc_server відмовляється прив’язувати не-loopback адресу без TLS, тож
віддалений сервер означає або власний сертифікат, або проксі, що завершує TLS,
або тунель чи VPN, який його надає.
Сам пакет розміщено окремо від сервера, з яким він спілкується. Окремий
cc_server обслуговує кінцеву точку RPC, поверхню MCP та її проксі, але не
вебпакет — шлях для статичних файлів у сервері існує, проте в окремому бінарнику
не налаштовано жодного вебкореня, тож йому нічого віддавати на /. Опублікований
образ контейнера cc-webapp — це те, що розміщує пакет, а
scripts/build_web.sh — те, що його збирає. Сервер приймає браузерні джерела
лише зі свого списку --allowed-origins, який типово дорівнює
https://app.usectrl.dev, тож самостійне розміщення пакета означає додати його
джерело.
Медіа (аватари, зображення стрічки, скриншоти PR) проходить через проксі-кінцеву точку сервера, а не звертається до вихідних хостів напряму, тож браузер ніколи не завантажує довільне джерело. Розгорнутий клієнт проставляє політику Content-Security-Policy на кожен запит із неконфіденційного cookie, тож дозволено лише джерело підключеного сервера.
Телефонний супровідний застосунок (cc_remote)
Розділ із назвою «Телефонний супровідний застосунок (cc_remote)»Застосунок Remote — окремий, легший клієнт: веб-PWA на Flutter за адресою remote.usectrl.dev. Він навмисно не є повним застосунком. Він спарюється через реле з брокером на WebSocket із наскрізним запечатуванням кадрів і говорить переважно читацьким зрізом поверхні інструментів: читає тікети, повідомлення та стрічку новин; надсилає відповідь або оновлює тікет.
Телефон — це принципал із нижчими привілеями, ніж локальний агент. Політика інструментів із забороною за замовчуванням, попередньо спільний ключ, який запечатує кожен кадр, можливість сеансу, яка закриває привілейовані операції (приміром, спарювання) для супровідного клієнта, наведені вище перевірки ролі та дозволу на репозиторій на кожен виклик і обмеження швидкості не дають схваленому (або злитому) спарюванню перетворитися на повне віддалене керування. Повну модель безпеки див. у розділі Віддалене керування та мобільний клієнт.
Клієнти MCP
Розділ із назвою «Клієнти MCP»Зовнішні інструменти — редактори, інші агенти, CLI, — які говорять протоколом Model Context Protocol, підключаються до поверхні MCP сервера й споживають типізований реєстр інструментів. Це програмний шлях інтеграції; це не графічний інтерфейс. Див. Використовувати MCP-сервер та Інструменти MCP.
Воркери флоту (cc_worker)
Розділ із назвою «Воркери флоту (cc_worker)»cc_worker — це headless-бінарник на чистому Dart, який спарюється з
cc_server, заявляє можливості свого хоста й забирає орендовані завдання на
виконання. Він транслює події процесів назад тим самим RPC і не тримає
жодного довготривалого стану: ні бази даних, ні автентифікації, ні схвалень,
ні бюджетів — усе це ніколи не покидає cc_server. Воркер — це кінцівка, а не
другий мозок. Один авторитетний сервер, N німих кінцівок; немає ні консенсусу,
ні трафіку між воркерами.
Орендоване виконання поки що зводиться до механіки оренди. agentRun — єдиний
вид, який виконує справжню команду (CC_JOB_COMMAND або промпт, повернутий
луною, якщо жодної не надали). pipelineStep, codeIndex, goldenRender,
benchmark і evalBatch наразі пробують git --version / rev-parse HEAD,
щоб задіяти шлях трансляції, не вдаючи, ніби ці завдання справді виконалися.
Він заявляє операційну систему, архітектуру CPU, кількість ядер, обсяг RAM, чи
доступний Flutter SDK, і які бекенди пісочниці має хост (на Windows — жодного).
Виділений воркер також завжди заявляє alwaysOn і acceptsParallel — саме
проти них розміщує завдання планувальник «закріпити → віддати перевагу →
перелити». Жодного зондування GPU чи ML немає.
Одноосібний настільний застосунок нічого цього не помічає, бо звичайна
диспетчеризація взагалі не йде через флот. Для хоста сервера справді
зареєстровано неявний внутрішньопроцесний воркер, але це лише шов реєстрації, до
якого не під’єднано жодного виконавця: завдання, фактично розміщене на
локальному воркері, зазнає невдачі, а не виконується. Сьогодні орендовані
завдання виконує лише справжній cc_worker.
Що де виконується
Розділ із назвою «Що де виконується»| Аспект | Сервер (cc_server) |
Воркер флоту (cc_worker) |
Настільний клієнт | Веб- / телефонний клієнт |
|---|---|---|---|---|
| Бази даних (Drift/SQLite) | володіє ними | — | — | — |
| Автентифікація, схвалення, бюджети, ідентичність | володіє цим | — | — | — |
| Конвеєри, узгоджувачі, слухач оркестрації | виконує це | — | — | — |
| Поверхня інструментів MCP | обслуговує її | — | — | — |
| Виконання агентів | виконує (або віддає в оренду) | виконує орендовані завдання | — | — |
| Рендеринг інтерфейсу | — | — | повний | повний (веб) / переважно читацький (телефон) |
| Запис зустрічей (мікрофон + звук зустрічі) | — | — | нативний запис | лише веб, через звук демонстрації екрана |
| OAuth календаря (Google) | зберігає токени | — | — | — |
Ці два рівні ізолюють по-різному. cc_server загортає запуски, які виконує сам,
у пісочницю ОС хоста, де такий бекенд існує — Seatbelt на macOS, bubblewrap на
Linux і WSL2 — і відкочується до інших меж на Windows або без bwrap і socat.
cc_worker не ізолює взагалі. Він заявляє можливість sandbox, щоб сервер
міг розміщувати завдання за платформою, але ніщо у воркері її не застосовує:
орендоване завдання запускають напряму, і його обмежують середовище оренди та
наданий worktree. Тож віддавання роботи в оренду виносить виконання за межі
пісочниці навіть на хості, де вона є. Про те, що обмежує запуск у кожному
випадку, див.
Пісочниця та безпека.
Запис зустрічей відбувається на клієнті, бо йому потрібен мікрофон і звук зустрічі — сервер у комірчині не може записати вашу розмову. Потім запис резюмує конвеєр сервера, а нотатки зберігаються там, звідки їх прочитає кожен клієнт.
Вебклієнт теж записує, що форма таблиці може приховати: він захоплює мікрофон і звукову доріжку демонстрації екрана в браузері та транслює на сервер PCM з частотою 16 кГц. Він вимагає цієї звукової доріжки, тож явно зазнає невдачі там, де браузер її не надає. Справді настільним є лише шар якості навколо запису — скасування відлуння на рівні сигналу через нативний процесор, індикатор рівня входу й попередження про мертвий мікрофон, повторне резюмування, скасування обробки та дія календаря «записати й прикріпити».
Спарювання та довіра
Розділ із назвою «Спарювання та довіра»Кожен клієнт доводить, хто він, за допомогою попередньо спільного ключа, наданого один раз:
- Настільний застосунок у локальному режимі генерує новий ключ під час кожного запуску й передає його запущеному серверу через середовище, тож на настільному застосунку не зберігається нічого таємного.
- Віддалені клієнти (настільний у віддаленому режимі, веб, телефон)
використовують ключ, створений командою
pairсервера й збережений у ланцюжку ключів ОС (а на телефоні — локально).
Відкликання діє наживо, а не ліниво. Сервер стежить за своєю таблицею спарованих пристроїв і розриває будь-який відкритий сеанс, чий пристрій вибув з активного набору, за секунди — як на прямому шляху WebSocket, так і на шляху через реле. Вам не потрібно чекати, доки пристрій перепідключиться, щоб його відрізати.
Ключ ніколи не подорожує URL-адресою, яку може побачити статичний хост. Коли він
надходить за посиланням — на телефоні чи в браузері — він їде у фрагменті
URL, частині після #, яку браузер ніколи не надсилає на сервер, що розміщує
сторінку. Це інваріант, який варто запам’ятати, і він стосується обох. У
вебклієнта є ще дві точки входу: форма підключення, де ви вставляєте сервер і
ключ напряму, та код запрошення, який він обмінює на облікові дані, створені
лише для цього браузера.
Пов’язані поняття
Розділ із назвою «Пов’язані поняття»- Робочі простори та ізоляція: чому кожен виклик RPC несе власний ідентифікатор робочого простору і як розділення баз даних робить ізоляцію структурною
- Архітектура: як кодову базу організовано навколо сервера та його клієнтів
- Віддалене керування та мобільний клієнт: модель спарювання телефона та безпеки
Пов’язані посібники
Розділ із назвою «Пов’язані посібники»- Запустити headless-сервер: запустити
cc_serverі спарувати клієнт - Під’єднатися до віддаленого сервера: спрямувати настільний або вебклієнт на сервер деінде
- Прив’язати пристрій: додати телефон, другий браузер або інший настільний застосунок
- Запустити воркер флоту: додати
cc_worker, який забирає орендовані завдання