agent-desktop, auditado: 16.842 estrellas, 42 watchers y 44 comandos que responden PERM_DENIED donde no hay ningún permiso que conceder

agent-desktop suma 16.842 estrellas y 42 watchers. En un Linux sin sesión gráfica, 44 de sus 60 comandos devolvieron PERM_DENIED por un bus D-Bus ausente.

Vercel Labs tiene la costumbre de publicar herramientas que parecen demos y acaban siendo infraestructura. agent-desktop es esa costumbre aplicada a un problema genuinamente difícil: que un agente de IA maneje una aplicación de escritorio sin capturas de pantalla y sin comparar píxeles.

La propuesta cabe en un párrafo, y es el párrafo correcto. El agente lee el árbol de accesibilidad del sistema operativo, recibe JSON, pulsa un ref, vuelve a leer la interfaz. No hay modelo dentro, no hay modelo de visión en el bucle, no hay coordenadas adivinadas a partir de una imagen. El repositorio tiene 16.842 estrellas, se creó el 19 de febrero de 2026 y publicó la versión 1.2.0 el 5 de noviembre.

Luego está el número que el README no enseña. subscribers_count —los watchers de GitHub, la gente que pidió que le avisaran— es 42. Un 0,25 % de las estrellas. En esta serie de auditorías he medido entre un 0,22 % y un 0,94 % en proyectos comparables; 0,25 % está en el suelo de esa banda. La estrella es un marcador. El watcher es intención.

Esto no es un ataque a una buena idea. Es lo que encontré al clonar el repositorio, leer los archivos que nadie enlaza, instalar el paquete npm en un Debian 13 sin entorno gráfico y medir qué hace de verdad la herramienta cuando falta el entorno que da por supuesto.

Qué es, en números

Un workspace de Rust con cinco crates más el crate del binario, compilados por plataforma y nunca enlazados entre sí:

CrateArchivos .rsLíneasArchivos con test en el nombreLíneas de test
crates/linux1.027124.06045364.915
crates/windows43879.05421739.302
crates/core67788.00929353.073
crates/macos29745.118789.839
crates/ffi19115.196101.393
src (CLI, MCP, batch, política)10314.379——
probes (código de medición)448.826——
Total2.784374.993—168.522

Hay dos cosas que el diagrama de arquitectura del README no transmite.

El crate de Linux es 2,75 veces el de macOS. 124.060 líneas frente a 45.118. Vercel Labs es una casa de macOS; cada captura de la documentación es de un Mac. La plataforma que menos usan internamente es la que más código tiene, y eso puede ser rigor admirable o un aviso sobre cuánto comportamiento de Linux hay que tratar como caso especial.

El 45 % del Rust está en archivos de test. 168.522 de 374.993 líneas. El reparto no es uniforme: Linux es 52,3 % líneas de test, core un 60,3 %, Windows un 49,7 % y macOS —insisto, la plataforma en la que trabaja el equipo— un 21,8 %. Si la densidad de tests indica dónde vive la incertidumbre de quien escribe, el orden es Linux, core, Windows, macOS.

A eso se suma un cuerpo de documentación: 77 archivos Markdown con 1.701.165 bytes, de los cuales 289.981 son las seis guías SKILL.md que van compiladas dentro del binario.

El archivo más interesante del repositorio es un registro de evidencias

Casi todos los proyectos esconden su incertidumbre en los issues. Este la publica como tabla.

probes/linux/FINDINGS.md tiene 1.054 líneas. Cada fila es un experimento con columnas para la prueba, la pila cliente (dbus, gi-atspi, tool), el alcance (api-contract, app, box), la frase de la especificación interna que se estaba probando, los valores capturados, un veredicto y la acción que obliga el hallazgo. El recuento de veredictos del archivo:

VeredictoFilasSignificado
NEW-EDGE388Comportamiento que nadie había documentado
CONFIRMS221La especificación acertaba
CONTRADICTS93La especificación se equivocaba y el código tuvo que cambiar
DEFERRED23Sin medir todavía, con un cierre con nombre

