dsh-android README

The project's README from github.com/ZSeven-W/dsh-android. As one markdown file: README.md.

DSH Android

DSH Android

A live Android device inside a DeepSeek Harness conversation — emulator or USB phone, driven entirely through adb.
20 agent tools • in-process live stream, no external helper • three-button navigation panel • Gradle build & run • Vision OCR

npm: @zseven-w/dsh-android · Current plugin release: 0.1.0-rc.4 · Tested with 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 — a live Android device inside the conversation

An Android device streamed and controlled from inside a DSH conversation — the agent's tool call in the center, the live device panel on the right

Why DSH Android

DSH Android gives the agent a real Android device inside the conversation — and gives you the pixels. The agent can start a stream on an emulator or a USB-connected phone, build and install a Gradle project, drive the UI by resource-id/text or by OCR, read logcat, and inspect processes and memory, while a live stream of the device renders in a persistent sidebar panel where you can tap, drag, rotate, and press Back / Home / Recents directly on the video. No image blocks and no screen-recording files: visual bytes reach the UI only through signed, expiring URLs served by the DSH webserver.

There is exactly one code path. adb devices -l reports a serial, and that serial is a device's only identity — emulator-5554, a USB serial, or an ip:port target all behave identically. The plugin is bound to no emulator product (AVD, Genymotion, WSA, a cloud device farm), and there is no simulator/real-device split to reason about.

