Stații
Doar fapte. Pentru modelul din spatele lor, citește Stații și medii izolate; ca să deschizi o mașină, urmează ghidul.
Suprafețe și backend-uri
Secțiune intitulată „Suprafețe și backend-uri”| Suprafață | Conduce | Backend | Valoare | Ieșire izolată |
|---|---|---|---|---|
| Terminal (exec) | Un shell peste canalul exec al VM-ului | microVM | smolvm |
Da |
| Browser | Chromium headless (CDP), Firefox (WebDriver BiDi) sau WebKit (W3C WebDriver) | microVM | smolvm |
Da |
| Computer | Desktop X11: intrare prin hipervizor, captură prin agentul din invitat | QEMU | qemu-hvf (macOS), qemu-kvm (Linux), qemu-tcg (variantă de rezervă neaccelerată, niciodată aleasă automat) |
Da |
| Android | Android prin ADB | emulatorul Google | android-emulator |
Nu — emulatorul își gestionează singur rețeaua |
| iOS | Simulator iOS prin WebDriverAgent | CoreSimulator | ios-simulator |
Nu — rețeaua urmează Mac-ul gazdă |
Terminalul și suprafața computer_use împart valoarea de suprafață
computer; terminalul se distinge prin markerul imaginii lui de exec și nu
este niciodată refolosit ca desktop condus (sau invers). O conversație
refolosește o singură mașină de terminal.
Motoare de browser
Secțiune intitulată „Motoare de browser”| Motor | Valoare | Protocol | Vizualizare live | Imagini statice |
|---|---|---|---|---|
| Chromium | chromium (implicit) |
Chrome DevTools Protocol | Screencast — browserul împinge cadre pe măsură ce pagina se redesenează | JPEG |
| Firefox | firefox |
WebDriver BiDi | Imagini statice la interval (BiDi nu are screencast), limitate la 6 fps | JPEG |
| WebKit | webkit |
W3C WebDriver (clasic) | Imagini statice la interval, transcodate de ffmpeg-ul gazdei | PNG |
O stație rulează un singur motor toată viața ei. O conversație poate ține o stație per motor, ceea ce face posibilă comparația alăturată: deschiderea „Firefox (VM)” lângă „Chromium (VM)” înseamnă două mașini, iar închiderea uneia o lasă pe cealaltă să ruleze.
Pasează engine la rig.open sau la instrumentul browser_use. Un nume
necunoscut este refuzat, nu înlocuit cu valoarea implicită — a cere un browser
și a primi în tăcere altul răspunde greșit la o întrebare de compatibilitate.
Limitele specifice fiecărui motor, declarate în loc să fie ocolite:
- Firefox nu are reîncărcare care ocolește cache-ul (respinge direct
ignoreCachedin BiDi), așa că o reîncărcare forțată este o reîncărcare normală. - WebKit nici nu are reîncărcare care ocolește cache-ul, nici flux de
consolă (ieșirea este capturată prin interceptarea
console.*după fiecare navigare, așa că orice se înregistrează în timpul încărcării este pierdut — fiecare extragere din consolă spune asta) și are nevoie deffmpegpe gazdă pentru vizualizarea live. - Niciunul nu expune un arbore de accesibilitate de platformă prin
protocolul său, așa că
extract (a11y)returnează o aproximare derivată din DOM, etichetată ca atare. - Niciunul nu poate primi o depunere reală de fișiere; un câmp de fișier poate fi totuși îndreptat spre căi din interiorul mașinii.
Imagini de mașină
Secțiune intitulată „Imagini de mașină”| Suprafață | Imagine | Adusă |
|---|---|---|
| Terminal | ubuntu:24.04, fixată prin digest |
Automat la prima pornire, prin rețeaua restricționată proprie a mașinii |
| Browser (Chromium, Firefox, WebKit) | debian:trixie-slim, fixată prin digest, plus pachetele proprii ale motorului |
Automat la prima pornire, apoi pusă în cache ca pachet preîncălzit |
| Computer | O imagine de desktop qcow2 cu Chromium, Firefox, WebKit și același set de bază CLI de tip runner găzduit de GitHub pe care îl încălzește terminalul | Importă în Setări → Server → Stații (descărcări fixate prin sumă de control, acolo unde sunt publicate) |
| Android | Imaginile de sistem Google | Prin Android SDK |
| iOS | Un runtime Xcode Simulator plus WebDriverAgent fixat prin sumă de control | Runtime prin Xcode; puntea de automatizare din Setări → Server → Stații |
După prima pornire a fiecărui tip de microVM, serverul pune în cache o copie
preextrasă în directorul de date și pornește de acolo fiecare mașină
ulterioară — inclusiv imagini preîncălzite cu pachetele deja instalate.
Terminalul încălzește un set de instrumente CLI de tip runner găzduit de
GitHub (git, gcc, python3, jq, rsync, instrumente de arhivare și
restul bazei apt; nu Docker sau toolcache-uri de limbaje). Imaginea qcow2 de
Computer include aceleași nume de CLI încă de la construirea imaginii, lângă
XFCE și cele trei browsere. Fiecare browser își încălzește motorul. Toate cele
trei motoare împart o bază Debian, dar nu împart niciodată un pachet: cache-ul
este indexat după imagine, motor și o revizie audio (audio-v1-<engine>),
pentru că o mașină încălzită pentru un motor — sau înainte ca PulseAudio și
ffmpeg să facă parte din sarcină — nu conține nimic din celălalt. Pachetul de
terminal este indexat după o revizie a setului de instrumente (tools-v1)
din același motiv: un pachet încălzit înainte ca compilatorul să facă parte
din imagine nu trebuie să deservească o mașină care ar trebui să aibă unul.
Nu există nicio imagine de automatizare preconstruită care merită folosită —
chromedp/headless-shell de la Chromium compilează atât ALSA, cât și
PulseAudio în afară, așa că nu poate emite niciodată sunet; singura pentru
Firefox întreținută are de 33× dimensiunea bazei Debian și tot nu include
socat; pentru WebKit nu există niciuna întreținută. Așa că fiecare motor se
instalează la prima pornire (chromium, firefox-esr sau
webkit2gtk-driver) și ajunge la arhivele Debian (deb.debian.org,
security.debian.org, cloudfront.debian.net) în timp ce face asta.
Chromium este binarul distribuției lansat ca chromium --headless=new.
Imagini personalizate per spațiu de lucru
Secțiune intitulată „Imagini personalizate per spațiu de lucru”| Cheie de setare | Suprascrie |
|---|---|
rigs.smolvm.execImage |
Imaginea de Terminal (VM) |
rigs.smolvm.browserImage |
Imaginea de Browser Chromium (VM) |
Scrise prin depozitul de setări al spațiului de lucru, protejat de administrator (Setări → Server → Stații → Imagini personalizate (acest spațiu de lucru)). Valoarea trebuie să fie o referință de registry — căile locale, arhivele și șirurile de tip flag sunt refuzate, atât la câmp, cât și din nou la pornire. O valoare stocată invalidă este ignorată cu o eroare vizibilă, iar în locul ei pornește valoarea implicită. Registry-ul propriu al imaginii (plus însoțitorii cunoscuți de blob-CDN pentru Docker Hub, GHCR, Quay, GitLab și GCR) este admis prin poarta de ieșire pentru descărcare.
Contractul imaginii de terminal — bash prezent; git, curl, socat
preinstalate sau o bază apt Debian/Ubuntu (mediul izolat instalează acele
trei plus un set de instrumente CLI de tip runner găzduit de GitHub la prima
pornire: compilatoare, Python 3, jq, instrumente de arhivare, rsync.
Toolcache-urile de limbaje și Docker nu sunt instalate).
Contractul imaginii de browser — o bază apt Debian/Ubuntu (sau pachetele
deja instalate) cu bash și socat, plus un binar chromium pe care
serverul îl lansează singur ca chromium --headless=new, cu DevTools pe
loopback-ul invitatului 9223, retransmis către portul 9222 al invitatului.
PulseAudio și ffmpeg sunt instalate la prima pornire pentru canalul audio. Nu
construi FROM chromedp/headless-shell: headless.gn din amonte compilează
atât ALSA, cât și PulseAudio în afară, așa că acel binar nu poate emite sunet.
Suprascrierea se aplică doar stațiilor Chromium — comanda de sarcină pe
care o pornește este a Chromium, iar darea ei către Firefox sau WebKit ar
lansa un binar care nu există. O stație Firefox sau WebKit o ignoră cu o
eroare vizibilă și pornește imaginea ei fixată.
Porturi de automatizare — fiecare mașină de browser redirecționează exact
un port loopback al gazdei către 9222 al invitatului. În spatele lui:
DevTools al Chromium și agentul remote al Firefox stau pe loopback-ul
invitatului 9223, cu un releu socat în față (ambele se leagă
necondiționat la loopback); driverul WebKit primește un --host și se leagă
el însuși la placa de rețea a invitatului. Firefox validează în plus antetul
Host față de propriul port, așa că clientul trimite autoritatea din partea
invitatului, nu pe cea către care a format conexiunea.
Verbe de acțiune
Secțiune intitulată „Verbe de acțiune”Fiecare acțiune este analizată total: un payload malformat returnează un mesaj care numește câmpul, niciodată o excepție. Verbele de observare (marcate ○) rămân permise cât timp controlul este la un om; verbele care modifică starea sunt refuzate.
computer_use
Secțiune intitulată „computer_use”screenshot○, cursor_position○, mouse_move, left_click,
right_click, middle_click, double_click, triple_click,
left_mouse_down, left_mouse_up, left_click_drag, scroll, key,
hold_key (0–60 s), type, wait○ (0–60 s), set_display,
clipboard_read○, clipboard_write.
Coordonatele sunt pixeli din invitat în modul curent al invitatului; fiecare
captură de ecran indică dimensiunea afișajului. Combinațiile de taste folosesc
grafia X11 (ctrl+s, alt+Tab, Return).
clipboard_read primește un selection: clipboard (ce completează ctrl+C,
valoarea implicită), primary (selectare-pentru-copiere în X11) sau xdnd (o
tragere în curs). Un rezultat gol este un răspuns normal pentru toate trei.
clipboard_write primește doar text — imaginile și fișierele călătoresc pe
canalul de fișiere, pentru că argumentele unei acțiuni sunt persistate în
jurnalul de audit și octeților de imagine nu le este locul acolo. Conținutul
clipboardului se întoarce delimitat ca date nesigure.
browser_use
Secțiune intitulată „browser_use”navigate (doar http/https), reload (hard opțional), stop_loading,
click (selector sau coordonată), fill, type, key (nume de taste DOM),
scroll, mouse_move, drag, left_mouse_down, left_mouse_up,
extract○ (a11y | dom | console), screenshot○ (full_page
opțional), set_viewport (minim 320×240), history, wait_for○ (selector,
0,1–30 s), clipboard_read○, clipboard_write.
Conținutul extras al paginii se întoarce delimitat ca date nesigure.
Browserul are exact un clipboard, așa că clipboard_read nu primește
selection. Ambele verbe de clipboard au nevoie de un context securizat:
o pagină servită prin http:// simplu nu are deloc API de clipboard, iar
verbul spune asta în loc să raporteze unul gol — folosește acolo type, care
inserează la cursor fără să treacă prin clipboard.
mobile_use
Secțiune intitulată „mobile_use”tap, swipe (50–5000 ms), type, key (aliasuri back, home,
recents, enter, delete, backspace, tab, escape, search,
menu, volume_up, volume_down, power sau orice KEYCODE_*),
screenshot○, ui_dump○, install_apk
(limitat la worktree-ul stației și la directorul de date al serverului),
start_app, stop_app, clear_app_data, uninstall_app, open_url și
shell. shell primește o listă argv și rulează în interiorul Android,
niciodată printr-un shell al gazdei; acoperă diagnostice de dezvoltator
precum logcat, dumpsys și pm grant.
mobile_use nu are verbe de clipboard. Începând cu Android 10, doar
aplicația aflată în prim-plan poate citi clipboardul.
tap, swipe (50–5000 ms), type, key (enter, backspace, tab,
escape, tastele-săgeată sau un singur caracter imprimabil; modificatori
opționali command, control, option și shift), home, lock,
unlock, screenshot○, ui_dump○, install_app (un director .app de
simulator limitat la worktree-ul stației sau la directorul de date al
serverului), start_app, stop_app, uninstall_app, open_url și spawn.
spawn primește argv și execută în interiorul simulatorului prin simctl;
nu invocă niciodată un shell al gazdei.
Coordonatele sunt puncte de ecran iOS, nu pixeli de captură de ecran. Fluxul live păstrează dimensiunea fixă a simulatorului și îl încadrează cu benzi negre în loc să redimensioneze dispozitivul. Transferul de text prin clipboard gazdă-simulator și simulator-gazdă folosește aceeași politică de permisiuni per utilizator și per direcție ca alte stații; fișierele și imaginile nu sunt acceptate de canalul pasteboard al iOS.
Fiecare stație creează un dispozitiv CoreSimulator propriu și o sesiune WebDriverAgent. Închiderea ei oprește WebDriverAgent, închide și șterge acel dispozitiv și elimină intrarea lui din registru. Stațiile iOS rulează doar pe un server macOS cu Xcode, un runtime iOS instalat și puntea de automatizare fixată. Ele nu oferă nicio garanție pentru audio, microfon, depunere de fișiere, shell al gazdei sau rețea izolată.
Instrumente de gestionare
Secțiune intitulată „Instrumente de gestionare”| Instrument | Descriere |
|---|---|
rig_list |
Listează stațiile unui spațiu de lucru cu suprafață, stare, afișaj și controlor |
rig_close |
Distruge o stație și elimină discul ei |
Ciclul de viață
Secțiune intitulată „Ciclul de viață”| Fază | Semnificație |
|---|---|
provisioning |
Pornește; detaliul de stare preia textual pasul curent |
ready |
Accesibilă și acceptă acțiuni |
parked |
Parcată pentru inactivitate: vCPU-urile oprite, RAM-ul încă rezident; următoarea acțiune o trezește |
closing |
Se închide |
closed |
Dispărută; rândul supraviețuiește pentru jurnalul de audit |
failed |
A murit sau nu a pornit niciodată; distinctă, ca interfața să poată oferi reîncercarea |
| Motiv de închidere | Declanșat când |
|---|---|
requested |
O persoană sau un agent a cerut-o |
idleTimeout |
Inactivitate peste 2× fereastra de inactivitate sau eliminare pentru memorie |
ttlExpired |
Durata de viață strictă a expirat |
conversationEnded |
Conversația proprietară s-a încheiat |
workspaceGone |
Spațiul de lucru a fost șters sau calitatea de membru s-a pierdut |
serverShutdown |
Serverul se oprește |
backendFailure |
Hipervizorul de dedesubt a murit |
Valori implicite și plafoane
Secțiune intitulată „Valori implicite și plafoane”| Valoare | Terminal | Browser | Computer | Mobile |
|---|---|---|---|---|
| Memorie | 512 MB | 2048 MB (Chromium); 2560 MB (Firefox, WebKit) | 4096 MB | 4096 MB |
| vCPU-uri | 2 | 2 | 4 | 4 |
| TTL strict | 8 h | 2 h | 2 h | 2 h |
| Fereastră de inactivitate | 45 min | 15 min | 15 min | 15 min |
| Afișaj la pornire | — | 1280×800 | 1280×800 | 1080×1920 |
- Imaginile statice de pe canalul agentului sunt limitate la 1280×800; canalul uman negociază până la 2560×1600.
- Bugetul de memorie rezidentă al gazdei pentru toate stațiile are implicit 12 GB, contabilizat în megaocteți rezidenți (o VM parcată își păstrează RAM-ul). Eliminarea se face după cel mai puțin utilizat recent și nu ia niciodată o mașină pe care cineva o conduce, o privește sau are un terminal deschis în ea.
- Un terminal deschis sau un canal de vizualizare fixează stația împotriva parcării și a recuperării din inactivitate; TTL-ul strict se aplică în continuare.
Porturi și domenii de dezvoltare
Secțiune intitulată „Porturi și domenii de dezvoltare”| Port | Unde | Rezervat pentru |
|---|---|---|
7911 |
Invitatul de terminal | Multiplexorul de porturi pe care călătorește fiecare redirecționare |
9222, 9223 |
Invitatul de browser | Releul și endpoint-ul de automatizare (WebKit se leagă direct la 9222 și nu folosește niciun releu) |
80, 443 |
Loopback-ul invitatului de browser | Canalele HTTP/HTTPS ale domeniului de dezvoltare |
- Descoperirea sondează porturile TCP în ascultare ale invitatului de terminal la fiecare ~4 s, pe partea de server.
- Cel mult 16 porturi sunt redirecționate automat per stație; mai multe sunt listate în jurnal, nu redirecționate.
- Redirecționarea din partea gazdei se leagă la loopback, preferând numărul de port propriu al invitatului; Partajează în rețeaua locală adaugă un port LAN atribuit de sistemul de operare.
- Domeniile de dezvoltare trebuie să corespundă cu
name.testsauname.localhost(litere mici, etichetă de maximum 63 de caractere). Un domeniu corespunde unui port. - HTTPS-ul pentru domeniul de dezvoltare folosește o autoritate de
certificare locală serverului (EC P-256, în
<dataDir>/rigs/tls/, chei 0600 și niciodată într-un invitat) cu un singur certificat final wildcard pentru*.test/*.localhost. Fărăopensslpe gazdă nu există canal HTTPS; HTTP simplu rutează în continuare.
Valori implicite de ieșire
Secțiune intitulată „Valori implicite de ieșire”| Suprafață | Listă de permisiuni implicită |
|---|---|
| Terminal | Baza de referință pentru dezvoltatori a sandboxului (platforme de găzduire a codului, registre de pachete, furnizori de modele) + mirror-urile apt Ubuntu + gazda de găzduire a codului proprie worktree-ului |
| Browser | usectrl.dev (site-ul produsului) — plus arhivele Debian la prima pornire a fiecărui motor, în timp ce își instalează pachetele. Pagina de pornire este un document local file:// și nu are nevoie de ieșire |
| Computer | Goală — refuză tot — până când un apelant acordă gazde |
| Mobile | Neaplicată (rețea gestionată de emulator) |
Fiecare microVM primește în plus gazdele de registry ale imaginii sale, doar pentru descărcare. Setul de gazde permise al brokerului de credențiale este derivat din aceeași listă de permisiuni: nu se emite niciun token pentru o gazdă pe care invitatul nu o poate atinge.
Clipboard și transfer de fișiere
Secțiune intitulată „Clipboard și transfer de fișiere”Ambele direcții de copiere/lipire și tragerea fișierelor înăuntru și în afară. Disponibile unei persoane care conduce o stație; un agent primește clipboardul prin verbele de mai sus și niciun transfer de fișiere (are deja un shell în interiorul mediului izolat).
Trecerea clipboardului este per utilizator și per direcție. Lipirea din gazdă în stație este permisă implicit; copierea din stație în gazdă cere confirmare (o autorizare de zece minute sau permisiune permanentă în Setări → Server → Stații → Acces la clipboard). Cele două alegeri sunt independente și nu sunt niciodată o setare de spațiu de lucru.
| Canal | Rută | Transportă |
|---|---|---|
| Clipboard | GET/POST /rig/clipboard/<rigId> |
Text, un PNG/JPEG (≤16 MB) și căile din invitat ale fișierelor aflate în clipboard |
| Fișiere | POST /rig/files/<rigId> |
Până la 64 de fișiere, ≤256 MB fiecare și ≤512 MB per depunere, cu un punct de depunere opțional în pixeli din invitat |
| Fișiere | GET /rig/files/<rigId>?p=<base64url path> |
Octeții unui fișier, înapoi afară |
- HTTP, nu RPC. Socketul RPC închide o conexiune la un cadru primit mai mare de 256 KB — o protecție anti-DoS potrivită pentru comenzile JSON și fatală pentru o captură de ecran lipită. Octeții călătoresc pe același tip de rută pe care o fac deja cadrele stației și audio-ul ședințelor.
- Autentificarea este ținta semnată
rig-files:<workspaceId>/<rigId>plus calitatea de membru al spațiului de lucru — o țintă proprie, așa că un URL emis pentru a privi o mașină nu poate scrie și fișiere în ea. - Preluarea controlului se aplică. Scrierea în clipboard și depunerea de fișiere modifică starea și sunt refuzate cât timp altcineva deține controlul; citirea clipboardului și extragerea unui fișier sunt observare și nu sunt refuzate.
- Unde ajung depunerile:
~/Drops(desktop),~/drops(terminal — lângă worktree, niciodată în el, așa că o depunere nu apare niciodată ca o modificare neurmărită),/tmp/cc-drops(browser). Serverul alege directorul; un apelant nu îl poate numi. Fișierele existente nu sunt niciodată suprascrise — un nume care se ciocnește ajunge ca2-name.ext. - Ce i se spune invitatului: o pagină de browser primește un eveniment de
depunere real în punctul cerut (
Input.dispatchDragEvent); un desktop primește fișierele plus URI-urile lor în clipboard, pentru că nicio gazdă nu poate sintetiza o tragere XDND într-un toolkit arbitrar fără un demon privilegiat din interiorul invitatului. Răspunsul spune care dintre cele două s-a întâmplat. - Tragerea în afară este doar pentru desktop și citește
XdndSelectional invitatului cât timp o tragere este activă. Un payload identic cu ceea ce ținea deja invitatul când a început apăsarea este tratat ca o selecție rămasă de la o tragere încheiată, nu ca una nouă — X nu are un pas de „tragerea s-a terminat”. - Cerințe de imagine: imaginea de desktop trebuie să fie protocol 2 sau mai nou (construită după ce a apărut suportul de clipboard). O imagine mai veche răspunde cu 404, iar aplicația spune să o reconstruiești, în loc să raporteze o mașină defectă.
- Jurnalul de audit consemnează forma, niciodată conținutul:
clipboard_read,clipboard_write,drop_filesșiread_filesunt rânduri care conțin numărători, dimensiuni și căi. Clipboardul este locul în care trăiesc credențialele câteva secunde o dată, iar nimic din el nu este scris.
Guardrail-uri, evenimente, stocare
Secțiune intitulată „Guardrail-uri, evenimente, stocare”- Instrumentele de stație declară clasa de acțiuni
enclosureControl(plusnetworkEgressșiprocessSpawnla instrumentele de utilizare;mobile_useșiios_usedeclară șipackageInstall).rig_closedeclarăenclosureControl;rig_listnu declară niciuna. Permise implicit; modurile numai-citire o refuză în bloc; cadranul de autonomie per spațiu o poate retrograda la „întreabă întâi”. - Evenimente de domeniu:
RigClosedEvent(cu motivul lui de închidere),RigControlChanged(preluare / predare a controlului),RigReaped(eliminare din inactivitate, TTL sau memorie, cu agentul care conducea). Deschiderea unei stații nu este un eveniment. Vezi evenimentele de domeniu. - Stocare:
rig_sessionsșirig_action_logîn baza de date a fiecărui spațiu de lucru. Jurnalul de acțiuni consemnează fiecare eveniment de intrare cu principalul lui și o secvență monotonă per stație; textul tastat este redactat la o lungime și un hash înainte de a fi scris. - Directorul de date:
rigs/images/(imaginile qcow2 importate),rigs/smolvm/(starea de rulare per stație),rigs/smolvm-packs/(pachetele de mașină preextrase),rigs/tls/(autoritatea de certificare a domeniului de dezvoltare),rigs/run/(straturile suprapuse și semințele QEMU). - Rute: vizualizarea live este o filă într-o pagină de spațiu sau de PR;
/settings/server/rigseste ecranul de setări Stații.