Noventa y tres filas donde el documento de diseño del propio proyecto perdió una discusión contra el sistema operativo. La regla que abre el archivo es la que enmarcaría: cuando una fila de D-Bus crudo y una de libatspi discrepan, gana la de D-Bus crudo, porque el adaptador en Rust habla D-Bus crudo.

Los números de dentro solo se obtienen ejecutando cosas:

  • Leer un árbol cuesta unos 2,1 ms por nodo con 9 llamadas D-Bus por nodo. GNOME Files, 201 nodos: 434,8 ms de mediana. VS Code, 252 nodos: 431,8 ms. Un árbol de 2.000 nodos cuesta unos 4 segundos.
  • La concurrencia compra 2,5–3,5×, no 10×. Files baja de 434,8 ms a 123,4 ms con 128 llamadas en vuelo; 16 en vuelo está a un 5–15 % de 128.
  • Properties.GetAll gana a cinco Get individuales por 3,3×: 189,76 ms frente a 57,62 ms sobre las mismas 201 referencias. La especificación afirmaba que AT-SPI no ofrecía lectura por lotes; la medición dice que sí, y el código se reescribió alrededor de esa frase.
  • GTK4 tiene una caché 7× más rápida, y GTK3, Chromium y Electron no. Cache.GetItems devuelve 208 elementos en 16,68 ms donde el recorrido concurrente necesita 118,7 ms. La Terminal de GTK3 y el VS Code de Chromium responden org.freedesktop.DBus.Error.UnknownMethod a esa misma llamada. La vía rápida es una detección de toolkit, no una función de la plataforma.

Y luego están las filas que importan si vas a construir encima:

FilaQué se midióPor qué importa
L4-13El length de EditableText.InsertText cuenta bytes UTF-8, no caracteres. Dos emoji con length 2 no insertan nada y devuelven trueUna llamada exitosa que no escribió nada. Relee o pierdes la escritura
L3-24VS Code: 404 nodos en un arranque y 270 en el siguiente con la misma regla de asentamiento; 255 de 270 claves (ruta, rol) compartidasEl árbol de Electron no se reproduce entre reinicios, así que la identidad por ruta no puede cruzar un proceso
L3-19En GTK3, GTK4, Qt 6, Chromium y Gecko, ninguno de expand, collapse, popup ni cancel aparece como nombre de acciónLa tabla de verbos documentada estaba mal; el adaptador lee GetName(i) y elige
L9-10En GNOME X11 con composición, una captura de la raíz no distingue una ventana mapeada de una desmapeada (root_mean_abs_diff: 0.0)La captura de pantalla completa no puede usar XGetImage sobre la raíz
L1-13Una app GTK4 arrancada antes del bus de accesibilidad nunca se registra: 12 s, ~1.155 sondeos, 0 hijos en el registroLa snapshot no la verá hasta reiniciarla. «No hay apps» no es «no hay bus»
L10-14El subárbol de gnome-shell tiene 2.006 nodos, 1.115 de ellos panel, y exactamente un nodo que declara una acciónLa vía de «leer el shell como una app» es casi toda estructura sin verbos

El documento más valioso del repositorio es más corto y más incómodo: docs/solutions/runtime-errors/an-atspi-write-that-returns-success-has-not-changed-anything.md. Es un catálogo, medido sobre GTK3, GTK4, Qt 6, Firefox y Chromium en vivo, de escrituras que responden true y no cambian nada (un widget GTK3 destruido; cualquier input de Gecko; un campo Qt de solo lectura, porque Qt no aplica READ_ONLY en esa ruta) y de escrituras que responden error y aterrizan igual (una aplicación detenida contesta NoReply y la llamada encolada se entrega en el SIGCONT).

Ahí está también la tesis real del producto: una respuesta prueba, como mucho, la entrega a un objeto vivo, y ni eso en GTK3. Todo lo demás —la relectura después de cada escritura, la disposición delivered_unverified, el cambio 2.5 que borró todos los indicadores ok: true incondicionales— se deduce de esa frase.