📱 Live device in the conversation A multipart/x-mixed-replace PNG stream produced in-process and served straight from the latest-frame buffer through signed /_dsh/dsh-android/* routes.
🔌 No external stream helper, no inner port One persistent adb exec-out child runs while :; do screencap -p; done; the host splits the concatenated PNGs into frames itself. There is no loopback stream server to proxy, no port range to manage, and nothing to adopt after an ungraceful exit.
🧩 One adb code path Emulators and phones are the same thing to adb and to this plugin. No simctl/WebDriverAgent dual stack, no build-and-trust dance before a physical device works.
🛠️ 20 agent tools Devices, boot/shutdown, screenshot, interact, Gradle build & run, app listing/launching, uiautomator UI tree + tap-by-element, list/feed row actions, Vision OCR find/tap/wait, logcat, processes, ANR/crash backtrace, meminfo, app info.
👆 Three-button navigation panel Tap and drag on the live video; a toolbar with ◁ Back · ○ Home · □ Recents plus rotate, screenshot, and refresh; a device menu for the notification shade, quick settings, lock, wake, and the assistant.
🖼️ Native multimodal On an image-capable model every capture tool (screenshot, interact, tap_element, tap_text, tap_row) returns the screenshot ITSELF as an image block — the model sees the screen directly. OCR stays for pixel-precise text taps and text-only routes; text-only models keep the plain JSON summary.
🔐 Signed loopback-only routes Every route requires a loopback peer, a loopback Host (DNS rebinding rejected), and Fetch-Metadata/Origin checks — before any capability is consulted. HMAC-SHA256 capabilities expire within 10 minutes.
🔍 Semantic + visual automation android_ui_tree dumps the uiautomator hierarchy and android_tap_element taps by resource-id, text, or content-description; when the tree is empty or the text is baked into an image, android_find_text / android_tap_text OCR the screen instead of guessing coordinates.

Tools

All 20 tools are registered on every host and return plain JSON — visual bytes reach the UI only through presentationMeta + signed routes, never as image blocks. When adb cannot be resolved the tools stay registered and every call fails with an explanatory error naming the fix.

Coordinates are normalized 0..1 of the streamed frame everywhere. The frame follows the display rotation (a landscape app streams 2400×1080 on a 1080×2400 device) and input tap shares that same space, so no client-side rotation math exists anywhere in this plugin.

Core tools

Tool What it does Key parameters
android_devices List every device adb devices -l reports (serial, state, emulator/physical, model, Android version, API level, AVD name) plus the machine's AVD names under avds. Use it to discover the serial the other tools take. A failed enumeration throws instead of returning an empty list. —
android_boot Start the live stream. Pass an ONLINE serial to stream it immediately, or an AVD name to launch that emulator first and stream it once it finishes booting (minutes on a cold start). The stream stays alive for the conversation so the panel can show the device live. device (required — a serial or an AVD name)
android_shutdown Shut an emulator down (adb emu kill) and stop the stream when it targets that device. A physical device is refused with the reason: adb cannot power off a phone. device
android_screenshot Capture a PNG and return a small JSON summary (path, bytes, dimensions, device); the image renders in the card and the panel, never as an image block. device (optional — streamed device, else the only online one)
android_interact Interact with the streamed device: tap at normalized 0..1 coordinates, type text, press a navigation or hardware button (back, home, recents, power, volume_up, volume_down, menu, enter, delete), send a swipe gesture, or scroll. After the action settles (~300 ms) a fresh screenshot shows the effect. action (required — tap/type/button/gesture/scroll), x/y, text, name, json, device
android_list_apps List the packages installed on the device (pm list packages), with the version name from dumpsys package and a human label when one is resolvable — a third-party package name cannot be guessed, so list it or pass name to android_launch_app. device, query (case-insensitive substring, CJK included), include_system (default false)
android_launch_app Launch an installed app by packageName, or by name (a case-insensitive label substring resolved through the same listing). Exactly one of the two. relaunch force-stops the app first. packageName or name (exactly one), device, relaunch
android_build_run Build a Gradle project (./gradlew assembleDebug), install the resulting debug APK (adb install -r), and launch it. Takes minutes for a full build; on failure the result carries the tail of the Gradle error output. projectPath (required), device

UI-tree and row tools (uiautomator)

Tool What it does Key parameters
android_ui_tree Dump the foreground app's uiautomator hierarchy as nodes — type (the class tail), text, contentDesc, resourceId, bounds in pixels, enabled, focused — capped at ~40 KB (the deepest levels are pruned and truncated is set). device, max_depth, filter (case-insensitive substring over text/content-description/resource-id)
android_tap_element Tap an element by identity — resource_id matches the node's resource-id; text matches its text or content-description. Exact match first, then case-insensitive substring; nested duplicates collapse to one target and an ambiguous match lists up to 8 candidates instead of picking one. Disabled elements are refused. The tap lands on the element center, then a ~300 ms screenshot shows the effect; pass expect_text / expect_gone and the tap plus its verification become one round trip. device, resource_id, text, expect_text, expect_gone
android_ui_rows Read a list/feed screen (RecyclerView and friends) as ROWS instead of a raw tree: repeated same-shaped children become rows carrying an index, a pixel frame, the aggregated label, and the counters parsed out of that label (number + classifier token, Chinese or English — no app vocabulary is hardcoded). Counter keys round-trip: pass one exactly as listed to android_tap_row.expect_count. device, max_depth
android_tap_row Tap at a relative position inside one visible row (index from android_ui_rows; x/y as fractions of that row's frame, default 0.5 = center). The frame comes from a FRESH tree read, so no absolute coordinates are guessed, and an out-of-range index FAILS rather than clamping. With expect_count={key, delta} the tool re-reads the row after ~800 ms and verifies the counter moved by exactly ±1; an unknown key REFUSES the tap before it happens. device, index (required), x, y, expect_count ({key, delta})

OCR, logs, and debug tools

Tool What it does Key parameters
android_find_text OCR the CURRENT screen with the plugin-compiled Vision helper (accurate recognition, zh-Hans + en-US). Use it when the UI tree is empty or degenerate, for text rendered as graphics (badge counts, prices baked into images), or to independently verify what is on screen. Returns {device, size, items:[{text, confidence, rect}]} where rects are pixel boxes with a top-left origin, confidence-sorted and capped at ~40 KB. macOS host only. device, query (case-insensitive substring), min_confidence (default 0.3)
android_tap_text OCR the CURRENT screen and tap the center of the best text match — the same exact → contains → candidate-list rules as android_tap_element, for text the UI tree cannot see. The matched pixel center is normalized against the frame size and sent as a tap; after ~300 ms a fresh screenshot shows the effect. macOS host only. device, query (required), min_confidence, expect_text, expect_gone
android_wait_for Wait until text appears or disappears, polling the same capture + OCR pipeline every 600 ms until the condition holds or the timeout expires (default 8 s, max 60 s). A timeout is a normal matched:false answer, never an error. macOS host only. device, text (required), mode (appear/disappear), timeout_ms, min_confidence
android_logs Read what the device logs: snapshot (logcat -d -v time over a recent window, default 2m) or follow (a bounded live capture for duration_seconds, default 10, max 60 — never a hanging stream). Filter to one app with bundle_id (the Android package name, resolved to its pid). Output is capped at ~300 lines / 30 KB with a narrowing hint. device, mode (snapshot/follow), duration, duration_seconds, bundle_id, grep
android_processes List the device's running processes (ps -A) as {pid, name} — the pid source for android_backtrace. device, filter (case-insensitive substring over the process name)
android_backtrace Ask the process to dump its stacks (kill -3) and read the resulting ANR trace from /data/anr/. Most non-rooted devices refuse that directory, so the tool degrades to the crash buffer (logcat -b crash -d) and reports honestly which engine answered and what it cannot see. device, pid or bundle_id
android_meminfo Parse dumpsys meminfo <package>: total PSS, the Java/native/graphics split, and the top categories — the Android answer to a leak summary. device, bundle_id (required)
android_app_info Installed-app facts from dumpsys package <package>: version name and code, data directory, code path, first-install time, and the system flag. A missing app returns installed: false plus a note naming android_list_apps — it does not throw. device, bundle_id (required)

Display surfaces

Security

Requirements

Physical devices

There is no WebDriverAgent equivalent to build, sign, trust, or re-sign every seven days. Enable USB debugging, plug the phone in, accept the authorization prompt on the device, and it appears in android_devices with every tool working against it. An unauthorized device is reported as such with the prompt hint, not as a mysterious failure.

Three honest caveats:

Performance

Measured on an emulator (Android 14, 1080×2400):

Persistent screencap loop ≈ 8 fps
ensureStreaming first frame ~200 ms
input tap round trip ~130 ms

The single persistent child is what buys this: spawning one adb per frame costs ~50–100 ms before any pixels move. Expect ~5–10 fps on an emulator and ~2–5 fps on a USB phone, depending on the machine and the screen density.

Install into DSH

dsh plugin --profile web add @zseven-w/dsh-android@latest
dsh web

Or add it as a dependency of an existing profile package:

pnpm add @zseven-w/dsh-android

Quick start

  1. Discover devices — "List the Android devices." → android_devices.
  2. Start the stream — "Stream emulator-5554." → android_boot. The panel opens with the device live. (An AVD name boots that emulator first.)
  3. Tap on the video — tap or drag directly on the panel, or let the

dsh-android (ZSeven-W/dsh-android)

Searching Database…

这是什么?DSH Android 是一个 DeepSeek Harness(DSH)插件,把一台实时在线的 Android 设备——模拟器或 USB 连接的手机——放进 AI 智能体对话中,让智能体完全通过 adb 来查看、控制和检查它。
它是独立应用,还是接入其他东西的插件?它不是独立应用——它是一个可安装的 DSH 插件(以 npm 包形式分发),为 DeepSeek Harness 对话添加 Android 设备工具和实时画面面板。
怎么把它加进我的环境?把 npm 包作为 DSH 插件安装即可;一旦在 DSH 对话中加载,智能体就会调用 androiddevices 和 androidboot 等工具来发现并串流设备。
我的机器上需要有什么才能用?你需要宿主机上可以调用的 adb,以便插件枚举和操控设备;还需要一个 DSH 宿主(针对 DSH 0.1.1-rc.1 测试过);此外,想让构建运行工具编译的项目里要有 Gradle。
不管什么系统、什么设备都能用全部功能吗?基于 OCR 的工具(androidfindtext、androidtaptext、androidwaitfor)只有在 DSH 宿主为 macOS 时才能运行;其余功能在 adb 可用的地方都能用,模拟器和实体设备之间没有区别。
它怎么处理捕获到的屏幕数据?截图和实时视频流不会以原始图像数据的形式保存到磁盘——它们通过 DSH web 服务器签发的带过期时间的签名 URL 提供,每条路由都要求本机回环对端并通过 Fetch-Metadata/Origin 校验,能力令牌会在 10 分钟内过期。
有了这 20 个工具,智能体实际能做什么?它可以枚举并启动设备、点击/输入/滑动、构建并安装 Gradle 项目、读取 uiautomator 的 UI 树并按 resource-id 或文本点击元素、对屏幕做 OCR、读取 logcat 和进程列表,以及获取 ANR/崩溃回溯和内存信息。
它对模拟器和真实 USB 手机的处理有区别吗?没有——两者都只是 adb 序列号(例如 emulator-5554 或某个 USB 序列号),所以无论设备是虚拟的还是实体的,每个工具的行为都完全一样。
模型真的能看到屏幕,还是只能拿到文字描述?在支持图像的模型上,screenshot 和 interact 等采集工具会把截图作为图像块返回,模型能直接看到屏幕;纯文本模型则会得到一份 JSON 摘要加上 OCR 结果。
实时设备面板长什么样?它是一个常驻侧边栏(窄屏时为居中覆盖层),实时串流设备画面,你可以直接在视频上点击、拖动和旋转,还配有返回/主屏/最近任务按钮,以及用于通知、快速设置、锁定和唤醒的设备菜单。
Creator
ZSeven-W
Size
2.6 MB
Stars
189
Updated
CommitOct 08
Age
1mo
Commits · 90 Days