DSH Android

DSH Android

Живое Android-устройство внутри диалога DeepSeek Harness — эмулятор или телефон по USB, полностью под управлением adb.
20 инструментов агента • живой поток внутри процесса, без внешнего помощника • панель навигации с тремя кнопками • сборка & запуск через Gradle • Vision OCR

npm: @zseven-w/dsh-android · Текущий релиз плагина: 0.1.0-rc.4 · Проверено с DSH 0.1.1-rc.1

English · 简体中文 · 繁體中文 · 日本語 · 한국어 · Français · Español · Deutsch · Português · Русский · हिन्दी · Türkçe · ไทย · Tiếng Việt · Bahasa Indonesia


DSH Android — живое Android-устройство прямо в диалоге

Android-устройство, транслируемое и управляемое прямо из диалога DSH — вызов инструмента агентом в центре, живая панель устройства справа

## Зачем нужен DSH Android DSH Android даёт агенту настоящее Android-устройство прямо в диалоге — а вам отдаёт пиксели. Агент может запустить поток на эмуляторе или на подключённом по USB телефоне, собрать и установить проект Gradle, управлять интерфейсом по `resource-id`/тексту или через OCR, читать logcat, изучать процессы и память, пока живой поток устройства отображается в постоянной боковой панели, где можно нажимать, перетаскивать, поворачивать и нажимать Назад / Домой / Недавние прямо на видео. Никаких блоков изображений и файлов записи экрана: визуальные байты попадают в интерфейс только через подписанные URL с ограниченным сроком действия, которые выдаёт веб-сервер DSH. Путь исполнения ровно один. `adb devices -l` сообщает **серийный номер**, и этот серийный номер — единственная идентичность устройства: `emulator-5554`, USB-серийник или цель вида `ip:port` ведут себя абсолютно одинаково. Плагин не привязан ни к какому продукту-эмулятору (AVD, Genymotion, WSA, облачная ферма устройств), и здесь нет разделения на симулятор и реальное устройство, о котором приходилось бы думать. | | | | --- | --- | | 📱 **Живое устройство в диалоге** | PNG-поток `multipart/x-mixed-replace`, создаваемый **внутри процесса** и отдаваемый напрямую из буфера последнего кадра через подписанные маршруты `/_dsh/dsh-android/*`. | | 🔌 **Ни внешнего помощника трансляции, ни внутреннего порта** | Один постоянный дочерний процесс `adb exec-out` выполняет `while :; do screencap -p; done`; хост сам разбивает склеенные PNG на кадры. Нет loopback-сервера потока, который нужно проксировать, нет диапазона портов для управления и нечего усыновлять после нештатного завершения. | | 🧩 **Единый путь исполнения через adb** | Для adb и для этого плагина эмуляторы и телефоны — одно и то же. Никакого двойного стека `simctl`/WebDriverAgent, никаких плясок со сборкой и доверием сертификату, прежде чем заработает физическое устройство. | | 🛠️ **20 инструментов агента** | Устройства, загрузка/выключение, скриншот, взаимодействие, сборка & запуск через Gradle, перечисление/запуск приложений, дерево UI `uiautomator` + нажатие по элементу, действия со строками списков/лент, поиск/нажатие/ожидание текста через Vision OCR, logcat, процессы, бэктрейс ANR/сбоев, meminfo, информация о приложении. | | 👆 **Панель навигации с тремя кнопками** | Нажимайте и перетаскивайте на живом видео; панель инструментов с **◁ Назад · ○ Домой · □ Недавние** плюс поворот, скриншот и обновление; меню устройства для шторки уведомлений, быстрых настроек, блокировки, пробуждения и ассистента. | | 🖼️ **Нативная мультимодальность** | На модели, умеющей работать с изображениями, каждый инструмент захвата (screenshot, interact, tap_element, tap_text, tap_row) возвращает сам скриншот как image block — модель видит экран напрямую. OCR остаётся для попиксельно точных нажатий по тексту и для чисто текстовых маршрутов; текстовые модели по-прежнему получают обычную сводку JSON. | | 🔐 **Подписанные маршруты только через loopback** | Каждый маршрут требует loopback-пира, loopback-`Host` (DNS-ребиндинг отклоняется) и проверок Fetch-Metadata/Origin — ещё до того, как будет прочитана хоть одна возможность. Возможности HMAC-SHA256 истекают в течение 10 минут. | | 🔍 **Семантическая + визуальная автоматизация** | `android_ui_tree` выгружает иерархию `uiautomator`, а `android_tap_element` нажимает по `resource-id`, тексту или content-description; когда дерево пустое или текст вшит в изображение, `android_find_text` / `android_tap_text` делают OCR экрана вместо угадывания координат. | ## Инструменты Все 20 инструментов регистрируются на любом хосте и возвращают чистый JSON — визуальные байты попадают в интерфейс только через `presentationMeta` + подписанные маршруты, никогда в виде блоков изображений. Когда adb не удаётся разрешить, инструменты остаются зарегистрированными, и каждый вызов завершается понятной ошибкой с указанием, что исправить. Координаты везде **нормализованы 0..1 относительно транслируемого кадра**. Кадр следует за поворотом дисплея (альбомное приложение транслируется как 2400×1080 на устройстве 1080×2400), и `input tap` использует то же пространство, поэтому никакой математики поворота на стороне клиента в этом плагине нет. ### Основные инструменты | Инструмент | Что делает | Ключевые параметры | | --- | --- | --- | | `android_devices` | Перечисляет все устройства, о которых сообщает `adb devices -l` (серийный номер, состояние, эмулятор/физическое, модель, версия Android, уровень API, имя AVD), плюс имена AVD этой машины в `avds`. Используйте его, чтобы узнать серийный номер, который принимают остальные инструменты. Неудачное перечисление бросает ошибку вместо пустого списка. | — | | `android_boot` | Запускает живой поток. Передайте серийный номер устройства в состоянии ONLINE, чтобы начать трансляцию сразу, или имя AVD, чтобы сначала запустить этот эмулятор и начать трансляцию, как только он загрузится (при холодном старте — минуты). Поток остаётся жив во время диалога, чтобы панель показывала устройство в реальном времени. | `device` (обязателен — серийный номер или имя AVD) | | `android_shutdown` | Выключает эмулятор (`adb emu kill`) и останавливает поток, если он нацелен на это устройство. Для физического устройства следует отказ с указанием причины: adb не умеет выключать телефон. | `device` | | `android_screenshot` | Снимает PNG и возвращает краткую JSON-сводку (путь, байты, размеры, устройство); изображение отображается в карточке и панели, никогда как блок изображения. | `device` (необязателен — транслируемое устройство, иначе единственное онлайн) | | `android_interact` | Взаимодействует с транслируемым устройством: нажатие по нормализованным координатам 0..1, ввод текста, нажатие навигационной или аппаратной кнопки (`back`, `home`, `recents`, `power`, `volume_up`, `volume_down`, `menu`, `enter`, `delete`), отправка свайпа или прокрутка. Когда действие устаканится (~300 мс), свежий скриншот показывает результат. | `action` (обязателен — `tap`/`type`/`button`/`gesture`/`scroll`), `x`/`y`, `text`, `name`, `json`, `device` | | `android_list_apps` | Перечисляет пакеты, установленные на устройстве (`pm list packages`), с именем версии из `dumpsys package` и человекочитаемой меткой, когда её удаётся разрешить — имя стороннего пакета угадать нельзя, поэтому сначала выведите список или передайте `name` в `android_launch_app`. | `device`, `query` (подстрока без учёта регистра, включая CJK), `include_system` (по умолчанию false) | | `android_launch_app` | Запускает установленное приложение по `packageName` или по `name` (подстрока метки без учёта регистра, разрешаемая через то же перечисление). Ровно одно из двух. `relaunch` сначала принудительно останавливает приложение. | `packageName` или `name` (ровно одно), `device`, `relaunch` | | `android_build_run` | Собирает проект Gradle (`./gradlew assembleDebug`), устанавливает получившийся debug-APK (`adb install -r`) и запускает его. Полная сборка занимает минуты; при неудаче результат содержит хвост вывода ошибок Gradle. | `projectPath` (обязателен), `device` | ### Инструменты дерева UI и строк (`uiautomator`) | Инструмент | Что делает | Ключевые параметры | | --- | --- | --- | | `android_ui_tree` | Выгружает иерархию `uiautomator` активного приложения в виде узлов — `type` (хвост имени класса), `text`, `contentDesc`, `resourceId`, `bounds` в пикселях, `enabled`, `focused` — с ограничением ~40 КБ (самые глубокие уровни обрезаются, ставится `truncated`). | `device`, `max_depth`, `filter` (подстрока без учёта регистра по тексту/content-description/resource-id) | | `android_tap_element` | Нажимает элемент по идентичности — `resource_id` сопоставляется с `resource-id` узла; `text` — с его текстом или content-description. Сначала точное совпадение, затем подстрока без учёта регистра; вложенные дубликаты схлопываются в одну цель, а при неоднозначном совпадении перечисляются до 8 кандидатов вместо выбора одного. Отключённые элементы отклоняются. Нажатие попадает в центр элемента, затем скриншот через ~300 мс показывает эффект; передайте `expect_text` / `expect_gone`, и нажатие вместе с проверкой станут одним циклом запроса. | `device`, `resource_id`, `text`, `expect_text`, `expect_gone` | | `android_ui_rows` | Читает экран списка/ленты (`RecyclerView` и подобные) как СТРОКИ, а не как сырое дерево: повторяющиеся однотипные дочерние элементы становятся строками с индексом, пиксельным фреймом, объединённой меткой и счётчиками, разобранными из этой метки (число + токен-классификатор, на китайском или английском — никакой словарь приложений не зашит). Ключи счётчиков проходят цикл туда-обратно: передавайте ключ в `android_tap_row.expect_count` ровно так, как он указан в списке. | `device`, `max_depth` | | `android_tap_row` | Нажимает по относительной позиции внутри одной видимой строки (`index` из `android_ui_rows`; `x`/`y` как доли фрейма этой строки, по умолчанию 0.5 = центр). Фрейм берётся из СВЕЖЕГО чтения дерева, поэтому абсолютные координаты не угадываются, а индекс вне диапазона ЗАВЕРШАЕТСЯ ошибкой, а не обрезается. С `expect_count={key, delta}` инструмент перечитывает строку через ~800 мс и проверяет, что счётчик сдвинулся ровно на ±1; неизвестный ключ ОТКЛОНЯЕТ нажатие до его выполнения. | `device`, `index` (обязателен), `x`, `y`, `expect_count` (`{key, delta}`) | ### Инструменты OCR, журналов и отладки | Инструмент | Что делает | Ключевые параметры | | --- | --- | --- | | `android_find_text` | Делает OCR ТЕКУЩЕГО экрана с помощью собранного плагином помощника Vision (точное распознавание, zh-Hans + en-US). Используйте, когда дерево UI пустое или вырожденное, для текста, отрисованного графикой (цифры бейджей, цены, вшитые в изображения), или чтобы независимо проверить, что на экране. Возвращает `{device, size, items:[{text, confidence, rect}]}`, где rect — **пиксельные** рамки с началом координат слева сверху, отсортированные по уверенности, вывод ограничен ~40 КБ. Только хост macOS. | `device`, `query` (подстрока без учёта регистра), `min_confidence` (по умолчанию 0.3) | | `android_tap_text` | Делает OCR ТЕКУЩЕГО экрана и нажимает центр лучшего совпадения текста — те же правила «точное → содержит → список кандидатов», что и у `android_tap_element`, для текста, который дерево UI не видит. Найденный пиксельный центр нормализуется относительно размера кадра и отправляется как нажатие; через ~300 мс свежий скриншот показывает эффект. Только хост macOS. | `device`, `query` (обязателен), `min_confidence`, `expect_text`, `expect_gone` | | `android_wait_for` | Ждёт, пока текст появится или исчезнет, опрашивая тот же конвейер снимок + OCR каждые 600 мс, пока условие не выполнится или не истечёт тайм-аут (по умолчанию 8 с, максимум 60 с). Тайм-аут — это нормальный ответ `matched:false`, никогда не ошибка. Только хост macOS. | `device`, `text` (обязателен), `mode` (`appear`/`disappear`), `timeout_ms`, `min_confidence` | | `android_logs` | Читает, что пишет устройство в журнал: `snapshot` (`logcat -d -v time` за недавнее окно, по умолчанию 2m) или `follow` (ограниченный живой захват на `duration_seconds`, по умолчанию 10, максимум 60 — никогда не висящий поток). Фильтруйте по одному приложению через `bundle_id` (имя пакета Android, разрешаемое в его pid). Вывод ограничен ~300 строками / 30 КБ с подсказкой по сужению. | `device`, `mode` (`snapshot`/`follow`), `duration`, `duration_seconds`, `bundle_id`, `grep` | | `android_processes` | Перечисляет работающие процессы устройства (`ps -A`) как `{pid, name}` — источник pid для `android_backtrace`. | `device`, `filter` (подстрока без учёта регистра по имени процесса) | | `android_backtrace` | Просит процесс выгрузить свои стеки (`kill -3`) и читает получившуюся трассировку ANR из `/data/anr/`. Большинство устройств без root закрывают этот каталог, поэтому инструмент деградирует до буфера сбоев (`logcat -b crash -d`) и честно сообщает, какой движок ответил и чего он не видит. | `device`, `pid` или `bundle_id` | | `android_meminfo` | Разбирает `dumpsys meminfo `: суммарный PSS, разбивку Java/native/graphics и топ категорий — Android-ответ на сводку по утечкам. | `device`, `bundle_id` (обязателен) | | `android_app_info` | Факты об установленном приложении из `dumpsys package `: имя и код версии, каталог данных, путь к коду, время первой установки и признак системного. Для отсутствующего приложения возвращает `installed: false` плюс примечание с указанием `android_list_apps` — а не бросает ошибку. | `device`, `bundle_id` (обязателен) | ## Поверхности отображения - **Боковая панель.** Живой вид живёт в постоянной правой панели (фиксированный док, отодвигающий диалог, или центрированный оверлей на узких вьюпортах). Она отображает живой PNG-поток и принимает клик-для-нажатия и перетаскивание-для-жестов прямо на видео, с панелью инструментов, где есть **◁ Назад**, **○ Домой**, **□ Недавние**, поворот, скриншот и обновление. Меню устройства выполняет пять действий уровня устройства (шторка уведомлений, быстрые настройки, блокировка, пробуждение, ассистент). Выбор устройства перечисляет все adb-устройства в ОДНОМ списке, сгруппированном по типу, а выключенные AVD показываются как подсказка, указывающая на `android_boot`, а не как загрузка по клику. Режимы размера и стили рамки (без рамки / ободок / корпус телефона) работают так же, как в iOS-близнеце; панель берёт соотношение сторон из собственного натурального размера кадра, поэтому поворот не требует никакой настройки. - **Компактные карточки диалога.** Результаты инструментов отображаются как однострочные карточки без встроенных изображений: имя устройства, подпись действия, бейдж статуса и подсказка «открыть в боковой панели». Клик по строке открывает панель. - **Статусная капсула над полем ввода.** Пока панель закрыта и поток онлайн, над полем ввода появляется маленькая капсула, открывающая панель по клику. - **Стандартный режим и режим Code.** Стандартные сессии используют спроецированный хостом `presentationMeta`; вложенные диспетчи Code Mode не несут meta, поэтому клиент восстанавливает идентичную meta из сохраняемого JSON результата — панель, карточки и капсула работают в обоих режимах. ## Безопасность - **Браузер никогда не обращается к adb, и обращаться к внутреннему порту тоже не к чему.** Поток создаётся в этом процессе и отдаётся из памяти; каждый байт проходит через источник веб-сервера DSH по принадлежащим плагину маршрутам `/_dsh/dsh-android/*`: `/stream/` (живой multipart PNG), `/screenshot/` (кэшированный PNG), плюс `/grant`, `/switch-device`, `/devices`, `/capture`, `/status`, `/control` и `/device-action`. Это строго меньшая поверхность атаки, чем проксируемый loopback-сервер потока. - **Тройное ограждение loopback, применяемое до чтения любой возможности.** Транспортный пир должен быть loopback-адресом, заголовок `Host` должен указывать на loopback-авторитет (так что `Host` с DNS-ребиндингом отклоняется), а Fetch-Metadata/`Origin` должны быть same-origin. Host и Origin — данные, контролируемые вызывающей стороной, и сами по себе им никогда не доверяют. - **Возможности HMAC-SHA256, истекающие в течение 10 минут**, в формате `base64url(payload).base64url(mac)` и подписанные 32-байтным ключом на каждый домашний каталог DSH (`/cache/dsh-android/stream-access.key`, режим 0600, создаётся атомарно). Возможность, выданная для одного устройства, перестаёт работать в тот момент, когда слот потока занимает другое устройство, а возможность для скриншота нельзя воспроизвести против маршрута потока. - **Маршрут скриншотов отдаёт ровно один каталог.** Пути обходятся через `lstat` (любая символическая ссылка отклоняется), завершаются проверкой вхождения через `realpath`, открываются с `O_NOFOLLOW`, ограничиваются по размеру и перепроверяются после чтения — поэтому файл, подменённый символической ссылкой между выдачей и загрузкой, никогда не будет отдан. - **`/grant` ничего не загружает.** Он только запускает цикл кадров для устройства, которое уже онлайн, и отказывается (409 `device_busy`) вырывать поток у другого устройства. Смена устройства требует явного жеста `/switch-device`; загрузка AVD остаётся за инструментом `android_boot`. - **Keep-alive и остановка при простое.** Упавший цикл кадров перезапускается в фоне (~5 с задержки); при нуле потребителей поток останавливается сам через 5 минут. Намеренные остановки никогда не оспариваются. ## Требования - **Node ≥ 24.11.0.** - **adb** из состава platform-tools Android SDK, разрешаемый в таком порядке: переменная окружения `ADB` → `adb` в `PATH` → ``/``/корень SDK по умолчанию для данной ОС + `/platform-tools/adb`. Установите его через `sdkmanager "platform-tools"`, через Android Studio или через `brew install --cask android-platform-tools`. Без adb плагин всё равно загружается и все 20 инструментов регистрируются; каждый вызов тогда объясняет, чего не хватает. - **Устройство**: эмулятор любого продукта или телефон с включённой отладкой по USB. Лаунчер `emulator` необязателен и нужен только для `android_boot` по имени AVD — всё остальное работает с тем, что видит adb. - **DSH ≥ 0.1.0-rc.6 с веб-бандлом** для панели. Headless-профили тоже работают: все 20 инструментов функционируют нормально, просто без живого вида. - **Хост macOS для OCR** (нужен только `android_find_text` / `android_tap_text` / `android_wait_for`): плагин при первом использовании компилирует встроенный `assets/ocr.swift` с помощью `swiftc` в `~/Library/Caches/dsh-android/bin/ocr`. На хостах Linux и Windows эти три инструмента сообщают, что OCR требует фреймворк Vision из macOS; остальных 17 это не касается. Переопределения: `DSH_ANDROID_OCR_DIR`, `DSH_ANDROID_OCR_SWIFT`, `DSH_ANDROID_SWIFTC`. - **ADBKeyboard** (необязательно, для ввода CJK и эмодзи): `adb shell input text` работает только с ASCII. Установите [ADBKeyboard](https://github.com/senzhk/ADBKeyBoard) на устройство и выберите его активным методом ввода — тогда не-ASCII текст доставляется через его broadcast-интерфейс. Без него ввод не-ASCII текста ОТКЛОНЯЕТСЯ с подсказкой по установке — и никогда не вводится молча с искажениями. ## Физические устройства Здесь нет аналога WebDriverAgent, который нужно собирать, подписывать, доверять ему и переподписывать каждые семь дней. Включите отладку по USB, подключите телефон, примите запрос авторизации на устройстве — и оно появится в `android_devices`, а все инструменты будут с ним работать. Неавторизованное устройство так и сообщается, с подсказкой про запрос, а не как загадочный сбой. Три честные оговорки: - **Частота кадров по USB ниже** — примерно 2–5 fps на телефоне против 5–10 fps на эмуляторе, потому что каждый кадр идёт по USB как полноценный PNG. - **Для ввода CJK нужен ADBKeyboard** (см. выше); это касается и эмуляторов, и телефонов. - **`android_shutdown` не может выключить телефон.** У adb нет такого глагола, и инструмент прямо об этом говорит, а не притворяется. ## Производительность Измерено на эмуляторе (Android 14, 1080×2400): | | | | --- | --- | | Постоянный цикл screencap | ≈ 8 fps | | Первый кадр `ensureStreaming` | ~200 мс | | Круг `input tap` | ~130 мс | Всё это даёт единственный постоянный дочерний процесс: запуск отдельного `adb` на каждый кадр стоит ~50–100 мс ещё до того, как сдвинется хоть один пиксель. Ожидайте ~5–10 fps на эмуляторе и ~2–5 fps на телефоне по USB — в зависимости от машины и плотности экрана. ## Установка в DSH ```sh dsh plugin --profile web add @zseven-w/dsh-android@latest dsh web ``` Либо добавьте его зависимостью существующего пакета профиля: ```sh pnpm add @zseven-w/dsh-android ``` ## Быстрый старт 1. **Найдите устройства** — «Перечисли Android-устройства.» → `android_devices`. 2. **Запустите поток** — «Транслируй emulator-5554.» → `android_boot`. Панель открывается с устройством вживую. (Имя AVD сначала загрузит этот эмулятор.) 3. **Нажимайте на видео** — нажимайте или перетаскивайте прямо на панели, или позвольте агенту управлять: «Открой Настройки, затем нажми Дисплей.» → `android_interact`, либо `android_ui_tree` + `android_tap_element` для нажатий по идентичности, либо `android_find_text` + `android_tap_text`, когда дерево слепо. 4. **Соберите и запустите ваше приложение** — «Собери и запусти /path/to/MyApp.» → `android_build_run`. Полная сборка Gradle занимает минуты; когда она завершится, приложение запустится, и вы увидите его вживую в панели. 5. **Читайте журналы** — «Покажи последние две минуты logcat для com.example.app.» → `android_logs`. ## Устранение неполадок - **Каждый инструмент сообщает, что adb недоступен** — в ошибке названы все три уровня разрешения. Задайте `ADB=/path/to/adb`, положите `adb` в `PATH` или установите platform-tools из SDK (`sdkmanager "platform-tools"`). - **Устройство в состоянии `unauthorized`** — примите запрос отладки по USB на экране устройства. `android_devices` честно сообщает состояние, а не прячет устройство. - **`android_boot` не находит AVD** — лаунчер `emulator` не удалось обнаружить. Запустите эмулятор любым способом; он появится в `android_devices`, как только его увидит adb, и тогда `android_boot` примет его серийный номер. - **Не-ASCII текст отклоняется** — установите ADBKeyboard и выберите его методом ввода (см. «Требования»). Отказ намеренный: `input text` молча потерял бы или исказил символы. - **`android_find_text` сообщает, что OCR недоступен** — OCR требует хост macOS (фреймворк Vision от Apple). Остальные 17 инструментов работают везде. - **Поток останавливается сам** — это политика простоя, а не сбой: при нуле потребителей (панель закрыта, карточки не смонтированы, активных маршрутов нет) поток останавливается через 5 минут и перезапускается при следующем вызове инструмента или открытии панели. Упавший цикл перезапускается сам в течение ~5 секунд. - **Поворот выглядит неправильно на лаунчере** — лаунчеры и Настройки закрепляют себя в портретной ориентации и игнорируют `user_rotation`. Это нормальное поведение Android, а не баг плагина; поворачивайте внутри приложения, которое это позволяет. ## Разработка ```sh pnpm install pnpm run build # host tsc + client bundle → lib/ pnpm run typecheck pnpm test # every static suite; no device required ``` Смоук-тесты в `scripts/` прогоняют собранный `lib/`. Все они статические, кроме `dev-emulator-smoke.mjs`, которому нужно устройство и который сообщает SKIP (код выхода 0), когда устройства нет. | Скрипт | Что покрывает | | --- | --- | | `node scripts/dev-adb-smoke.mjs` | Разрешение adb (env / PATH / SDK) на подставном бинарнике, разбор `devices -l`, безопасный для бинарных данных `exec-out`, разделитель PNG-кадров и его ресинхронизацию, экранирование вводимого текста и жизненный цикл хоста (поток, управление, остановка при простое, dispose) на фиктивном тулчейне. | | `node scripts/dev-routes-static-smoke.mjs` | Подписанные маршруты на фиктивном хосте: относительные гранты, истёкшие/подделанные/чужого типа токены, ограждение loopback, конверты 405/415/400, закодированные отказы устройств, валидацию `/control`, форму поворота, вхождение путей скриншотов и живой multipart-поток. | | `node scripts/dev-tools-smoke.mjs` | Основные инструменты на фиктивном хосте через шов `createAndroidTools`. | | `node scripts/dev-uitree-smoke.mjs` | Инструменты дерева UI и строк: разбор XML `uiautomator`, селекторы, ограничение глубины, эвристики строк и счётчиков. | | `node scripts/dev-logs-smoke.mjs` | snapshot/follow у `android_logs`, фильтры, лимиты и сбор процессов. | | `node scripts/dev-panel-smoke.mjs` | Компоненты панели, режимы размера, стили рамки, логику дока/триггера/капсулы (только SSR). | | `node scripts/dev-emulator-smoke.mjs [serial]` | Живое устройство: первый кадр, устойчивую частоту кадров, круг нажатия, dispose. | ## Устранение неполадок ### Пустой / белый поток на эмуляторе Если панель транслирует сплошное белое (или чёрное) изображение, а `android_ui_tree` при этом видит реальные элементы интерфейса, значит на вашей машине сломано обратное чтение фреймбуфера GPU хоста эмулятором (известная проблема gfxstream на некоторых хостах macOS — сам `screencap` возвращает пустые кадры, поэтому затронуты все экранные инструменты). Перезапустите эмулятор с программным рендерингом: ```bash emulator -avd -gpu swiftshader_indirect ``` или задайте `hw.gpu.mode=swiftshader_indirect` в `config.ini` этого AVD. Физических устройств это никогда не касается. ## Дорожная карта - **Источник с более высокой частотой кадров.** Шов `StreamSource` намеренно сделан подключаемым: путь через `scrcpy-server` + WebCodecs H.264 заменил бы покадровый PNG-поток, не затрагивая ни маршруты, ни инструменты, ни панель. - **Горячая перезагрузка превью Compose.** iOS-близнец горячо подменяет превью SwiftUI в виде dylib; у Compose сегодня нет эквивалентного примитива горячей замены, поэтому это остаётся пунктом на будущее, а не выпущенной и нестабильной функцией. ## Экосистема - [DSH iOS Simulator](https://github.com/ZSeven-W/dsh-ios) — та же архитектура для симулятора iOS и подключённых по USB iPhone - [DSH Crew](https://github.com/ZSeven-W/dsh-crew) — делегировать задачи агентам DSH из Claude Code / Codex - [DSH Noema](https://github.com/ZSeven-W/dsh-noema) — долговременная память для DSH - [DSH OpenPencil](https://github.com/ZSeven-W/dsh-openpencil) — просматривать и редактировать документы `.op` прямо в диалоге ## Благодарности & лицензия - [Android SDK platform-tools](https://developer.android.com/tools/releases/platform-tools) (`adb`) — разрешается во время выполнения и никогда не распространяется вместе с плагином: лицензия SDK от Google не разрешает его встраивать. - [ADBKeyboard](https://github.com/senzhk/ADBKeyBoard) — Senzhk — необязательный IME на устройстве, обеспечивающий ввод не-ASCII текста (Apache-2.0; не встроен). - Архитектура и подход к маршрутам общие с [dsh-ios](https://github.com/ZSeven-W/dsh-ios), откуда этот плагин и портирован. - Полный список уведомлений см. в [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md). **Лицензия**: MIT