Es el caso poco habitual de un proyecto cuya autodocumentación vale más que su marketing. Si te llevas una sola cosa de este artículo, llévate el registro.

Qué pasa en una máquina sin escritorio

La tabla de plataformas del README dice que Linux necesita Ubuntu 24.04 o posterior, x86_64 o aarch64, X11 o Wayland. Yo lo ejecuté en Debian 13, sin servidor gráfico y sin bus de sesión D-Bus: el entorno en el que viven los runners de CI, los contenedores y la mayoría de los sandboxes de agentes.

Empecemos por la instalación, porque ahí npm 12 cambió las reglas:

$ npm install -g agent-desktop
added 1 package in 1s
npm warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts   agent-desktop@1.2.0 (postinstall: node scripts/postinstall.js)

El paquete trae 23 archivos en bin/ —seis binarios de plataforma de dos crates más sus sumas de verificación— para una instalación de 30 MB, de la que necesitas uno de 7,1 MB. Con el postinstall bloqueado, cada comando pasa por el lanzador de Node, que avisa una vez y declara el coste:

this command runs through the Node launcher because npm did not run the install script, which adds about 125 ms per call.

Lo medí, diez ejecuciones por vía, el mismo comando:

VíaMedianaMínimoMáximo
Lanzador de Node (scripts bloqueados)44,7 ms40,4 ms53,9 ms
Binario nativo (scripts permitidos)3,2 ms3,0 ms3,3 ms

El sobrecoste del lanzador es de 41,5 ms, no 125 ms. Siendo justos: puede que se refieran a un comando más pesado que version, donde el coste del lanzador sería el mismo 41,5 ms por encima. En cualquier caso, el número que un harness de agentes debe presupuestar es el medido.

Después viene el entorno:

{"version":"2.6","ok":true,"command":"status","data":{
  "permissions":{"accessibility":{"state":"denied","suggestion":"No D-Bus session bus is reachable. Run inside a desktop session, or start one with 'dbus-run-session -- <command>'."}},
  "platform":"linux","supported_surfaces":[],"state_root":"/opt/data/home/.agent-desktop","version":"1.2.0"}}

ok: true, código de salida 0 y supported_surfaces: []. La guía de Linux sí lo documenta: un supported_surfaces vacío significa que el bus de accesibilidad no respondió, no que el escritorio esté vacío. Hay que reconocerlo: un agente que lee la guía no se deja engañar. Un agente que solo lee códigos de salida ve un éxito.

Y aquí llega la parte por la que escribí este artículo. Todo comando que necesita accesibilidad pasa por una comprobación previa antes de tocar el adaptador, y esa comprobación informa mal del motivo.

$ agent-desktop snapshot
{"ok":false,"command":"snapshot","error":{"code":"PERM_DENIED",
 "message":"Accessibility permission not granted",
 "suggestion":"No D-Bus session bus is reachable. ...",
 "disposition":{"delivery":"not_delivered","retry":"safe"}}}

$ agent-desktop list-apps
{"ok":false,"command":"list-apps","error":{"code":"PLATFORM_NOT_SUPPORTED",
 "message":"The AT-SPI accessibility bus is not available in this session",
 "platform_detail":"Failed to connect to address `unix:path=/tmp/hermes-runtime/bus`: No such file or directory (os error 2)",
 "details":{"retryable":false},
 "disposition":{"delivery":"not_delivered","retry":"safe"}}}

La misma máquina, el mismo bus ausente, dos códigos distintos —y el mensaje del primero es falso en Linux, donde no existe ningún permiso de accesibilidad que conceder. El mecanismo está en el código y no es un fallo de un comando concreto:

  • crates/linux/src/system/bus/bus_fault.rs convierte cualquier fallo de bus —sin bus de sesión, servicio desconocido, arranque fallido, socket muerto— en un único ErrorCode::PlatformNotSupported con details.retryable: false.
  • src/command_policy/mod.rs ejecuta una comprobación previa al despacho. Clasifica 44 de los 60 nombres de comando como necesitados de PermissionNeed::Accessibility, y si el informe de permisos dice denied, devuelve ErrorCode::PermDenied con la sugerencia del informe, antes de llamar al adaptador.

