Ga naar inhoud

Afstandsbediening en mobiel

Control Center is een desktopapp, maar het werk dat het volgt stopt niet wanneer je van je bureau weggaat. Afstandsbediening koppelt je telefoon aan je cc_server, met Remote, de bijbehorende app op remote.usectrl.dev, zodat je de fleet vanaf daar kunt volgen: berichten en tickets lezen, een agent antwoorden, je nieuwsfeed triëren, zonder dat de audio, het transcript, de broncode of de referenties die op de server staan die ooit verlaten.

Zie het als een metgezel van het deck die vooral leest. Het is niet bedoeld om de volledige desktopclient te vervangen.

De telefoon bereikt cc_server via een WebSocket: ofwel een directe verbinding (loopback, LAN, tailnet of wss://), ofwel een ruimte waarvoor een uitnodiging nodig is op een signaleringsbroker die verzegelde frames doorgeeft. Er is geen peer-to-peer-datakanaal.

De gekoppelde telefoon bereikt een klein, bewust gekozen deel van het oppervlak van de server:

  • Lezen: tickets, agenten, ruimtes en hun berichten, en je nieuwsfeed
  • Antwoorden: een bericht in een ruimte sturen, een ticket bijwerken of toewijzen, een artikel als gelezen of bewaard markeren
  • Een playbook uitvoeren: een opgeslagen planningssjabloon met zijn parameters instantiëren. Dit stelt alleen een plan voor; er wordt niets uitgevoerd of besteed tot een operator het in Plan Studio op een volledige client goedkeurt
  • Van werkruimte wisselen, via de eigen kiezer van de telefoon

Dat is ongeveer alles. Een telefoon is een principal met minder rechten dan een volledige client. Hij kan geen processen starten, geen LLM-budget besteden, niet naar GitHub pushen en geen werkruimtes aanmaken. Zie het toegangsmodel voor hoe dat wordt afgedwongen in plaats van alleen bedoeld.

De verbinding loopt via een relay met een broker: de server voegt zich als eigenaar bij één ruimte waarvoor een uitnodiging nodig is op een wss://-broker en elke gekoppelde client — desktop, web, telefoon — voegt zich bij dezelfde ruimte met een tijdgebonden toegangstoken. Drie onderdelen komen samen:

Onderdeel Rol
Remote (telefoonapp) De bijbehorende webapp, standaard gehost op remote.usectrl.dev, of zelf gehost. Laadt in een browser en houdt de koppelingsregistratie bij in IndexedDB.
Relay-broker Een stateless wss://-relay (cc_signaling_server) die N-weg-ruimtes host. Geeft alleen ondoorzichtige frames door: elke payload wordt end-to-end verzegeld tussen de telefoon en de server, dus de broker ziet nooit app-gegevens of het koppelingsgeheim en interpreteert nooit wat hij doorstuurt.
cc_server De eigenaar van de ruimte. Beantwoordt de aanroepen van de telefoon via dezelfde dispatcher die elke andere client gebruikt.

Een koppelingsaanbod bevat een verbindingsdescriptor met elke route naar de server (LAN, tailnet, publieke wss://, relay) plus de identiteitsvingerafdruk. De resolver van de telefoon kiest de best bereikbare route en pint de vingerafdruk vast bij de eerste verbinding (TOFU); de relay garandeert verbinding zelfs wanneer er geen directe route bestaat, zonder ooit het verkeer te kunnen lezen.

De koppeling wordt vanuit de app aangemaakt en als deeplink afgeleverd; zie Een apparaat koppelen voor de procedure. De anatomie is hier wat telt.

https://<pwa-host>/#<payload>

De payload reist mee in het fragment van de URL, het deel na # dat browsers nooit naar de server sturen, zodat de HTTPS-host van de PWA het nooit ziet. Hij bevat alles wat de telefoon nodig heeft om de server te bereiken en te bewijzen wie hij is: de verbindingsdescriptor (elke route plus de identiteitsvingerafdruk), een nieuw aangemaakte apparaat-id, een 32-byte vooraf gedeelde sleutel en een vervalstempel. De telefoon decodeert hem, slaat lokaal een koppelingsregistratie op en verwijdert het fragment zodat het geheim de URL verlaat.

Er zijn twee payloadvormen en ze zijn niet uitwisselbaar. De telefoon-QR die het koppelingspaneel in de app aanmaakt, is de v2 PairingPayload hierboven, gericht op de Remote PWA-host. cc_server pair --client-url levert een andere, oudere v1-link ({s, i, k} — server-URL, apparaat-id, sleutel) die alleen voor de webclient bedoeld is.

Ze verschillen ook in levensduur, wat makkelijk mis te gaan is:

Aangemaakt door Verval van referenties
Koppelingspaneel in de app (pairing.mint) 30 dagen, altijd
cc_server pair op de opdrachtregel Geen — de referenties verlopen niet

Een gekoppeld apparaat is geauthenticeerd maar niet vertrouwd. Verschillende onafhankelijke controles voorkomen dat een goedgekeurde (of gelektte) koppeling volledige controle over de server wordt.

1. Lidmaatschap, niet de koppeling, is de grens

Sectie getiteld “1. Lidmaatschap, niet de koppeling, is de grens”

Een geldige koppelingssleutel bewijst welk apparaat je bent. Wat je mag lezen of wijzigen, wordt bepaald door je werkruimterol, die bij elke afzonderlijke aanroep opnieuw wordt bepaald, en door rechten per repo voor alles wat repo-inhoud blootstelt. Een apparaat met een volkomen geldige sleutel dat toebehoort aan een gebruiker die geen lid is van de genoemde werkruimte, wordt geweigerd met “Not a member of this workspace”.

De minimumrol volgt uit het soort bewerking, tenzij die zijn eigen rol aangeeft: lezen vereist guest, mutaties vereisen member, destructieve bewerkingen vereisen admin.

Naast de rolcontrole heeft een telefoonsessie minder mogelijkheden dan een desktop- of websessie. Bewerkingen die als alleen-voor-volledige-clients zijn aangemerkt, worden voor een telefoon geweigerd voordat de handler draait, ongeacht hoe bevoorrecht de gebruiker erachter is: het aanmaken, opvragen, hernoemen en intrekken van apparaatkoppelingen; het lezen en instellen van tunnelverbindingen; serverback-up en export/import van werkruimtes; procesdetectie en het doden van processen; adaptertests; serverinstellingen; SSO-beheer; fleet-beheer; en het overnemen of teruggeven van een draaiende agent.

Specifiek voor het MCP-tooloppervlak (tools/call) bepaalt een allow-list die standaard weigert welke tools de telefoon mag aanroepen. Alles wat niet op de lijst staat, wordt geweigerd voordat het draait. De lijst leunt zwaar op lezen en observeren, plus een handvol alleen lokale schrijfacties — een bericht, een ticketupdate, een artikelmarkering — die geen LLM-budget besteden, geen proces starten en geen extern systeem aanraken.

Geweigerd is bijvoorbeeld: een agent raadplegen, een AI-review starten, een agent doden, een review naar GitHub publiceren, agenten aannemen of ontslaan, een werkruimte aanmaken.

De server heeft geen “huidige werkruimte”. Hij is bewust stateless: elke aanroep met werkruimtebereik moet zijn doel-workspace_id in zijn argumenten noemen, en de dispatcher weigert de aanroep direct wanneer die ontbreekt of een werkruimte noemt die de server niet geregistreerd heeft — voordat er iets een database opent. Een telefoon kan dus nooit het bereik van een andere client overnemen of daarin afdrijven, en een vreemde werkruimte noemen helpt ook niet, want de lidmaatschapscontrole in controle 1 geldt nog steeds. Dit is dezelfde werkruimte-isolatie-invariant die de rest van het product afdwingt.

5. End-to-end verzegelde frames met de vooraf gedeelde sleutel

Sectie getiteld “5. End-to-end verzegelde frames met de vooraf gedeelde sleutel”

Elk frame tussen telefoon en server wordt verzegeld met de koppelingssleutel; het toegangstoken krijgt een client alleen de ruimte in, het ontsleutelt niets. De broker geeft ciphertext door en ziet de sleutel nooit. Een apparaat dat wordt ingetrokken, ontdekt dat zijn referenties al weg zijn, dus het faalt gesloten bij opnieuw verbinden.

Een referentie die de app aanmaakt, is tijdgebonden aan 30 dagen; daarna laat de verbindingscontrole hem mislukken en moet de telefoon opnieuw koppelen. Intrekken gebeurt live: de server houdt de apparaattabel in de gaten en verbreekt de open sessies van een ingetrokken apparaat binnen enkele seconden in plaats van te wachten op de volgende verbinding.

Een limiter met een schuivend venster beperkt het verkeer van remote tools tot 120 aanroepen per minuut, met een strakkere sublimiet van 30 per minuut op de muterende werkwoorden. Het budget hoort bij de principal, niet bij de sessie: drie van je apparaten putten uit één tegoed, dus een gekaapte client kan geen extra ruimte kopen door meer sessies te openen en één luidruchtig lid kan de anderen niet uithongeren.

De relay-broker is bewust dom: hij host ruimtes, stuurt verzegelde frames door en houdt geen accounts, geen app-gegevens en geen sleutels bij. Ruimtes zijn uitnodigingsgebonden en tijdelijk. De app-gegevens, audio, het transcript, de broncode en de referenties blijven allemaal op de server.

Eenmaal gekoppeld ontvangt de telefoon live-updates als notificatieframes — nieuwe agentberichten, tickets die aan jou zijn toegewezen, statuswijzigingen — die de eigen meldingen van de desktop spiegelen. De server zendt die frames naar elke verbonden sessie uit en het werkruimtefilter wordt aan de clientkant toegepast, dus wat je ziet is beperkt tot de werkruimte waar je naar kijkt.

Oppervlak Wat het toont
Instellingen → Jij → Je apparaten (/workspaces/:workspaceId/settings/you/devices) Gekoppelde clients met live-status, plus Een nieuwe client koppelen (webbrowser, desktopapp of telefoon) en intrekken

Hetzelfde paneel maakt koppelingen voor een tweede browser of desktopapp, niet alleen voor telefoons — de flow is identiek, alleen de deeplink verschilt.