Sari la conținut

Stații

Doar fapte. Pentru modelul din spatele lor, citește Stații și medii izolate; ca să deschizi o mașină, urmează ghidul.

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.

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 ignoreCache din 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 de ffmpeg pe 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.
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.

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.

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.

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.

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.

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ă.

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
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
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.
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.test sau name.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ă openssl pe gazdă nu există canal HTTPS; HTTP simplu rutează în continuare.
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.

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 ca 2-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 XdndSelection al 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 și read_file sunt 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.
  • Instrumentele de stație declară clasa de acțiuni enclosureControl (plus networkEgress și processSpawn la instrumentele de utilizare; mobile_use și ios_use declară și packageInstall). rig_close declară enclosureControl; rig_list nu 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 și rig_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/rigs este ecranul de setări Stații.