Es decir: el mismo hecho —este proceso no alcanza un bus de accesibilidad— lo reportan como PLATFORM_NOT_SUPPORTED los catorce comandos sin puerta (list-apps, list-windows, clipboard-*, status, permissions…) y como PERM_DENIED los cuarenta y cuatro con puerta. Confirmé el reparto en snapshot, find, key-down, list-apps, list-windows, clipboard-get y screenshot.

El registro del propio proyecto dice cuál de los dos pretendía. La fila L1-7 prueba el caso de bus ausente contra la frase de la especificación «If the bus is not running, return PLATFORM_NOT_SUPPORTED» y anota la acción: mapear ServiceUnknown y Spawn.ExecFailed a PLATFORM_NOT_SUPPORTED con platform_detail distinto, y observar que ambos fallan en menos de 2 ms, así que no hace falta presupuesto de timeout. La vía sin puerta hace exactamente eso. La vía con puerta no.

Por qué importa más allá de la pulcritud:

  1. PERM_DENIED está documentado como «Accessibility permission not granted» en docs/json-output.md. En Linux ese permiso no existe. Un agente que ramifica por error.code —que es justo lo que el README le dice que haga— tomará la rama de remediación de macOS en un host Linux.
  2. disposition.retry es safe en todos, incluido el que lleva details.retryable: false. La documentación explica el porqué con honestidad: retry se deduce de delivery, y not_delivered es safe. No se envió nada, así que reintentar es seguro. Pero la regla del README para agentes es «reintenta un comando fallido solo cuando error.disposition.retry sea safe», y las notas de la 2.6 admiten la consecuencia en una frase: disposition.retry sigue siendo safe porque no se envió nada, pero repetir la misma referencia devuelve el mismo rechazo. Un bucle de reintentos sobre un bus ausente son 44 comandos «seguros» que nunca van a funcionar.
  3. La solución sugerida nombra un binario que Debian 13 no instala. dbus-run-session viene en dbus-x11; ni él ni dbus-daemon existen en mi máquina. La sugerencia es correcta para un Ubuntu de escritorio e inutilizable en el host que la produjo.

Probé también la vía de batch, porque un ejecutor por lotes es donde suelen fugarse las puertas de permisos. No se fuga, pero sí se detiene: una entrada snapshot dentro de un lote de tres comandos devuelve PERM_DENIED con details.batch_index y batch_command, y el lote se corta en la comprobación previa en lugar de ejecutar las entradas de alrededor. Es defendible; tampoco es lo que «bounded, sequential, non-atomic» sugiere a quien espera que el resto se ejecute.

Los cuatro comandos que no pueden ejecutarse, por diseño

El README es notablemente directo: de los 60 nombres de comando, 56 son operativos y cuatro —key-down, key-up, mouse-down, mouse-up— se niegan a ejecutarse hasta que exista un daemon capaz de liberar una tecla o un botón retenidos.

El código dice lo mismo en una línea: la entrada retenida exige una transacción propiedad de un daemon que pueda garantizar la liberación. En el binario distribuido no hay daemon, así que los cuatro existen para fallar en cerrado y remitirte a press, mouse-click y drag.

Es la decisión correcta para una herramienta orientada a agentes: un agente que muere a mitad de un arrastre con el botón pulsado deja un escritorio sobre el que ningún comando posterior puede razonar. También es una decisión de producto que conviene entender antes de diseñar encima: esos cuatro nombres aparecen en --help y en la tabla de comandos, y nada en la tabla los marca como no disponibles.

Lo que la guía de Linux concede

Las guías de plataforma ocupan 7.771 bytes en macOS, 8.349 en Windows y 17.219 en Linux. La de Linux contiene la palabra «refuse» y sus variantes 17 veces. macOS: cero. Windows: cinco.

La tabla de capacidades de Linux es lo más honesto del material comercial:

