Implementação e clientes
O Control Center não é um único programa. É uma arquitetura de cliente leve: um servidor headless que é dono dos seus dados e um conjunto de clientes que apresentam uma interface sobre uma única ligação RPC a esse servidor. Compreender esta separação explica porque é que algumas coisas correm onde correm e o que cada cliente consegue e não consegue alcançar.
O servidor é dono dos dados
Secção intitulada “O servidor é dono dos dados”Um processo chamado cc_server detém as bases de dados — uma pequena global.db
para o estado de todo o servidor mais um ficheiro SQLite por
espaço de trabalho, aberto de forma
preguiçosa — e executa o trabalho de longa duração: motores de pipelines,
reconciliadores, a superfície de ferramentas MCP e o ouvinte de orquestração. Tudo
o que precisa de sobreviver a uma janela, ou que dois clientes devem ver de forma
idêntica, vive aí.
Nenhum cliente abre um ficheiro de base de dados por si. Em vez disso, cada cliente
detém um RemoteRpcClient e emite operações de repo e subscrições através dele.
O servidor autentica a ligação com uma chave pré-partilhada de dispositivo
emparelhado — e depois deixa de se preocupar com a ligação. Uma sessão não está
vinculada a um espaço de trabalho. O distribuidor é sem estado: cada chamada com
âmbito de espaço de trabalho transporta o seu próprio workspace_id nos argumentos,
e uma chamada que o omita é recusada.
Isso é deliberado e é o que permite que dois clientes no mesmo servidor fiquem em espaços de trabalho diferentes sem um “espaço de trabalho atual” detido pelo servidor sobre o qual possam discordar. O isolamento é imposto por chamada, em quatro passos:
- A operação declara se tem âmbito de espaço de trabalho; se tiver e não chegar
nenhum
workspace_id, a chamada é rejeitada. - O id indicado tem de ser um espaço de trabalho registado, verificado antes de
algo abrir uma base de dados — caso contrário, um id desconhecido materializaria
um
workspace.dbfantasma vazio em cada pedido. Uma falha é um não-encontrado. - O
WorkspaceRolede quem chama tem de cumprir o mínimo da operação, derivado do seu tipo: ler exige convidado, alterar exige membro, destruir exige administrador. - Uma operação que expõe conteúdo de repo exige ainda uma concessão por repo ao
nível que declara —
none/read/review/write— para que a pertença a um espaço de trabalho nunca ultrapasse silenciosamente os privilégios da forge.
A frase que importa: ter uma chave de emparelhamento não é a fronteira de acesso; ser membro é.
Porque dividir desta forma
Secção intitulada “Porque dividir desta forma”Três pressões empurraram no sentido de um servidor:
- Uma única fonte de verdade. Um computador de secretária e um telemóvel, ou um portátil e um computador de secretária, devem ver o mesmo estado da frota. Se cada um tivesse a sua própria base de dados, iriam divergir. Um único servidor significa que todos os clientes leem os mesmos dados em tempo real.
- A web não se pode servir sozinha. Um navegador não consegue lançar um subprocesso nem abrir um ficheiro local, por isso uma build web nunca pode ser dona da base de dados nem executar um agente. Tem de falar com um servidor que o faça. Em vez de construir um segundo caminho de dados exclusivamente web, o computador foi trazido para o mesmo caminho.
- Operação headless. Um servidor que pode executar numa máquina pequena — acessível a partir de um telemóvel no comboio ou de um navegador em qualquer lugar — é mais útil do que uma GUI que tem de manter aberta.
O compromisso é que o servidor passa a ser uma dependência: se não estiver a correr, nada funciona. A resposta predefinida do computador para isso é lançar um por conta própria.
Os clientes
Secção intitulada “Os clientes”Cinco clientes chegam ao servidor, cada um com uma forma e um perfil de confiança diferentes.
Aplicação para computador
Secção intitulada “Aplicação para computador”A aplicação Flutter para computador (macOS, Windows, Linux) é o cliente completo. No primeiro arranque, pergunta como deve ser executada e depois memoriza a escolha:
- Local: lançar e supervisionar um
cc_servernesta máquina — a configuração predefinida, autónoma e de um único utilizador. O computador lança o servidor, que é dono da base de dados sob a raiz de suporte da aplicação e fala com ele através de loopback. - Remoto: ligar a um
cc_servera correr noutro local através de um WebSocket seguro. Os dados vivem nesse servidor; este computador é puramente um renderizador.
Nos dois casos, o cliente RPC resultante substitui o fornecedor de dados da aplicação, pelo que toda a interface — cada ecrã, cada funcionalidade — lê e escreve através do servidor. O computador nunca toca diretamente no ficheiro de base de dados.
Cliente web
Secção intitulada “Cliente web”A build web é a mesma aplicação Flutter compilada para correr num navegador, alojada
em app.usectrl.dev, por isso não há nada para instalar. Um
navegador não consegue lançar um subprocesso, pelo que o cliente web é sempre
remoto: liga-se a um cc_server por wss:// e apresenta toda a interface de
computador. Uma vez ligado, mostra os mesmos ecrãs sobre a mesma frota, porque é o
mesmo código sobre o mesmo RPC. As lacunas são o punhado de locais que precisam de uma
capacidade nativa que um navegador não tem — a camada de qualidade da captura de
reuniões abaixo é a principal e as notificações do sistema operativo são outra.
É por isso que “sem transferência” e “sem servidor” são afirmações diferentes. O
cliente web elimina a instalação; não elimina o servidor. Aquilo a que se liga pode
ser um cc_server no mesmo portátil — ws://localhost:9030, que não precisa de
certificado porque os navegadores consideram o loopback fiável — ou um noutra máquina,
que tem de estar acessível por wss:// com um certificado em que o navegador confie.
O cc_server recusa associar-se a um endereço que não seja de loopback sem TLS, por
isso um servidor remoto implica ou o seu próprio certificado, ou um proxy que termine
TLS, ou um túnel ou VPN que forneça um.
O próprio pacote está alojado separadamente do servidor com que fala. Um cc_server
autónomo serve o ponto final RPC, a superfície MCP e os seus proxies, mas não um
pacote web — o caminho de ficheiros estáticos existe no servidor, mas não está
configurada nenhuma raiz web no binário autónomo, por isso não tem nada para servir em
/. A imagem de contentor cc-webapp publicada é o que aloja o pacote e
scripts/build_web.sh é o que o compila. Um servidor só aceita origens de navegador
na sua lista --allowed-origins, que tem como predefinição
https://app.usectrl.dev, por isso alojar o pacote por conta própria implica
adicionar a sua origem.
Os conteúdos multimédia (avatares, imagens do feed, capturas de PR) são encaminhados pelo ponto final de proxy do servidor em vez de contactarem diretamente os servidores a montante, por isso um navegador nunca vai buscar uma origem arbitrária. O cliente implementado aplica uma Content-Security Policy por pedido a partir de um cookie não sensível, para que só a origem do servidor ligado seja permitida.
Companheiro para telemóvel (cc_remote)
Secção intitulada “Companheiro para telemóvel (cc_remote)”A aplicação Remote é um cliente separado e mais leve — uma PWA Flutter para web em remote.usectrl.dev. Deliberadamente não é a aplicação completa. Emparelha através de um relay WebSocket intermediado e selado ponto a ponto, e fala uma fatia predominantemente de leitura da superfície de ferramentas: ler tickets, mensagens e as notícias; enviar uma resposta ou atualizar um ticket.
Um telemóvel é um principal com menos privilégios do que um agente local. Uma política de ferramentas de negação por predefinição, uma chave pré-partilhada que sela cada frame, uma capacidade de sessão que exclui operações privilegiadas (o emparelhamento, por exemplo) de um cliente companheiro, as verificações de função e de concessão por repo por chamada acima referidas e a limitação de taxa impedem que um emparelhamento aprovado (ou divulgado) se torne controlo remoto total. Ver Controlo remoto e telemóvel para o modelo de segurança completo.
Clientes MCP
Secção intitulada “Clientes MCP”Ferramentas externas — editores, outros agentes, CLIs — que falam o Model Context Protocol ligam-se à superfície MCP do servidor e consomem o registo de ferramentas tipadas. Este é o caminho de integração programática; não é uma GUI. Ver Usar o servidor MCP e Ferramentas MCP.
Executores da frota (cc_worker)
Secção intitulada “Executores da frota (cc_worker)”Um cc_worker é um binário headless de Dart puro que emparelha com um cc_server,
declara as capacidades do seu anfitrião e recolhe trabalhos em lease para executar.
Transmite eventos de processo de volta pela mesma RPC e não detém nenhum estado
duradouro: sem base de dados, sem autenticação, sem aprovações, sem orçamentos —
isso nunca sai do cc_server. Um worker é um braço, não um segundo cérebro. Um
servidor autoritativo, N braços burros; não há consenso nem tráfego entre workers.
A execução em lease ainda é canalização de leases. agentRun é o único tipo que
executa um comando real (CC_JOB_COMMAND, ou um prompt ecoado se nenhum tiver sido
fornecido). pipelineStep, codeIndex, goldenRender, benchmark e evalBatch
testam atualmente com git --version / rev-parse HEAD para que o caminho de
streaming seja exercitado sem fingir que esses trabalhos correram.
O que anuncia é o sistema operativo, a arquitetura de CPU, o número de núcleos, a RAM,
se um Flutter SDK está acessível e que backends de sandbox o anfitrião tem (nenhum no
Windows). Um worker dedicado anuncia também sempre alwaysOn e acceptsParallel, que
são os critérios em função dos quais o escalonador pin → prefer → spill faz a
colocação. Não há qualquer sondagem de GPU nem de ML.
Um computador que funcione sozinho não dá por nada disto, porque o despacho normal não
passa pela frota de todo. Existe um worker implícito no próprio processo registado
para o anfitrião do servidor, mas é apenas uma costura de registo sem runners ligados
a ela — um trabalho colocado no worker local falha em vez de ser executado. Hoje, só
um cc_worker real executa trabalhos em lease.
O que corre onde
Secção intitulada “O que corre onde”| Aspeto | Servidor (cc_server) |
Executor da frota (cc_worker) |
Cliente de computador | Cliente web / telemóvel |
|---|---|---|---|---|
| Bases de dados (Drift/SQLite) | é dono delas | — | — | — |
| Autenticação, aprovações, orçamentos, identidade | é dono disso | — | — | — |
| Pipelines, reconciliadores, ouvinte de orquestração | executa | — | — | — |
| Superfície de ferramentas MCP | serve-a | — | — | — |
| Execução de agentes | executa-a (ou passa-a por lease) | executa trabalhos em lease | — | — |
| Apresentação da interface | — | — | completa | completa (web) / sobretudo de leitura (telemóvel) |
| Captura de reuniões (microfone + áudio da reunião) | — | — | captura nativa | só na web, através do áudio da partilha de ecrã |
| OAuth do calendário (Google) | guarda os tokens | — | — | — |
Os dois níveis não aplicam a mesma sandbox. O cc_server envolve as execuções que ele
próprio realiza na sandbox do sistema operativo do anfitrião, onde existe um backend —
Seatbelt no macOS, bubblewrap no Linux e no WSL2 — e recorre às outras fronteiras no
Windows ou sem bwrap e socat. O cc_worker não aplica sandbox nenhuma.
Anuncia uma capacidade sandbox para que o servidor possa colocar trabalhos por
plataforma, mas nada no worker aplica uma: um trabalho em lease é lançado diretamente,
limitado pelo ambiente do lease e pela worktree que lhe foi atribuída. Entregar
trabalho por lease desloca, por isso, a execução para fora da sandbox, mesmo num
anfitrião que tenha uma. Ver
Sandbox e segurança para saber o que
limita uma execução em cada caso.
A captura de reuniões acontece num cliente porque precisa de um microfone e do áudio da reunião — um servidor guardado num armário não consegue gravar a sua chamada. A gravação é depois resumida pelo pipeline do servidor e as notas são guardadas onde todos os clientes as possam ler.
O cliente web também grava, algo que a forma da tabela pode obscurecer: capta o microfone mais uma faixa de áudio de partilha de ecrã no navegador e transmite PCM a 16 kHz para o servidor. Exige essa faixa de áudio, por isso falha explicitamente onde o navegador não a forneça. O que é genuinamente exclusivo do computador é a camada de qualidade em torno da captura — cancelamento de eco ao nível do sinal através do processador nativo, o medidor de nível de entrada e o aviso de microfone inativo, voltar a executar um resumo, cancelar o processamento e a ação de gravar e vincular do calendário.
Emparelhamento e confiança
Secção intitulada “Emparelhamento e confiança”Cada cliente prova quem é com uma chave pré-partilhada fornecida uma única vez:
- O computador em modo local gera uma chave nova em cada arranque e entrega-a ao servidor lançado através do ambiente, para que nada secreto seja persistido no computador.
- Os clientes remotos (computador remoto, web, telemóvel) usam uma chave emitida pelo
comando
pairdo servidor e guardada no porta-chaves do sistema operativo (ou, no telemóvel, localmente).
A revogação é imediata, não preguiçosa. O servidor vigia a sua tabela de dispositivos emparelhados e derruba qualquer sessão aberta cujo dispositivo tenha saído do conjunto ativo, em segundos — tanto no caminho direto por WebSocket como no caminho do relay. Não tem de esperar que o dispositivo se volte a ligar para o cortar.
A chave nunca viaja através de um URL que um alojamento estático possa ver. Quando
chega por link — no telemóvel ou num navegador — vai no fragmento do URL, a parte
depois de #, que o navegador nunca envia ao servidor que aloja a página. Essa é a
invariante que vale a pena recordar, e aplica-se a ambos. O cliente web tem dois outros
pontos de entrada: o formulário de ligação, onde cola diretamente um servidor e uma
chave, e o código de convite, que troca por uma credencial emitida só para esse
navegador.
Conceitos relacionados
Secção intitulada “Conceitos relacionados”- Espaços de trabalho e isolamento: porque é que cada chamada RPC transporta o seu próprio id de espaço de trabalho e como a divisão da base de dados torna o isolamento estrutural
- Arquitetura: como o código está estruturado em torno do servidor e dos seus clientes
- Controlo remoto e telemóvel: o modelo de emparelhamento e segurança do telemóvel
Guias relacionados
Secção intitulada “Guias relacionados”- Executar um servidor headless: iniciar o
cc_servere emparelhar um cliente - Ligar a um servidor remoto: apontar o cliente de computador ou web para um servidor noutro local
- Emparelhar um dispositivo: adicionar um telemóvel, um segundo navegador ou outro computador
- Executar um worker da frota: adicionar um
cc_workerque recolhe trabalhos em lease