在侧边栏运行并调试安卓应用,画面流畅可交互
30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/26 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add yuxiaole-bili/dsh-apk-vm该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
展开完整介绍(123 字)↓
在 DeepSeek Harness 里预览和调试 Android APK:本地 root 过的 AOSP 虚拟机,最高 60 fps 的 H.264/WebCodecs 串流,拖拽安装,UIAutomator 自动化,每个智能体独享隔离虚拟机。
README
由 DeepSeek 最新模型翻译生成APK 虚拟机 —— 一个 DeepSeek Harness 插件
在本地开源、已 root 的 Android 虚拟机里运行真实的 Android APK,并在 DeepSeek Harness 侧边栏里预览 —— 以 H.264/WebCodecs 视频串流,最高 60 fps。
本仓库包含静态 DSH 插件 dsh-apk-vm。它新增一个一级侧边栏入口(「APK Virtual Machine」/「APK 虚拟机」),主面板会启动 AOSP Android 模拟器,把 APK 装进虚拟机,将虚拟机画面串流到面板,再把点击、滑动、按键、文本输入转发回虚拟机。虚拟机镜像是 userdebug 版本,已为 root 做好准备(每次启动都执行 adb root);插件通过同源、仅限回环地址的调试 HTTP API 和 apk_vm 智能体工具,把这个已 root 的设备暴露给 DSH。
中文简介:在 DeepSeek Harness 的侧边栏里预览 Android APK。
展开完整文档(README 剩余 1 节)
APK 虚拟机 —— 一个 DeepSeek Harness 插件
在本地开源、已 root 的 Android 虚拟机里运行真实的 Android APK,并在 DeepSeek Harness 侧边栏里预览 —— 以 H.264/WebCodecs 视频串流,最高 60 fps。
本仓库包含静态 DSH 插件 dsh-apk-vm。它新增一个一级侧边栏入口(「APK Virtual Machine」/「APK 虚拟机」),主面板会启动 AOSP Android 模拟器,把 APK 装进虚拟机,将虚拟机画面串流到面板,再把点击、滑动、按键、文本输入转发回虚拟机。虚拟机镜像是 userdebug 版本,已为 root 做好准备(每次启动都执行 adb root);插件通过同源、仅限回环地址的调试 HTTP API 和 apk_vm 智能体工具,把这个已 root 的设备暴露给 DSH。
中文简介:在 DeepSeek Harness 的侧边栏里预览 Android APK。插件会启动本地开源 AOSP Android 虚拟机(Android 14 / API 34,system-images;android-34;default;x86_64),安装 APK,并通过 scrcpy H.264 + WebCodecs 把画面串流到侧边栏(最高 60 fps),同时把点击、滑动、按键、文本输入送回虚拟机。支持拖拽 APK 上传、环境自检卡片、root 调试 API 与 apk_vm Agent 工具;多个 Agent 可各自使用独立的隔离虚拟机实例(多开)。
参考机上冷启动约 29.5 s、快照快速启动约 8.4–10.5 s。
当前 Windows x64 为端到端已验证平台;SDK/模拟器探测已覆盖 Windows / macOS / Linux。
状态: 0.3.13 · Apache-2.0 · 无 npm 运行时依赖 · 无需构建步骤。
这是一个开源 DSH 插件,以 dsh-apk-vm 为名发布在 npm 上。仓库: github.com/yuxiaole-bili/dsh-apk-vm。
维护者:yuxiaole-bili。
提交 issue 或发布之前,请先阅读 CONTRIBUTING.md、CODE_OF_CONDUCT.md、 SECURITY.md、docs/PRIVACY.md 和 docs/RELEASING.md。
一句话价值
把 DeepSeek Harness 变成本地 Android APK 工作台:启动已 root 的 AOSP 虚拟机,在侧边栏里以最高 60 fps 预览,让智能体不用离开会话就能安装、点击、检查和调试 APK。
安装
安装已发布的 npm 包(DSH 插件中心和社区市场用的就是这条命令):
dsh plugin --profile web add dsh-apk-vm
Electron 托管的 Desktop 配置档,请用 Plugins → add/install local bundle 选择本目录,或通过应用的插件管理器安装该包。
不走 npm 的源码安装方式:
dsh plugin --profile web add github:yuxiaole-bili/dsh-apk-vm
local clone / non-Electron profile:
dsh plugin --profile desktop add <path-to-this-clone>
能力类别
开发与运维 / 开发者工具 — Android 虚拟机生命周期、APK 安装与预览、root 调试,以及面向智能体的 UI 自动化,服务开发工作流。
风险、权限与兼容性
环境要求
- Android SDK,含 adb、模拟器、sdkmanager/avdmanager、匹配的系统镜像和 AVD(Environment 卡片会逐项检查;SDK 引导安装需手动开启)。
- 硬件虚拟化:Windows 上用 AEHD/WHPX,Linux 上用 KVM,macOS 上用 HVF。
- Node.js ≥ 20,仅用于仓库的无头检查;插件本身没有 npm 依赖,也没有构建步骤。
- 每个运行中的虚拟机约占 2.5 GB 内存;maxInstances 默认允许额外 3 台虚拟机。
权限 / 风险
- 客户机刻意使用 userdebug 并开启 root(启动时执行 adb root);不要把密钥或个人数据放进 AVD。
- apk_vm 会为配置档中的每个智能体注册,并开放客户机 root shell、应用安装/卸载、UIAutomator 和输入注入。请像对待一台已 root 的 Android 设备上的终端那样,给予同等信任。
- /apk-vm/ HTTP 路由和 WebSocket 升级同源,且默认仅限回环地址;allowRemote: true 是显式开启项,用于放在可信的已认证代理之后。
- 无遥测。只有用户通过 sdkmanager 显式下载 SDK/系统镜像,或用户配置远程 adb 设备时,才会产生网络访问。
兼容性
- 已在 DSH Desktop 0.2.1-rc.3 上测试(静态 bundle/插件 API)。
- 仅 Windows x64 做过端到端验证(启动、H.264、root 调试、智能体工具)。macOS/Linux 有无头安装/CI 覆盖,但本仓库未做过实机模拟器运行。
- 可选集成 dsh-better-sidebar;该服务不存在时,插件会平稳回退。
- AOSP Android 14 / API 34,x86_64 系统镜像;也可以接入物理/远程 adb 设备,替代 AVD。
演示 / 可见证据
以下截图来自参考主机:
APK VM 主面板
APK VM 在右侧边栏中
| 素材 | 状态 |
| docs/screenshot-panel.png | ✅ 主面板:Environment、客户机 H.264、控制项 |
| docs/screenshot-sidebar.png | ✅ 会话旁的右侧边栏布局 |
| docs/demo.gif | 可选 — 在真实主机上录一段 20–30 秒演示 |
性能和验证两节逐项列出测了什么、在哪台主机上测、怎么测。参考运行中 apk_vm 的真实输出:
find_ui {"text":"Phone"} -> Phone @ 102,1121
assert_text {"text":"Phone"} -> found 1 matching UI node(s)
tap_text {"text":"Phone"} -> tapped Phone at 102,1121
key {"text":"HOME"} -> returned to launcher
功能
- 侧边栏入口 + 主面板。 注册一个本地化侧边栏项(sidebar.panellist,id apk-vm,order 30),带手机图标(icon.svg),外加一个完整的中栏面板(main,键 apk-vm)。两者都在参考配置档中经过实机验证。
- 环境自检卡片。 一眼看清宿主机的就绪情况——SDK、adb、模拟器、命令行工具(sdkmanager/avdmanager)、已配置的系统镜像、 AVD 和 CPU 加速——并列出缺失项和可用的修复操作(GET/POST /apk-vm/setup)。系统镜像已安装时,卡片能创建 AVD;当 allowSetupDownloads: true 时,还能安装缺失的 SDK 组件,或 引导一台完全空白的宿主机(下载 Google 命令行工具、解压、接受许可、安装 platform-tools/emulator/build-tools/platform)。
- 多智能体隔离 / 多实例。 多个 DSH Agent 可以同时驱动各自独立的 VM:第一个 Agent 拿到默认 VM,后续 Agent 分配到隔离实例,各自拥有自己的 AVD、模拟器端口、快照、调试状态和 H.264/WebCodecs 流 (maxInstances,默认额外 3 个;额外 VM 使用 720×1280 预览显示)。
额外实例会暴露 owner / lastUsedAt / idleMs,并在 instanceIdleMs 内没有面板帧、流客户端或 Agent 命令时自动停止 (AVD 保留,便于快速复用)。面板选择器和 VM 实例卡片显示 owner/idle 状态,并提供 Stop/Destroy。apk_vm 工具接受 instance,并暴露 list_instances / create_instance / destroy_instance。
- 多 AVD 切换。 环境卡片列出模拟器可见的 AVD,并支持切换(GET /apk-vm/avds、POST /apk-vm/avd);当前 AVD 会自动停止、切换并重启。
- 按钮或拖拽上传 APK。 面板有 Upload APK 按钮,屏幕区域还有拖放浮层;文件会 POST 到 /apk-vm/upload (原始 APK 字节,≤ 500 MiB,校验 .apk/ZIP magic),安装到运行中的 VM 并启动。上传的文件存放在操作系统临时目录,插件销毁时清理。
仍支持按本地绝对路径安装(/apk-vm/install)。
- 已安装应用管理。 面板的 Installed apps 卡片列出当前实例的第三方包,并提供强制停止、清除数据、卸载;它使用与 Agent 工具相同的已 root 的 debug/packages + debug/action API。
- 可选的 dsh-better-sidebar 标签页。 安装该社区插件后,本插件通过它的服务注册真实标签页类型(apk-vm:preview)——无需 fork,无需 DOM hack,服务缺失时也能干净降级(见兼容性)。
- 从 UI 管理 VM 生命周期。 启动 / 停止 / 重启 AVD,或保存快照;环境卡片列出所有 adb 设备(模拟器、USB/物理机、网络),并可在运行时附加其中一台。Detach 会恢复受管 AVD 身份。实时状态、serial、启动模式/时间,以及
- 展示有界诊断日志。
- APK 发现 + 一键安装。 在配置的根目录下递归查找 .apk,用 aapt2 dump badging 读取包名 / 标签 / 启动 Activity,然后执行 adb install -r,再用 am start 启动应用。
- APK 静态预览。 GET /apk-vm/apk-info?path=… 不启动 VM 就能报告版本、min/target SDK、权限、原生 ABI、特性和启动 Activity;GET /apk-vm/apk-icon?path=… 直接从 APK ZIP 中提取位图启动图标。APK 列表在存在位图图标时显示图标。
- 实时 H.264 流,最高 60 fps。 默认传输方式是 scrcpy 5.0.1 H.264,走同源 WebSocket /apk-vm/ws,用 WebCodecs VideoDecoder 解码到 (配置为 60 fps、540×960、6 Mbps)。晚加入的客户端会收到 RESET_VIDEO 关键帧请求。如果 WebCodecs / WebSocket 不可用,面板回退到 adb exec-out screencap -p PNG 轮询(目标 1–10 fps)。
- 完整输入转发。 点击 → tap,拖拽 → swipe,滚轮 → swipe,双指捏合/缩放通过 scrcpy 控制套接字,另有 Home / Back / Recents / Power 和文本框;点击屏幕后,常用硬件键(方向键/Enter/Escape/Tab/Backspace/Delete)和可打印字符会作为 Android 按键/文本输入转发。剪贴板文本可写入设备(scrcpy SET_CLIPBOARD)或读回(GET_CLIPBOARD)。坐标从渲染的手机边框换算到设备分辨率。
- Root 调试面 + 额外设备工具。 每次启动后自动 adb root,设备端工具包位于 /data/local/tmp/dsh/debug.sh,同源调试 HTTP API,以及每个 DSH 智能体都能调用的 apk_vm Agent 工具:root shell、logcat、props、packages、install、push/pull、reboot、force-stop / clear-data / uninstall、screenshot、rotate、UIAutomator UI 树、5 秒录屏(拉取到主机临时目录)和性能快照。
- 快速启动。 检测 Hypervisor(AEHD / WHPX / KVM / HAXM / HVF)、主机 GPU 渲染、快速启动快照、预热和自动启动——在参考主机上实测冷启动 29.5 s、快速启动 8.4–10.5 s(详情)。
- 跨平台 SDK 发现。 lib/setup.js 和 lib/android.js 在 Windows、macOS 和 Linux 的目录布局中发现 Android SDK、adb、模拟器和 AVD(包括 .exe 后缀和各 OS 的回退位置)。目前只有 Windows x64 经过端到端验证。
- 仅同源。 所有 /apk-vm/ HTTP 路由和 /apk-vm/ws 升级都拒绝跨源请求;请求体和命令输出有界,主机进程用 argv 数组启动(绝不用 shell 字符串)。
- 主题化 + 本地化。 所有界面元素使用 --dsw-alias- 主题 token;英文和简体中文打包在 locale/。
- 后端零依赖。 Host 部分只用 Node 内置模块:不依赖 npm 包,也没有构建步骤。
快速开始
1. 前置条件
| 要求 | 说明 |
| DeepSeek Harness | 一个能安装本地插件包的配置档(桌面 Web UI,或任意带 plugin_manager 工具的智能体)。 |
| Android SDK | platform-tools(adb)、emulator、build-tools(用于 aapt2)、命令行工具(sdkmanager/avdmanager)以及一个 AOSP 系统镜像。SDK 命令行工具需要 JDK(17 可用)。 |
| AOSP 系统镜像 | system-images;android-34;default;x86_64(Android 14,API 34)——下文所有测量都用这个镜像。可通过 systemImage 配置。 |
| 硬件虚拟化 | x86_64 镜像必需:Windows 上用 AEHD 或 WHPX,Linux 上用 KVM,macOS 上用 HVF。见加速矩阵。 |
| 磁盘 / 内存 | SDK + 系统镜像和 AVD 需要几 GB 空间;建议用带现代桌面 GPU 的模拟器。 |
| Node.js 24+ (可选) | 只用于运行本仓库的检查(验证)。插件本身没有 npm 依赖。 |
插件的 Environment 卡片会替你完成这项检查(见首次运行);下面的命令是等效的手动操作。
CPU 加速矩阵
| 宿主系统 | 后端 | 启用方式 |
| Windows | AEHD(Android Emulator Hypervisor Driver) | 通过 SDK Manager 包 extras;google;Android_Emulator_Hypervisor_Driver 安装,或用独立安装程序;需要管理员权限并重启。Hyper-V/WHPX 未启用时推荐。 |
| Windows | WHPX | 启用 Windows Hypervisor Platform 可选功能(需要的话再启用 Hyper-V),重启。Hyper-V 已在用时选这个。 |
| Linux | KVM | 加载 kvm_intel/kvm_amd,确认 /dev/kvm 存在且你的用户能访问(例如加入 kvm 组),然后重启或重新登录。 |
| macOS | HVF(Hypervisor.framework) | macOS 内置;在 Intel 和 Apple Silicon 宿主上,模拟器会自动使用。 |
启动插件前先检查宿主:
"$ANDROID_HOME/emulator/emulator" -accel-check
插件自己也会运行 SDK 的 emulator-check accel,并把结果报告到 /apk-vm/state(vm.accel)、/apk-vm/setup(accel)和面板日志。如果没有可用的 hypervisor,它会快速失败并给出明确错误,而不是卡 20 分钟(x86_64 镜像在纯 TCG 下无法启动)。
2. 安装插件
把本仓库复制或克隆到本地任意位置:
git clone https://github.com/yuxiaole-bili/dsh-apk-vm.git
cd dsh-apk-vm
然后任选一种:
- 命令行 npm 安装(社区市场 / 非 desktop 配置档):
dsh plugin --profile web add dsh-apk-vm
源码回退:dsh plugin --profile web add github:yuxiaole-bili/dsh-apk-vm。
- DSH Web UI —— 打开 Plugins,选择从本地添加/安装插件包目录,然后选中这个文件夹。补丁包是 cordis.patch.yml;package.json 里的 dsh.client 声明会把 client.js 加载到 Web UI。
- 智能体工具 —— 让 DSH 智能体运行 plugin_manager,action 设为 install_bundle,target 设为这个文件夹的路径。
- 命令行本地安装(仅限非 Electron 配置档) —— dsh plugin --profile <profile> add <path-to-this-folder>。由 Electron 管理的 desktop 配置档必须从应用 UI 里管理。
安装或升级插件后,重新加载 DSH Web 页面,让新的客户端包生效。
3. 准备 Android SDK、系统镜像和 AVD
插件会自动发现 SDK(依次查找 sdkRoot、ANDROID_HOME、ANDROID_SDK_ROOT,再查各系统常见路径)以及 adb/emulator。最快的做法:打开面板,查看 Environment 卡片,装好系统镜像后点 Create AVD。如果镜像缺失,且已设置 allowSetupDownloads: true,卡片也能替它运行 sdkmanager --install;否则你自己装:
sdkmanager --install "platform-tools" "emulator" "build-tools;34.0.0" \
"system-images;android-34;default;x86_64"
avdmanager create avd -n dsh_apk -k "system-images;android-34;default;x86_64"
插件也可以连上一台已在运行的 adb 设备,不必自己启动 AVD;设置 serial(例如 emulator-5558 或 <device-ip>:5555),并把 emulatorPath/avdName 留空。
4. 配置
插件包自带可移植的默认值(自动检测 SDK,avdName: dsh_apk)。按你的机器检查配置 —— 至少看看 roots,如果你想加额外的 APK 扫描目录。完整的键列表见配置参考。
config:
sdkRoot: 'C:/Android/sdk' # optional; otherwise auto-detected
emulatorPath: 'C:/Android/sdk/emulator/emulator.exe' # optional; auto-detected
serial: 'emulator-5558' # ...or attach an existing adb serial instead of spawning
systemImage: 'system-images;android-34;default;x86_64'
avdName: dsh_apk
port: 5558
roots: ['C:/projects'] # extra APK discovery roots (workspace is automatic)
gpuMode: host
accel: auto
requireAccel: true
quickBoot: true
snapshotName: default_boot
saveSnapshotAfterBoot: true
prewarm: false # opt-in warm boot on plugin activation
prewarmDelayMs: 3000
autoStart: true
multiInstance: true # isolated VMs for concurrent agents
maxInstances: 3 # extra VMs (default VM is always available)
instanceIdleMs: 900000 # stop idle extra VMs after 15 min (AVD kept)
instanceReapIntervalMs: 60000
allowRemote: false # reject non-loopback VM API clients
allowSetupDownloads: false # gate the optional system-image download action
scrcpy: true
scrcpyMaxSize: 960
scrcpyMaxFps: 60
scrcpyBitrate: 6000000
frameFps: 2 # only the PNG fallback target (1-10)
如果省略 emulatorPath / adbPath,插件会按顺序自动查找 Android SDK:先看 sdkRoot,再看 ANDROID_HOME / ANDROID_SDK_ROOT,然后是常见的单用户/系统安装位置,最后回退到 PATH。启动 AVD 仍然需要 avdName;想接入已有设备,改用 serial。
5. 首次运行
1. 打开 DSH 侧边栏,点击 APK Virtual Machine。查看 Environment 卡片:绿色 Ready 表示已找到 SDK、镜像、AVD 和加速;Setup needed 会列出缺少什么,并提供 Create AVD(当 allowSetupDownloads: true 时还可选 Download image)。
2. 按 Start VM(或让 prewarm/autoStart 启动它)。首次启动是冷启动(在参考主机上实测 29.5 s);之后插件会保存快速启动快照 default_boot,后续启动直接恢复它(实测 8.4–10.5 s)。
3. 安装 APK:点击 Upload APK,把 .apk 文件拖到面板上,或从发现的列表里选一个 / 按绝对路径安装。应用会自动安装并启动。用手机外框做输入,用 Rooted debug 卡片做 root shell、logcat、props、UI 树、旋转、录屏、性能和截图。
4. 或者交给智能体,例如:"Use the apk_vm tool to install C:/work/app.apk and take a screenshot."
配置
所有键都是可选的;默认值见 Default 列。
| 键 | 默认值 | 用途 |
| emulatorPath | 自动检测 | Android 模拟器可执行文件的路径。从 SDK / PATH 自动发现。 |
| avdName | — | 要启动的 AVD 名称。启动 AVD 时必填。 |
| serial | — | 接入这个 adb serial,而不是启动模拟器。 |
| port | 5558 | 启动时使用的模拟器控制台端口。 |
| sdkRoot | ANDROID_HOME / ANDROID_SDK_ROOT | Android SDK 根目录,供 adb、aapt2、emulator-check、sdkmanager/avdmanager 使用。 |
| adbPath | <sdk>/platform-tools/adb | 显式指定 adb 可执行文件。 |
| systemImage | system-images;android-34;default;x86_64 | Environment 卡片要求的系统镜像,也用于创建 AVD。 |
| spawnEmulator | true | 设为 false 时只接入(从不启动)。 |
| emulatorArgs | [] | 追加到模拟器命令行的额外 argv。 |
| roots | [] | 额外的 APK 发现根目录。DSH 工作目录、process.cwd() 和主目录会自动加入。 |
| bootTimeoutMs | 1200000 | 等待客户机报告 sys.boot_completed=1 的最长时间。 |
| gpuMode | host | host 使用真实 GPU;swiftshader_indirect 是无头主机的软件回退。 |
| accel | auto | auto 检测 AEHD/WHPX/KVM/HAXM/HVF;on/off 强制决定。 |
| requireAccel | true | 找不到 hypervisor 时立即报错退出,而不是在 TCG 下一直启动。 |
| quickBoot | true | 有已保存的 AVD 快照时直接恢复;设为 false 会追加 -no-snapshot。 |
| snapshotName | default_boot | 要加载/保存的快照名。 |
| saveSnapshotAfterBoot | true | 首次冷启动成功后保存快照。 |
| prewarm | false | 插件激活后不久在后台启动 VM。 |
| prewarmDelayMs | 3000 | 预热启动前的延迟。 |
| autoStart | true | 收到第一个 /apk-vm/state 或 /apk-vm/frame 请求时启动 VM。 |
| multiInstance | true | 为并发智能体额外开隔离 VM。 |
| maxInstances | 3 | 额外实例上限(不含默认 VM)。每个运行中的实例都占用 guest 内存和 CPU。 |
| instanceIdleMs | 900000 | 额外实例超过这段时间没有面板帧、视频客户端或智能体命令就停止(AVD 保留)。 |
| instanceReapIntervalMs | 60000 | 空闲回收器检查额外实例的间隔。 |
| allowRemote | false | 允许非 loopback 客户端访问 /apk-vm/ 和 /apk-vm/ws(只能放在你自己的带认证代理后面)。 |
| allowSetupDownloads | false | 允许环境卡片的 install-image 动作执行 sdkmanager --install(大体积下载)。 |
| frameFps | 2 | PNG 回退路径的目标帧率,1–10(不影响 H.264 路径)。 |
| scrcpy | true | 启用 H.264/WebCodecs 传输。 |
| scrcpyMaxSize | 960 | 编码器最大尺寸;960 输出 540×960 视频。 |
| scrcpyMaxFps | 60 | 编码器帧率上限,1–60。 |
| scrcpyBitrate | 6000000 | 视频码率,单位 bit/s。 |
| scrcpyPort | 27183 | scrcpy 服务端隧道的本地 adb forward 端口。 |
| scrcpyJarPath | 内置 JAR | 覆盖 scrcpy 服务端 JAR 路径。 |
环境报告(GET /apk-vm/setup)返回 ok、platform、sdkRoot、adbPath、 emulatorPath、avdmanagerPath、sdkmanagerPath、emulatorVersion、systemImage + systemImagePresent、avdName + avdPresent + avdPath、accel { available, backend, detail }、hypervisorNotes、missing[] 和 actions[],其中 actions[] 带每个动作的可用性。
架构
┌────────────────────────────── DSH Web UI ────────────────────────────────┐
│ sidebar.panellist [apk-vm, order 30] ──click──▶ main[apk-vm] panel │
│ Client half: client.js │
│ Environment card · upload/drop · APK list · phone frame (WebCodecs │ │ canvas or PNG fallback) · Rooted debug card │ └──────────────────────────────────┬───────────────────────────────────────┘ │ same-origin HTTP + WebSocket ┌──────────────────────────────────▼───────────────────────────────────────┐ │ DSH Host: index.js (/apk-vm/ routes, ws relay, debug API, agent tool) │ │ ├─ lib/android.js AndroidVm: AVD lifecycle, adb, aapt2, input, debug │ │ ├─ lib/setup.js Environment detection, createAvd, installSystemImage │ │ ├─ lib/scrcpy.js ScrcpySession:推送 server、adb forward、H.264 解复用 │ │ │ (Annex-B → AVCC)、控制消息(RESET_VIDEO 等) │ │ └─ lib/ws.js 零依赖 RFC 6455 WebSocket 服务器 │ └──────────────────────────────────┬───────────────────────────────────────┘ │ adb ┌──────────────▼──────────────┐ │ AOSP Android Emulator │ │ Android 14 / API 34, x86_64 │ │ userdebug, adbd as root │ └──────────────────────────────┘
| 层 | 实现 | 作用 |
| VM | AOSP Android Emulator(emulator + system-images;android-34;default;x86_64) | 启动开源 Android 系统镜像;通过 adb 驱动。 |
| 安装 | lib/setup.js | 零依赖检测 SDK / adb / emulator / AVD / 硬件加速,并提供一键 create-avd 和可选的 install-image。 |
| 串流 | scrcpy 5.0.1 server(lib/vendor/scrcpy-server-v5.0.1.jar,推送到 /data/local/tmp/scrcpy-server.jar)+ lib/scrcpy.js | H.264 屏幕采集和 scrcpy 控制协议,不依赖桌面版 scrcpy 客户端。 |
| 设备桥接 | Android SDK platform-tools 中的 adb | install / launch / screencap / input / logcat / 文件推拉。 |
| 插件运行时 | DSH 插件 API(slots、locale、webServer、tools、dsh.client) | 侧边栏入口、主面板、同源 HTTP/WebSocket 控制面、智能体工具。 |
不使用任何 npm 依赖:Host 端只导入 Node 内置模块,client.js 是预构建的浏览器 bundle,插件没有构建步骤。
快速启动:四层
| 层 | 做什么 |
| 硬件 CPU 加速 | 运行 emulator-check accel;AEHD / WHPX / KVM / HAXM / HVF 可用时,模拟器获得硬件虚拟化。x86_64 镜像绝不悄悄退回纯 TCG(requireAccel: true 会快速失败并给出明确错误)。 |
| Host GPU | -gpu host 用真实 GPU 渲染 VM,而不是 SwiftShader;在无头 / 无 GPU 主机上切换 gpuMode。 |
| 快速启动快照 | 首次冷启动成功后保存 adb emu avd snapshot save default_boot;之后的启动传入 -snapshot default_boot。只有 ram.img 而没有 snapshot.pb 的残留不算有效快照。 |
| 预热启动 | prewarm: true 在插件激活约 3 秒后启动 VM;autoStart: true 在首次请求 /apk-vm/state//apk-vm/frame 时启动。 |
/apk-vm/state 暴露 vm.bootKind(cold / snapshot / attach)、vm.bootMs、vm.accel、vm.gpuMode 和 vm.snapshotAvailable;面板显示 cold Ns / quick Ns。
H.264 串流与输入
默认传输方式(scrcpy: true 时):
scrcpy-server (guest H.264) → adb forward → ScrcpyVideoDemuxer → Annex-B→AVCC
→ /apk-vm/ws WebSocket → browser VideoDecoder →
- WebSocket 文本帧 init:{ codec:"h264", codecString, description (avcC, base64), width, height, maxFps, bitrate }。
- 二进制帧:[0]=1][1=keyframe flag][2..9]=pts (int64 LE)][10..]=AVCC NAL units]。
- 客户端连接到已在运行的会话时,会触发 scrcpy 控制消息 RESET_VIDEO(type 17),从而收到新的关键帧;客户端会丢弃关键帧之前的增量帧,画布因此不会解码出乱码。
- 会话由所有已连接的客户端共享;最后一个客户端断开后,经过一段宽限期会停止 scrcpy 编码器(VM 继续运行)。停止或重启 VM、卸载插件都会停止推流。
- 输入与推流路径相互独立:/apk-vm/input 仍是唯一的输入通道(面板不会绕过它),所以即使回退到 PNG 模式,输入依然可用。
针对 scrcpy 5.0.1 实现这套协议时整理的协议笔记在 spike/scrcpy/PROTOCOL.md。
HTTP 控制面(同源,仅限本地)
所有路由都会拒绝跨源请求;WebSocket 升级也按同样方式校验。JSON 请求体上限 64 KiB;原始 APK 上传上限 500 MiB。
| 方法 | 路径 | 用途 |
| GET | /apk-vm/state | VM 状态、serial、启动时间/方式、加速/GPU、root/debug 状态、显示尺寸、当前 APK、推流计数器、日志尾部。 |
| GET | /apk-vm/setup?force=1 | 环境报告(SDK/adb/emulator/tools/image/AVD/加速,缺失项,可执行操作)。缓存约 15 秒。 |
| POST | /apk-vm/setup | {"action":"refresh"\|"create-avd"\|"install-image"\|"install-tools"\|"bootstrap-sdk"}。install-tools/bootstrap-sdk 可接受可选的 packages;所有下载操作都要求 allowSetupDownloads: true(否则返回 403),且绝不会自动执行。 |
| GET | /apk-vm/apks | 在配置的根目录下发现 .apk 文件。 |
| GET | /apk-vm/apk-info?path=… | 通过 aapt2 读取静态 APK 元数据(包名/版本/targetSdk/权限/特性/原生代码/启动 Activity/图标路径)。 |
| GET/HEAD | /apk-vm/apk-icon?path=… | 从 APK ZIP 中提取位图启动图标(PNG/WebP/JPEG)。 |
| GET | /apk-vm/devices | 列出 adb 设备(serial、state、kind、model、product、transport),以及当前设备/受管设备的元数据。 |
| POST | /apk-vm/device | {"serial":"<adb-serial>"} 连接在线的物理设备或网络设备;{"serial":""} 断开连接并恢复受管 AVD。 |
| POST | /apk-vm/vm | {"action":"start"\|"stop"\|"restart"\|"snapshot"}。 |
| POST | /apk-vm/install | {"path":"C:/path/app.apk"} → 安装并启动。 |
| POST | /apk-vm/upload | 原始 APK 字节(≤ 500 MiB),文件名放在 x-apk-name 请求头 → 校验 ZIP/APK 魔数,写入操作系统临时目录,安装并启动。 |
| GET | /apk-vm/frame?t=… | 最新的 PNG 帧(VM 未就绪时返回 409)。 |
| POST | /apk-vm/input | tap / swipe / key / text 载荷。 |
| POST | /apk-vm/touch | scrcpy 多点触控控制(视频帧坐标系下传 {"action":"down\|move\|up\|cancel","pointerId":0,"x":…,"y":…}),用于双指缩放。 |
| GET/POST | /apk-vm/clipboard | 读取设备剪贴板(GET,scrcpy GET_CLIPBOARD),或写入文本(POST {"text":…})。 |
| POST | /apk-vm/stream | {"fps":1–10} — set the PNG fallback target frame rate. |
| GET (upgrade) | /apk-vm/ws | WebSocket H.264 stream (see above). |
| GET | /apk-vm/instances | List default + extra VM instances, including owner, lastUsedAt, idleMs, idleTimeoutMs, videoClients. |
| POST | /apk-vm/instances | {"name":"agent-a","start":true} — create an isolated AVD/VM; every extra instance has its own H.264 stream. |
| DELETE | /apk-vm/instances/:id | Stop/dispose an extra instance (the default cannot be destroyed). |
| GET/POST | /apk-vm/instances/:id/... | Per-instance mirror of state, frame, vm, input, install, upload, debug/. |
Uploaded APKs are written under <os temp>/dsh-apk-vm-uploads and that directory is removed
when the plugin is disposed (best-effort stale-file cleanup also runs before each upload).
Rooted debug surface
The VM image is the AOSP android-34;default;x86_64 userdebug build. After every boot the
Host half prepares a rooted debug environment and captures it in the quick-boot snapshot:
- adb root by default. _prepareDebugSystem() runs adb root, waits for adbd, and verifies
uid=0(root). The image has ro.debuggable=1 and ro.adb.secure=0, so there is no adb
authorization prompt. State reports root: true plus identity (debugBuild, adbSecure).
- Debug-friendly settings. adb/development settings enabled, package verifier and
adb-install verification disabled, persist.sys.usb.config=adb, root-access property set.
- On-device toolkit at /data/local/tmp/dsh/debug.sh (+ README.txt, mode 755):
info | logs [lines] | prop <name> [value] | pkgs | install <apk> | help.
- Snapshot persistence. Root state, settings and the toolkit are captured by
adb emu avd snapshot save default_boot; a quick boot restores them and adb root is
re-applied idempotently.
- The emulator accepts only -prop qemu. arguments, so root/debug state is applied right after
boot (not faked via a -prop ro. flag) and then snapshotted.
Debug HTTP API (same origin)
| Method | Path | Operation |
| GET | /apk-vm/debug/info | serial, identity (Android/API/ABI/debug), root, toolkit, display, uptime. |
| POST | /apk-vm/debug/shell | {"command":"id; getprop …"} → rooted shell (bounded stdout/stderr/code). |
| GET | /apk-vm/debug/logcat?lines=200&buffer=main&filter=… | bounded logcat dump; buffer=crash for the crash buffer. |
| GET/POST | /apk-vm/debug/prop | read (?name=) / write ({"name","value"}) system properties. |
| GET | /apk-vm/debug/packages | third-party (or ?all=1 all) packages with APK paths. |
| POST | /apk-vm/debug/root | ensure adbd is root. |
| POST | /apk-vm/debug/push | {"localPath","remotePath"} (remote under /data/local/tmp, /sdcard, /storage/emulated/0). |
| POST | /apk-vm/debug/pull | {"remotePath","localPath"} (local confined to APK roots + temp). |
| POST | /apk-vm/debug/reboot | {"mode":"system"\|"recovery"\|"bootloader"}. |
| POST | /apk-vm/debug/action | force-stop / clear-data / uninstall by package name. |
| POST | /apk-vm/debug/rotate | {"orientation":"portrait"\|"landscape"\|"left"\|"right"\|"upside-down"} or rotation 0..3; locks the guest rotation. |
| GET | /apk-vm/debug/ui | UIAutomator hierarchy XML (bounded to 500 000 chars) plus node count and guest remote path. |
| POST | /apk-vm/debug/record | {"seconds":1–60} (default 5) → screenrecord MP4 pulled to the host temp dir; returns localPath and byte size; the panel auto-downloads it. |
| GET | /apk-vm/artifacts?name=… | Download a screenshot/recording previously written by the plugin into the OS temp dir (basename whitelist, no arbitrary paths). |
| GET | /apk-vm/debug/perf | load average, /proc/meminfo totals/free/available and the first lines of dumpsys cpuinfo. |
| GET/HEAD | /apk-vm/debug/screenshot | PNG download (Content-Disposition: attachment). |
Agent tool apk_vm
Registered globally through ctx.tools, so every DSH agent can operate the rooted VM directly:
info, shell, logcat, getprop, setprop, packages, install, push, pull,
reboot, root, force_stop, clear_data, uninstall, rotate, ui_dump, record, perf,
screenshot, high-level input tap / swipe / long_press / key / text (device coordinates),
UI automation find_ui / assert_text / tap_text (UIAutomator text/resource-id/content-desc/class),
clipboard clipboard_get / clipboard_set,
static apk_info (metadata without booting a VM),
device management (devices, attach_device, detach_device),
plus start / stop and instance management (list_instances, create_instance,
destroy_instance). It calls the same Host methods as the HTTP routes and the panel (no
duplicated logic).
For concurrent Agents, pass instance to target a specific VM; when omitted, the tool resolves
the calling Agent's assigned instance (first Agent → default VM, later Agents → isolated
instances created on demand until maxInstances). Each extra instance has its own H.264
WebSocket (/apk-vm/instances/<id>/ws) and falls back to PNG if WebCodecs is unavailable.
UI automation is live-verified against a multi-instance VM: find_ui {"text":"Phone"} returned
Phone @ 102,1121, assert_text {"text":"Phone"} found one node, tap_text {"text":"Phone"}
tapped that center and opened the app, and key HOME returned to the launcher.
Panel UI
The APK VM panel has an Environment card (readiness, missing items, Create AVD / Download
image / Re-check) and a Rooted debug card: root/shell badge, Android/API/ABI line, rooted
command input (equivalent to adb shell), Logcat / Live logcat / Crash log / Props / Root /
UI tree / Perf /
Record 5s / Rotate / Screenshot buttons, and a bounded output console.
Compatibility with dsh-better-sidebar
dsh-better-sidebar is the community sidebar
workbench (right-sidebar tabs, bottom workbench, file viewers). When it is active, this plugin
registers a real tab type through its documented service — no forks, no DOM hacks, no hard dependency:
- The client half defers with ctx.inject(['betterSidebar'], …), so the plugin keeps working unchanged on a plain DSH and simply gains the tab when the service appears.
- ctx.betterSidebar.registerTab({ id: 'apk-vm:preview', title, description, icon, order: 60,
single: true, component }) — the tab appears in the native right Sidebar's + menu and in the bottom workbench, rendered with the same VM console adapted to a narrow pane (stacked APK list, compact header, visible-gated frame polling).
- The main panel gains an "Open in side bar" action that calls
ctx.betterSidebar.openTab({ type: 'apk-vm:preview', target: 'right' }) for the active
session (with a clear message when no session is open).
- Everything degrades cleanly: the service is optional, and a missing/broken service never
affects the native main panel or the left sidebar entry.
Live-verified in the reference profile: the sidebar.right.pane.tab slot lists the occupant
dsh-better-sidebar:apk-vm:preview (active: true). The sandbox test
spike/ui-better-sidebar-smoke.cjs separately asserts
descriptor fields, localization, disposal, the "already present" path, and resilience when the
service disappears.
Performance
Measured on the reference host: Windows x64, AEHD 2.2 hardware acceleration, -gpu host
(NVIDIA RTX 5070 Ti), AOSP system-images;android-34;default;x86_64 (Android 14 / API 34).
These are observations from a single machine, not universal guarantees.
| Metric | Result |
| Cold boot (first boot; also after re-saving the snapshot for the current AVD config) | 29.5 s |
| Quick boot (snapshot restore) | 8.4 s measured (Boot completed in 8371 ms, bootKind: snapshot); 8.4–10.5 s across runs — inside the 30 s target |
| Raw H.264 stream over /apk-vm/ws (60 Hz animation test app) | ~59.3 fps (425–474 packets in 8 s, zero protocol errors) |
| Headless browser software WebCodecs decode | 318 frames / 6.32 s = 50.3 fps (~57 fps excluding ~0.7 s startup) |
| Late second client (keyframe recovery) | 234 packets / 4 s (~58.5 fps) after RESET_VIDEO |
| PNG screencap fallback | ~1.1 fps at 720×1280 (transport limit; not a regression) |
| Rooted guest | adb shell id = uid=0(root); ro.debuggable=1; ro.adb.secure=0; toolkit installed (755) |
FPS is content-driven: scrcpy only emits frames when the screen changes, so a static home
screen legitimately shows a low FPS. The 60 Hz animation test sustains ~59 fps over the wire.
The Target FPS selector applies only to the PNG fallback; the H.264 path is capped by
scrcpyMaxFps.
Verification
Automated checks (CI)
.github/workflows/ci.yml runs on ubuntu-latest with Node.js 24 on
every push and pull request (full headless suite), plus a cross-platform headless matrix
(ubuntu-latest, windows-latest, macos-latest) that runs syntax checks, setup detection,
instance lifecycle and the UI bundle smoke test:
- node --check on index.js, client.js and every lib/.js module;
- spike/ui-smoke.cjs — 136 sandbox assertions against the real client
bundle (module wrapper, registration, i18n, rendering states, POST handlers);
- spike/ui-better-sidebar-smoke.cjs — descriptor / localization / disposal / fallback assertions for the optional integration;
- spike/setup-smoke.mjs — 30 hermetic environment-detection / create-avd / install-image / setup-report checks (mocked OS layouts and exec, no SDK needed);
- spike/android/mock-device.mjs — device critical path against a mock adb (24 checks), including real aapt2 APK metadata parsing;
- spike/android/launch-plan.mjs — fast-boot argv plans (snapshot restore, fail-fast without a hypervisor, explicit TCG, -no-snapshot).
No Android emulator or AVD is required in CI. Emulator-dependent integration tests are
Windows-local only and are not suitable for hosted CI runners (nested virtualization is not
available on GitHub-hosted runners).
Reference-host verification
| Check | Result |
| Bundle install via DSH plugin_manager install_bundle | application: applied, no warnings |
| Sidebar registration (sidebar.panellist, id apk-vm, order 30) | active in the live slot tree |
| Main panel registration (main, key apk-vm) | active in the live slot tree |
| GET /apk-vm/state / GET /apk-vm/apks | 200 {"ok":true,…}; discovered the workspace sample.apk and user APKs |
| GET /apk-vm/frame while stopped | 409 with JSON error (as specified) |
| Client bundle | node --check OK; 136/136 sandbox checks pass |
| dsh-better-sidebar tab | live sidebar.right.pane.tab occupant dsh-better-sidebar:apk-vm:preview; smoke test passes |
| Host/backend modules | node --check OK; lib/android.js smoke tests 11/11; mock-device 24/24 |
| Environment detection | spike/setup-smoke.mjs 30/30 (hermetic, runs on CI) |
| scrcpy relay | 160 assertions incl. a fake-device end-to-end protocol test; real emulator measured ~59 fps |
| H.264 / WebCodecs | raw stream 59.3 fps; headless software decode 50.3 fps; late-client keyframe recovery works |
| Fast boot | cold 29.5 s; quick boot 8.4–10.5 s (bootKind: snapshot) |
| Rooted guest / debug API | root:true, debugPrepared:true; /debug/info, /debug/shell, /debug/logcat, /debug/prop, /debug/packages, snapshot save all returned ok |
| Agent tool apk_vm | registered through ctx.tools; DSH reported the updated tool schema |
The APK upload, environment card, and the added device actions (rotate / UI tree / record / perf /
screenshot download) are implemented in this release and covered by the syntax and headless
suites where applicable; the measured table above only lists values that were actually observed.
No screenshots are published yet (see Demo).
Run the same headless suites locally (Windows or any OS) from the repository root:
bash
node --check index.js
node --check client.js
for f in lib/.js; do node --check "$f"; done
node spike/ui-smoke.cjs
node spike/ui-better-sidebar-smoke.cjs
node spike/setup-smoke.mjs
node spike/android/mock-device.mjs
node spike/android/launch-plan.mjs
spike/android/mock-device.mjs needs a real aapt2; install Android SDK build-tools or point
ANDROID_HOME / ANDROID_SDK_ROOT at an SDK that has them.
Troubleshooting
The Environment card says "Setup needed"
The report lists each missing item: SDK path, adb, emulator, sdkmanager/avdmanager, the
configured system image, the AVD, or CPU acceleration.
- Missing SDK/emulator: install Android SDK packages and/or set sdkRoot (or ANDROID_HOME /
ANDROID_SDK_ROOT).
- Missing system image: install it with sdkmanager, or set allowSetupDownloads: true and use
Download image in the card (large download; never automatic).
- Missing AVD: use Create AVD in the card (requires the system image and avdmanager) or run
avdmanager create avd -n dsh_apk -k "system-images;android-34;default;x86_64".
- Missing acceleration: see the next section.
"No usable CPU hypervisor (AEHD/WHPX) was found"
The plugin fails fast (requireAccel: true by default) because an x86_64 system image cannot
boot under pure software TCG. Check with "$ANDROID_HOME/emulator/emulator" -accel-check, then:
- Windows (AEHD): install the extras;google;Android_Emulator_Hypervisor_Driver package or
the standalone AEHD installer, then reboot. Conflicts with Hyper-V/WHPX, WSL2, memory
integrity/VBS, or vendor emulator drivers (MuMu, BlueStacks, LDPlayer, Nox) can prevent AEHD
from loading or cause an installer error such as 1450 — stop/disable the competing driver, reboot, and retry. Do not run two hypervisor stacks at the same time.
- Windows (WHPX): enable the Windows Hypervisor Platform feature and reboot. If the
Windows component store is damaged, enabling WHPX can fail (for example DISM error 14098);
repair Windows or use AEHD instead.
- Linux: enable KVM in the firmware, load the KVM module, and make sure your user can open
/dev/kvm.
- macOS: HVF is built in; make sure the machine is not running inside another VM.
Setting requireAccel: false / accel: off is only useful for diagnostics — the x86_64 guest is not expected to finish booting that way.
AEHD installed but the emulator is slow or refuses to start
- Confirm the driver is running (sc query aehd on Windows) and that virtualization is enabled
in the BIOS/UEFI.
- A stale vendor driver (for example MuMu's MuMuVMMDrv) can hold AMD-V/VT-x. Stop/disable it
and reboot. (Restore it later if you still use that emulator.)
- A reboot is required after changing hypervisor-related Windows features or drivers.
Emulator / scrcpy port conflicts
| Port | Used for | Fix |
| 5558 (port) | Emulator console port | Change port, or close the stale emulator/adb process. |
| 27183 (scrcpyPort) | Local adb forward for the scrcpy tunnel | Change scrcpyPort. |
| 5037 | adb server | adb kill-server if a stale or foreign adb server owns it. |
Quick boot fails: "different AVD configuration", black screen, or no snapshot
- The snapshot is only valid for the AVD configuration that created it. After changing the AVD
(API level, ABI, resolution, GPU mode, -writable-system, …) delete the old snapshot and let the plugin create a new one: remove
~/.android/avd/<avdName>.avd/snapshots/<snapshotName> (on Windows,
%USERPROFILE%\.android\avd\<avdName>.avd\snapshots\<snapshotName>), then start cold again.
- A snapshot directory with ram.img but no snapshot.pb is not a valid snapshot; the plugin
ignores it. Delete it or set quickBoot: false once to force a clean cold boot.
- If a stale offline adb emulator is attached, adb kill-server (or kill the leftover
emulator/qemu-system- process) and retry.
APK upload fails
- The upload endpoint accepts raw .apk/ZIP bytes up to 500 MiB; larger files return 413
and non-ZIP content returns 400. Use Install from path for unusual cases.
- Only one install (path install or upload) can run at a time — a concurrent request returns 409; wait for the current install to finish.
- A file that installs on a phone can still be rejected by the emulator (ABI/split APK); the
error is shown in the panel and in /apk-vm/state.
The sidebar panel does not update after install/upgrade
- Reload the DSH Web page so the new client.js bundle is loaded; a long-lived tab may keep the
old bundle cached.
- If API requests return 401/403 after a DSH restart, sign in / refresh the session — the DSH Web GUI requires its auth token, and the plugin routes live behind the same server.
- Check the plugin log tail in the panel (/apk-vm/state → vm.logTail) and the DSH host log.
Low FPS / a blank canvas
- A static screen legitimately shows low FPS — scrcpy only encodes frames when the display changes. Measure with an animation, not the home screen.
- A blank canvas that never receives a keyframe: reload the page; the panel requests
RESET_VIDEO on connect. If the browser has no WebCodecs/WebSocket, the panel falls back
to PNG polling (~1 fps) automatically.
- The Target FPS selector changes only the PNG fallback; use scrcpyMaxFps for H.264.
APK is not listed / aapt2 errors
- Add the project directory to roots; the DSH working directory, process.cwd() and the home
directory are searched automatically.
- Install Android SDK build-tools (provides aapt2) and make sure ANDROID_HOME /
ANDROID_SDK_ROOT points at the SDK, or set the SDK root via config.
- A malformed or non-APK file is rejected by aapt2 dump badging; the panel surfaces the error.
macOS / Linux notes
SDK/adb/emulator/AVD discovery covers the standard per-OS layouts (including Homebrew and
/opt style installs), but end-to-end emulator + streaming behavior has only been verified on
Windows x64 so far. If you hit a platform issue, include /apk-vm/setup output in the bug
report — it lists exactly which paths were detected.
Security
This plugin intentionally exposes powerful local control over an Android emulator. Read this
before pointing the DSH Web UI at an untrusted network.
- The plugin manages processes on your machine. It spawns the Android emulator, adb,
sdkmanager and avdmanager with child_process.spawn(exe, argv) — never through a shell — so user-supplied strings are not shell-interpreted. Paths are validated (APK paths must exist and end in .apk; push/pull paths are restricted to /data/local/tmp, /sdcard, /storage/emulated/0 and allowed local roots).
- The guest is deliberately rooted and debuggable. adb root, ro.debuggable=1 and
ro.adb.secure=0 mean anything you install inside the VM is not sandboxed from root. Do not
put real secrets or personal data in the AVD.
- The debug HTTP API is a local root shell for the guest. /apk-vm/debug/shell runs
arbitrary commands inside the emulator* (not on the host), and /apk-vm/debug/action can
uninstall packages. Requests are same-origin only and bounded, but any page/extension that can
make same-origin requests to your DSH Web server can exercise them.
- Upload and environment setup are privileged local actions. Upload accepts only APK/ZIP
bytes up to 500 MiB and installs them into the guest; create-avd writes an AVD; and
install-image runs sdkmanager --install only when the user opted in with
allowSetupDownloads: true (default false). All of these are reachable only from the same
origin.
- The apk_vm Agent tool is available to every agent in the DSH profile. Grant agent access
to this plugin with the same care as giving an agent a terminal on an Android device.
- Loopback-only by default. In addition to the same-origin check, every /apk-vm/ HTTP
route and the WebSocket upgrade reject non-loopback clients unless allowRemote: true is
explicitly set (for use behind your own authenticated reverse proxy). This means exposing the
DSH Web UI on the LAN does not automatically expose the root debug API.
- Keep the DSH Web server local. If you expose the DSH Web UI beyond localhost (reverse
proxy, LAN bind, tunnel), you also expose the plugin routes. Add authentication at the proxy
layer or keep the port local; do not disable the same-origin/loopback checks.
- No telemetry. The plugin makes no network requests of its own to third-party services;
it only talks to the local adb/emulator and the same-origin DSH Web server (the opt-in
install-image action runs the user's own sdkmanager, which does contact Google's SDK
repository).
To report a vulnerability, follow SECURITY.md — please use private disclosure, not a public issue.
Roadmap
The features below are not shipped yet — they are tracked for future releases. Everything described above is implemented in this release.
- Physical-device switching — first-class selection/attachment of adb-attached physical devices from the panel (config serial already supports attaching one; multi-AVD switching is shipped via /apk-vm/avds + /apk-vm/avd and the Environment card).
- Audio streaming — forward guest audio alongside the video stream.
- Cloud / remote VM execution — run the guest on a remote host and stream it to the panel.
- Static APK preview (optional)* — inspect an APK's manifest, resources and launcher UI without booting a full VM.
QEMU + Android-x86 for hosts without hardware virtualization and -writable-system image
customization remain research notes in DESIGN.md §8.
Known limitations
- Windows x64 is the only end-to-end verified platform. SDK/adb/emulator/AVD discovery is
implemented for Windows, macOS and Linux, but emulator + H.264 behavior on macOS/Linux (and
arm64-v8a images on Apple Silicon) is unverified.
- Hardware virtualization is mandatory for the x86_64 AOSP image; pure TCG fails by design.
- Quick boot is snapshot-based. Changing AVD configuration invalidates the snapshot; recreate
it as described in Troubleshooting.
- H.264 FPS is content-driven. Static screens show low FPS; the PNG fallback is ~1 fps by
transport limitation.
- The rooted debug API is local-only by design. Do not expose the DSH Web port to untrusted
networks.
- No audio and no physical-device switching yet (multi-AVD switching is available; see
Roadmap).
Contributing
Contributions are welcome. Start with CONTRIBUTING.md for the development
workflow, repository layout, the checks to run before a PR, and the rules for the UI half
(theme tokens, localization, no new npm dependencies). All participants are expected to follow
CODE_OF_CONDUCT.md. Security issues should follow SECURITY.md.
By contributing you agree that your contributions are licensed under the repository's Apache-2.0
terms. Release mechanics are in docs/RELEASING.md.
Quick reference for a change:
bash
node --check index.js && node --check client.js
for f in lib/*.js; do node --check "$f"; done
node spike/ui-smoke.cjs
node spike/ui-better-sidebar-smoke.cjs
node spike/setup-smoke.mjs
node spike/android/mock-device.mjs
node spike/android/launch-plan.mjs
node spike/instances-smoke.mjs
node scripts/privacy-scan.mjs
Use .github/ISSUE_TEMPLATE/bug_report.yml for bugs and
.github/ISSUE_TEMPLATE/feature_request.yml for
features. Emulator integration testing is Windows-local only (see
CONTRIBUTING.md); hosted CI runs the headless suites only.
License
Licensed under the Apache License, Version 2.0 — see LICENSE. Third-party components and attribution are listed in NOTICE.
- scrcpy — © Genymobile and contributors, Apache-2.0. The server JAR lib/vendor/scrcpy-server-v5.0.1.jar is redistributed unmodified under that license and is pushed to the guest at runtime; the desktop scrcpy client is not used.
- AOSP Android Emulator / system images / adb — © The Android Open Source Project, Apache-2.0. They are installed separately through the Android SDK and are not bundled in this repository.
- dsh-better-sidebar — a separate community plugin (not bundled here); the optional integration uses its published service only.
This project is not affiliated with or endorsed by Genymobile, the Android Open Source Project,
or the DeepSeek Harness project. "Android" is a trademark of Google LLC.预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
yuxiaole-bili/dsh-apk-vm
在侧边栏运行并调试安卓应用,画面流畅可交互
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,225未汉化DSH
73.6
新词雷达zhuyansen/new-term-radar
new-term-radar新词雷达
★ 2,049已汉化MCP
72.8
插件生态导航0xsline/awesome-deepseek-harness
安装 贡献指南 DeepSeek Docs 公开插件目录 Issues
★ 1,188已汉化DSH
72.6
技能注册中心iflytek/skillhub
企业级开源智能体技能注册中心——发布、发现并管理组织内可复用的技能包。
★ 5,370已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13941 个插件含 PR 收录与…
★ 355已汉化DSH
71.8
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7
这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。