ÁreaLo que dice la guía de Linux
Gestos de puntero (double-click, triple-click, hover, drag)--headed, solo X11. En Wayland rechazan con ACTION_NOT_SUPPORTED
Desplazamiento con ruedaEntrada física; X11 o --headed
focusRequiere --headed; GTK4 rechaza GrabFocus, así que termina en ACTION_FAILED grab_focus_rejected
NotificacionesSolo GNOME Shell, verificado únicamente en GNOME Shell 46.0; las mutaciones necesitan --headed en X11
Superficies del shelltaskbar rechaza siempre; start-menu y system-tray-overflow no están soportados aunque --help los liste
Superposición del cursorEn Wayland guarda el ajuste y devuelve data.rendered: false
Control de ventanasfocus-window necesita X11; el resto del control y el cierre ordenado necesitan un cliente X11
Otras distribuciones«El puente de Ubuntu 22.04 aborta en Action.GetActions»; Debian necesita el arreglo dc0dc33 de at-spi2-core

Lee esa tabla junto a la frase de plataforma del README —«Ubuntu 24.04 o posterior, x86_64 o aarch64, X11 o Wayland»— y la distancia no es deshonestidad, es compresión. El README vende la superficie; la guía avisa de que la superficie depende del toolkit, del escritorio, del servidor gráfico y del tipo de sesión.

Esa compresión tiene consecuencias en la historia de CI. El job test-linux de ci.yml instala at-spi2-core, dbus-x11 y xvfb, y luego ejecuta la suite de Linux bajo dbus-run-session -- xvfb-run. Eso es un bus de sesión y una pantalla virtual, sin gestor de ventanas y sin GNOME Shell. La suite end-to-end real de Linux es otro workflow, linux-e2e.yml, cuyo único disparador es workflow_dispatch, y corre sobre vars.LINUX_E2E_RUNNER. Las mediciones del registro las hizo una persona conduciendo sondas en un GNOME 46 real. La puerta que corre en cada push no puede reproducir ese entorno.

Lo que cuesta ser una sola persona

Diez contribuidores, 335 contribuciones. Uno de ellos, lahfir, tiene 230. Tres bots suman 83 más, así que de las 252 contribuciones humanas, el 91,3 % son de una persona. El sexto contribuidor humano tiene tres commits; cuatro humanos tienen uno.

El ritmo en 8,5 meses: 49 releases, 13 artefactos cada una —seis archivos por crate y plataforma, 369 artefactos en total— y 51 referencias de tag. Commits por mes: 81 en el mes de lanzamiento (febrero de 2026), 11, 13, 21, 34, 11, 15, luego 68 en septiembre, 62 en octubre y 19 en los cinco primeros días de noviembre.

La disciplina de versiones es donde aparece el riesgo de un solo mantenedor. Entre el 28 de octubre y el 3 de noviembre se publicaron 1.0.0, 1.0.1, 1.0.2, 1.1.0 y 1.2.0. La 1.1.0 trajo tres cambios incompatibles en la misma entrega: el major de la ABI de C pasa a 7 (y ad_resolve_element_exact más ad_execute_ref_action_exact_with_policy rechazan siempre con ACTION_NOT_SUPPORTED), el sobre de respuesta pasa a 2.6 y las referencias de snapshots anteriores dejan de resolver en Windows y Linux. El CHANGELOG.md lo cuenta todo con claridad. Si consumes la biblioteca en C, cinco versiones en una semana no es ruido que se absorba sin coste.

La demanda existe y no tiene la forma de las estrellas: 38.585 descargas npm en el mes que termina el 4 de noviembre. Si eso son pipelines de CI o agentes en producción no lo puedo saber desde fuera.

La señal comunitaria es fina. El primer Show HN, en mayo de 2026 (publicado bajo la cuenta personal lahfir/agent-desktop, antes del traslado a vercel-labs), llegó a 99 puntos y 44 comentarios. Un segundo Show HN en agosto titulado «I spent 3 months making desktop automation stop lying to AI agents» se quedó en 6 puntos y cero comentarios. Y el gestor de incidencias parece la cola del propio mantenedor: de los 30 elementos abiertos más recientes, 20 son pull requests, y uno de los informes con forma de usuario real es el #569 —la snapshot de una ventana elevada vuelve vacía y se reporta como complete: true— que es exactamente la clase de fallo que el directorio de soluciones se escribió para evitar.

Un archivo que contradice al producto

skills/jev-desktop/SKILL.md ocupa 16.283 bytes, es la skill más grande del repositorio y no viaja en ningún paquete instalado. Requiere un clon del repositorio y una TYPESAFE_API_KEY. Su propia descripción dice qué hace: maneja una app de escritorio a partir de un objetivo en lenguaje natural sin leer nunca el árbol de accesibilidad, para que «un árbol JSON de 150 elementos» no entre en tu contexto.

Es la premisa contraria a la de la herramienta que lo contiene. No lo leo como una contradicción de mala fe: es un experimento documentado sobre el compromiso al que se enfrenta toda la categoría. Si quieres saltarte el árbol, necesitas un modelo que mire una pantalla, y entonces vuelves a visión, latencia y coste. Lo interesante es que la vía cara está versionada, referenciada por una skill y no mencionada en el README.

Está también AGENTS.md, más preciso que el README como suelen serlo los documentos internos, y que contiene las dos frases que mejor explican este código:

Shipped behavior is the code and its tests. A doc that contradicts the code is a defect; fix it in the same change.

Never add app-specific branches keyed by app names, bundle IDs, executables, window titles, or a known app’s UI layout.

La primera explica por qué existe el registro. La segunda explica por qué el crate de Linux tiene 124.060 líneas: un adaptador agnóstico de aplicación sobre cinco toolkits tiene que absorber en código compartido las discrepancias de GTK3, GTK4, Qt 6, Chromium y Gecko, y cada discrepancia se convierte en un test.

Qué haría yo con esto

Si lo vas a meter en un harness de agentes, las decisiones son concretas.

  1. Lee data.complete y data.identity_unproven, no ok. Desde la 2.2, una snapshot que agota su presupuesto devuelve ok: true con complete: false y truncated: true. Un agente que solo mira ok actuará sobre un árbol parcial.
  2. Ramifica por error.code y error.suggestion, en ese orden, y trata PERM_DENIED en Linux como un problema de bus. El reparto 44/14 hace que el código por sí solo no identifique la causa.
  3. No reintentes solo porque disposition.retry sea safe. Condiciona los reintentos a details.retryable cuando exista, y ponles tope. Todos los errores de este artículo eran retry: safe.
  4. Fija la versión. Cinco releases en una semana, un sobre que pasó de 2.1 a 2.6 y una ABI de C que llegó al major 7 dentro de la 1.1.0. Un rango flotante en el paquete npm está a una reinstalación de un cambio de payload.
  5. Presupuesta 2 ms por nodo y espera 2,5–3,5× de la concurrencia. Una snapshot de 200 nodos son unos 400 ms en secuencia y unos 120 ms con 128 llamadas en vuelo. Ese es el número que tiene que sobrevivir al timeout de tu bucle, no el valor por defecto de la herramienta.
  6. Verifica tú las escrituras si te importan. El producto ya relee en set-value, type y compañía; no puede hacerlo en un campo seguro y lo dice con delivered_unverified. Si tu flujo escribe texto y luego actúa sobre él, trata delivered_unverified como desconocido.
  7. Comprueba status en el primer contacto con cada host, no una vez por despliegue. supported_surfaces: [] es la señal más barata de que ese host no va a funcionar nunca, y tiene código de salida 0.

Y si lo comparas con la automatización de navegador tipo Playwright: no es esa herramienta, y el README lo dice. Automatiza apps nativas a través del árbol de accesibilidad, lo que significa que es rápido y headless en macOS y Windows, y condicional al escritorio, al servidor gráfico y al toolkit en Linux. Una app que no exponga árbol de accesibilidad —un juego, un canvas, una interfaz dibujada a mano— queda fuera de alcance por diseño.

Preguntas frecuentes

¿agent-desktop es de Vercel? Es un proyecto de Vercel Labs (vercel-labs/agent-desktop). Los productos de Labs son experimentos, no productos de plataforma con soporte; el README enlaza a la página de productos de Labs. El código es Apache-2.0 y el repositorio se publicó originalmente en la cuenta personal del autor en febrero de 2026, antes de mudarse a la organización.

¿Funciona en un servidor Linux sin escritorio? No para leer aplicaciones. El binario arranca y responde status y version, pero toda observación necesita un bus AT-SPI alcanzable dentro de una sesión gráfica. En una máquina sin escritorio, status devuelve ok: true con supported_surfaces: [] y accessibility.state: denied, y los comandos con puerta devuelven PERM_DENIED. Cuenta con una sesión de escritorio, una pantalla virtual con bus de sesión o otro host.

¿Por qué 44 comandos y 14 con dos códigos de error distintos? Porque dos rutas de código describen el mismo hecho. crates/linux/src/system/bus/bus_fault.rs mapea los fallos de bus a PLATFORM_NOT_SUPPORTED; src/command_policy/mod.rs hace la comprobación previa de los comandos con puerta de accesibilidad y convierte un informe de permiso denegado en PERM_DENIED. Los comandos sin puerta (listado de apps, portapapeles, status) nunca pasan por la capa de política.

¿De verdad no necesita capturas de pantalla? Para observar, sí: el árbol se lee por AX (macOS), UI Automation (Windows) o AT-SPI (Linux). Las capturas existen como comando aparte para ventanas de aplicación y en Linux son la parte más dependiente del entorno: X11 no pide permiso para una ventana de app, una captura de pantalla completa necesita un gestor sin composición, y Wayland pasa por el consentimiento del portal.

¿Para qué sirve el archivo de 1.054 líneas? Es el registro de evidencias del proyecto: una fila por experimento de Linux con la pila, el alcance, la frase de la especificación bajo prueba, los valores capturados, el veredicto y la acción. Está calificado contra el documento de diseño del propio proyecto, y 93 filas salen CONTRADICTS. Es lo más útil del repositorio para decidir si el soporte de Linux es real en tu escritorio.

¿Es seguro dejarlo corriendo sin supervisión? El diseño es inusualmente cuidadoso en ese punto: las referencias están acotadas a su snapshot, las obsoletas se rechazan en lugar de reapuntarse, las acciones llevan una disposición de entrega y cuatro comandos de entrada fallan en cerrado porque ningún daemon puede garantizar la liberación. El riesgo no es que la herramienta invente clics; es que tu harness lea ok: true en una snapshot parcial o reintente para siempre un error «seguro».

Conclusión

agent-desktop es el intento más serio que he leído de hacer legible la automatización de escritorio para un agente, y la razón no es el diseño de la CLI: es que el equipo escribió lo que los sistemas operativos hacen de verdad, incluidos los 93 puntos donde su propia especificación se equivocaba, y luego reescribió el código para coincidir.

El precio de esa seriedad se ve en los mismos archivos. Linux es 2,75 veces el adaptador de macOS y la mitad tests, porque cinco toolkits se contradicen; 44 de 60 comandos reportan en Linux un error con forma de permiso de macOS para una condición que no tiene nada que ver con permisos; y el consejo de reintento del README, aplicado al pie de la letra, entra en bucle con errores que details.retryable: false ya declara imposibles.

42 watchers sobre 16.842 estrellas no prueba que el proyecto esté abandonado. Prueba que quienes le dieron la estrella todavía no han tenido que depurarlo en un host sin sesión gráfica. Lee probes/linux/FINDINGS.md antes de ser uno de ellos.

Si comparas esto con otra infraestructura para agentes que he medido, la auditoría de gstack aplica el mismo método a un corpus de prompts, y la auditoría de Worktrunk lo aplica a una CLI cuyos hooks fallan en silencio.