android_emulator
by huguangyu666
README.md
# dsh-plugin-android-emulator
把 **ZCode 官方内置插件 `android-emulator`**(MIT,© Z.ai)移植到 DeepSeek Harness。
提供 **23 个 Android 原生工具**:Gradle 构建、模拟器/真机生命周期、APK 安装与启动、截图、日志、ADB 与 UI Automator 自动化;并附带 `android-dev` 技能、`/android` 斜杠命令与原生设置面板。
> 本插件是**集成层**:不修改上游一行代码。上游产物以 MIT 许可逐字节 vendor 在
> `vendor/zcode-android-emulator/`,来源、版本与逐文件 SHA-256 见
> [PROVENANCE.md](vendor/zcode-android-emulator/PROVENANCE.md)。
---
## 1. 移植可行性结论
**可以移植,而且属于「低风险高保真」移植。**
原因:ZCode 的插件模型与 DSH 在关键处天然对齐 —— 上游插件本身就是一个
**自包含的 stdio MCP 服务器**(esbuild 打包,只 import `node:*` 内置模块),
而 DSH 有官方 MCP 桥接器 `@deepseek-ai/dsh-mcp-client`。两者对接后:
- 工具名完全一致:上游 `.mcp.json` 里服务器名是 `android-emulator`,ZCode 会规范化成
`android_emulator`,模型看到 `mcp__android_emulator__<tool>`;本插件直接把
`serverName` 设为 `android_emulator`,**产出的工具名与 ZCode 逐字节相同**,
因此上游技能文本无需改写工具名。
- 上游的 23 个工具、12 项 preflight 诊断、Gradle/ADB/UI Automator 全部逻辑
**零改动复用**,只是换了个宿主。
### 映射表
| ZCode 机制 | DSH 对应实现 |
| --- | --- |
| `.mcp.json` 里的 `${ZCODE_PLUGIN_ROOT}` | 本包内 `vendor/zcode-android-emulator/` 路径 |
| `${ZCODE_PROJECT_DIR}`(子进程 cwd) | `projectDir` 设置,默认 DSH 进程工作目录 |
| `${ZCODE_PLUGIN_DATA}` | `<DSH_HOME>/android-emulator` |
| `userConfig.*`(7 项) | `ctx.settings.register('android-emulator', ...)` 原生设置面板 |
| `skills/android-dev/SKILL.md` | `ctx.skills.registerProvider()`(`source: bundled`, `rank: 600`) |
| `commands/android-dev.md` → `/android-dev <goal>` | **DSH 原生技能手势**:输入框打 `/android-dev <goal>`,技能正文作为 instructions 注入、你的话作为任务(已实测,见 §7) |
| (本移植新增) | `ctx.tools.register()` → **`android_set_project_dir`**:让**模型自己**把 MCP 工程根目录对齐到当前会话(ZCode 靠动态 cwd 自动完成,DSH 的 `cwd` 是静态的,必须补这一环) |
| (本移植新增) | `ctx.commands.register()` → `/android`:给**人**用的状态查看 / 切换工程根目录 |
| 插件被 ZCode 发现并注入系统提示 | `ctx.systemPrompt.section()` 注入工作流段落 |
| 官方 MCP 服务器托管 | `@deepseek-ai/dsh-mcp-client` stdio 传输 + 自动重连 |
> ZCode 的 `/android-dev` 命令在 DSH 里**不需要**用 `ctx.commands` 复刻:`dsh-commands`
> 的 handler 只能返回给用户看的文本,没有「把任务派给模型」的通道。而 DSH 的技能手势
> (`/(^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?=\s|$)/`)对 `userInvocable` 技能做的事,
> 恰好等价于 ZCode 命令的 `skills:` frontmatter + `$ARGUMENTS`:技能正文进 instructions,
> 用户原话作为任务。所以这条是**宿主原生覆盖**,不是缺口。
### 与 ZCode 的差异
| 项 | ZCode | 本移植 |
| --- | --- | --- |
| 工具名 | `mcp__android_emulator__*` | **完全相同** |
| 工具超时 | 60 s | 默认 **900 s**(Gradle 首次构建很慢) |
| 工程根目录 | 随会话工作区 | 进程级固定,用 `/android dir <path>` 切换(见「已知限制」) |
| 技能资源 | `skills/android-dev/INSTALL_ENVIRONMENT.md` | 同样随包提供,`resourceBase` 指向 `assets/android-dev/` |
| 宿主运行时 | ZCode 内嵌 Node 24 | DSH 的 Electron-as-Node 24.18,自动加 `ELECTRON_RUN_AS_NODE=1` |
---
## 2. 工具清单(23 个)
全部以 `mcp__android_emulator__` 为前缀,例如 `mcp__android_emulator__android_preflight`。
| 分组 | 工具 |
| --- | --- |
| 诊断 | `android_preflight` |
| 工程 | `android_discover_project`、`android_create_app`、`android_build_app`、`android_build_and_run` |
| 设备 | `android_list_devices`、`android_list_avds`、`android_start_emulator`、`android_stop_emulator`、`android_create_avd` |
| 应用 | `android_install_app`、`android_launch_app`、`android_terminate_app`、`android_open_url` |
| 观测 | `android_screenshot`、`android_logs` |
| UI 自动化 | `android_ui_status`、`android_ui_describe`、`android_ui_resolve`、`android_ui_tap`、`android_ui_swipe`、`android_ui_type_text`、`android_ui_keyevent` |
`android_preflight` 会实际探测 12 项:Host OS、Android SDK root、插件默认值、adb、emulator、
sdkmanager、avdmanager、Java、Gradle、模拟器加速、AVD 列表、已连接设备。
---
## 3. 环境要求
- **DSH** ≥ 0.1.5-rc.1(本插件在 0.1.7-rc.2 上验证)
- **Node ≥ 24**(上游产物以 `--target=node24` 构建)。DSH 桌面版自带 Node 24.18,无需额外安装;
若宿主 Node 低于 24,插件会自动优先使用 PATH 上满足要求的 `node`。
- **macOS 或 Windows**(上游 P0 明确不支持 Linux,`android_preflight` 会如实报告)
- Android SDK:`platform-tools`(adb)、`emulator`、`cmdline-tools`(sdkmanager/avdmanager)
- 模拟器工作流:至少一个 AVD;真机工作流:开启 USB 调试的设备
- 可选:`gradle` 在 PATH 上(用于给缺 wrapper 的工程生成 Gradle wrapper)
---
## 4. 安装
### 方式一:官方插件安装器
```bash
dsh plugin --profile desktop add dsh-plugin-android-emulator
```
### 方式二:本地开发安装(推荐先 dry-run)
```bash
cd dsh-plugin-android-emulator
# 1. 还原上游产物(首次 clone 后必须执行;从本机 ZCode 安装目录复制并校验哈希)
node tools/vendor-upstream.mjs
# 2. 构建宿主产物
npm run build
# 3. 看看将要做什么改动
node tools/install-into-profile.mjs --profile desktop --dry-run
# 4. 真正安装(自动备份 profile package.json,并用 dsh --dump-config 复核合成树)
node tools/install-into-profile.mjs --profile desktop
```
安装后**下次启动 DSH 生效**(正在运行的 Host 不会热加载 profile bundles)。
卸载:`node tools/install-into-profile.mjs --profile desktop --uninstall`。
### 方式三:只做一次试验(不改任何 profile)
```bash
dsh --profile desktop --patch ./cordis.patch.yml headless "调用 android_preflight 看看环境"
```
---
## 5. 设置项(DSH 设置页 →「Android 模拟器」)
| 设置 | 默认 | 说明 |
| --- | --- | --- |
| `enabled` | `true` | 是否启用 |
| `projectDir` | `''` | MCP 工程根目录;留空 = DSH 进程工作目录 |
| `nodePath` | `''` | 运行 MCP 服务器的 Node;留空自动选择 |
| `sdkPath` | `''` | Android SDK 根目录;留空自动探测 `ANDROID_HOME` / `ANDROID_SDK_ROOT` / 常见路径 |
| `defaultAvd` | `medium_phone` | 首选 AVD |
| `apiLevel` | `35` | 建工程 / 装 SDK 包 / 选系统镜像用的 API level |
| `buildToolsVersion` | `35.0.0` | build-tools 版本(用于安装指引) |
| `systemImageVariant` | `default` | `default` / `google_apis` / `google_apis_playstore` |
| `systemImageAbi` | `''` | 留空时 ARM64 主机用 `arm64-v8a`,其余 `x86_64` |
| `jdkMajor` | `17` | preflight 与安装指引使用的 JDK 主版本 |
| `toolCallTimeoutMs` | `900000` | 单次工具调用超时(15 分钟) |
| `failOnStartupError` | `false` | 首次连接失败是否让插件加载失败 |
| `registerSkill` | `true` | 是否注册 `android-dev` 技能 |
| `promptSection` | `true` | 是否注入工作流系统提示段落 |
以上 7 项 Android 参数会以 `ANDROID_PLUGIN_*` 环境变量传给 MCP 服务器,
与上游 `plugin.json` 的 `userConfig` 一一对应。
---
## 6. 使用
### 斜杠命令(给人用)
```
/android # 查看状态:服务器路径、工程根目录、数据目录、Node 运行时、ANDROID_PLUGIN_* 环境
/android dir <path> # 切换工程根目录(自动重启 MCP 服务器,23 个工具重新注册)
```
### 原生工具 `android_set_project_dir`(给模型用)
ZCode 的工程根目录**跟着会话自动变**(`ZCODE_PROJECT_DIR = context.workingDirectory`,每次连接动态解析);
DSH 官方 MCP 桥接器的 `cwd` 只接受静态字符串,所以本移植补了一个原生工具让模型自己对齐:
```
android_set_project_dir # 不传参数 = 用当前会话的工作目录
android_set_project_dir { path } # 显式指定工程根目录
```
它做三件事:校验目录 → 按新根目录重建 MCP 桥接 → **在本地探测** `settings.gradle(.kts)` /
`gradlew` / `gradle.properties` 等标记并直接返回,所以模型一次调用就能确认对不对,
不用再多跑一次 `android_discover_project`。返回形如:
```json
{
"ok": true, "changed": true,
"previousProjectDir": "...", "projectDir": "C:\\…\\<你的安卓工程>",
"source": "argument",
"looksLikeAndroidProject": true,
"markers": ["settings.gradle.kts", "gradlew", "gradlew.bat", "gradle.properties", "local.properties"]
}
```
系统提示词里也写了「若 `android_discover_project` 找不到 Gradle 根,就调 `android_set_project_dir`」,
所以这一步是**自愈**的,不需要人插手。`isConcurrencySafe()` 返回 `false` —— 重挂是进程级操作,不能并发。
### 技能
`android-dev` 技能会出现在会话技能目录里,模型可直接加载;其工作流为:
`android_preflight` → `android_discover_project`(或 `android_create_app`)→
`android_build_and_run` → `android_screenshot` → 按需 `android_logs` / UI 自动化。
**在输入框直接打 `/android-dev <你的目标>`** 即可启动开发循环 —— 这是 DSH 的原生技能手势,
等价于 ZCode 的 `/android-dev` 命令(技能正文进 instructions,你的话作为任务):
```
/android-dev 帮我做一个计数器 App,跑起来截图给我看
```
技能还随包提供上游的 `INSTALL_ENVIRONMENT.md`(macOS / Windows 的 JDK、Gradle、
cmdline-tools、SDK 包、AVD 创建、模拟器加速逐步指引)。
> 本机已有 DSH 自带的 Android 技能(`adaptive`、`agp-9-upgrade`、`edge-to-edge`、
> `navigation-3`、`camerax`、`testing-setup`、`r8-analyzer` 等)。那些覆盖**怎么写**,
> 本插件覆盖**怎么跑起来并验证**,两者互补。
---
## 7. 验证
```bash
npm run verify # 构建 + 全部测试
npm run vendor:check # 校验 vendor 目录与上游逐文件哈希一致
```
测试金字塔(45 个用例,全部通过):
| 层 | 文件 | 覆盖内容 |
| --- | --- | --- |
| L1 契约 | `test/contract.test.mjs` | `files` 含 `cordis.patch.yml`/`vendor`/`assets`;patch 只有一个 insert;无 `export default`;无运行时依赖;产物未内联 `@deepseek-ai/*` |
| L1 完整性 | `test/vendor-integrity.test.mjs` | PROVENANCE 清单与 41 个 vendored 文件 SHA-256 逐一比对;bundle 只依赖 `node:*` |
| L2 生命周期 | `test/mock-lifecycle.test.mjs` | 用**真实** schemastery 与**真实** MCP 桥接模块 + 假 ctx,断言桥接配置、技能 provider、提示词段落、`/android` 行为、effect 作用域回收、缺服务降级 |
| L3 端到端 | `test/mcp-server.test.mjs` | 真实 spawn 上游 MCP 服务器:握手、23 个工具与入参 schema、真实 `android_preflight` 报告、`ANDROID_PLUGIN_*` 覆盖生效、cwd 决定工程根 |
### 本机实测记录(Windows + 完整 Android SDK)
**① 用 DSH 自己的运行时**(Electron 44 / Node 24.18.1,`ELECTRON_RUN_AS_NODE=1`)
拉起上游 MCP 服务器:
```
initialize: {"name":"android-emulator","version":"0.1.0"} protocol 2024-11-05
tools: 23
names: mcp__android_emulator__android_preflight, ... , mcp__android_emulator__android_build_app
preflight ok: false | checks: 12
PASS Host OS / Android SDK root / Android plugin defaults
PASS adb / emulator / sdkmanager / avdmanager / Java
PASS Emulator acceleration / Android Virtual Devices
FAIL Gradle <- 本机 PATH 无 gradle 且工程无 wrapper,符合预期
FAIL ADB devices <- 当前没有运行中的设备,符合预期
```
**② 真实 DSH 宿主端到端验收**(隔离 profile,`dsh --profile android-e2e "…"`):
```
1. Yes — `android-dev` is in my skill catalog.
2. 23 `mcp__android_emulator__` tools.
3. android_preflight: passed 11, failed 2 — failed checks: Gradle (not found), ADB devices (0 devices).
```
即:**技能注册、23 个工具注册、真实工具调用**在真实 DSH 宿主里全部跑通,
preflight 的 11/2 结果与直接探测一致(两项 FAIL 是真实环境状态,不是移植缺陷)。
**③ 原生技能手势等价于 ZCode 的 `/android-dev` 命令**(同 profile,直接以 `/android-dev` 开头):
```
$ dsh --profile android-e2e "/android-dev 我想做一个计数器 App,先别动手,只告诉我:按照技能里的
默认工作流,你的第一步和第二步分别要调用哪个工具?"
按 `android-dev` 技能里的 Default Workflow,前两步是:
| 第 1 步 | mcp__android_emulator__android_preflight | 纯诊断…若环境缺失,先按 INSTALL_ENVIRONMENT.md 处理,
不能替你接受 SDK 许可、输密码、清模拟器数据或删 AVD |
| 第 2 步 | mcp__android_emulator__android_discover_project | …并读取 warnings(缺 gradle.properties /
local.properties / wrapper 要先修)|
补充:第 2 步里内含一个分支:如果发现没有 Android 工程,才在该步调用 android_create_app…
另外注意 path 解析:这两个工具的相对路径都相对插件的 projectDir(MCP project root),不是当前 shell 目录。
```
模型复述的「这两个工具的相对路径都相对插件的 `projectDir`(MCP project root),
不是当前 shell 目录」只存在于**本移植改写后的技能正文**里(上游 SKILL.md 里没有
`projectDir` 也没有 `ambient shell directory` 这句话)—— 证明注入的确实是这个技能。
`/android-dev` 手势完整可用,不需要再用 `ctx.commands` 复刻一条。
(注:同一次回答里提到的「不能替你接受 SDK 许可、清模拟器数据」是**上游原文**,
不能作为版本证据;`projectDir` 那句才是。)
**④ 真实工程端到端**(2026-09-25,本地一个真实工程
`AiDocHelper` v4.131 —— Kotlin + Compose + Room + OpenCV + MLKit,含 ABI splits):
| 步骤 | 结果 |
| --- | --- |
| `android_preflight` | `ok=true`,12 项检查,仅 `ADB devices` 未就绪(尚无设备) |
| `android_discover_project` | `root=""`(= cwd)、`modules=[:app]`、`appIds=[com.aidoc.helper]`、4 个 APK |
| `android_start_emulator` | 复用既有 AVD `dj`,5.5s 启动,`serial=emulator-5554` |
| `android_build_app` | `:app:assembleDebug` **BUILD SUCCESSFUL in 22s**(38 tasks up-to-date) |
| `android_install_app` | 显式传 x86_64 APK,`Success` |
| `android_launch_app` | `launched=true` |
| `android_screenshot` | 1080×2400 PNG,**142,629 bytes**,App 主界面渲染正常 |
| `android_logs` | logcat 拿到 `libopencv_java4.so ... ok` / `Library opencv_java4 loaded` |
| `android_stop_emulator` | `stopped=true` |
**全程 85 秒**(首次 12:23:54 → 12:25:19),未创建任何 AVD、未安装任何 SDK 包、
未修改工程任何源文件(`git status` 干净)。
两个实测细节值得记下:
- **截图要等冷启动。** 首轮在 `launch` 后 12 秒截图,拿到的是闪屏(39 KB);
该 App 冷启动要加载 OpenCV 原生库,45 秒后才是主界面(142 KB)。
- **ARM 转译让错配 APK 也能装上**,详见 §8.5 —— 预测的 `INSTALL_FAILED_NO_MATCHING_ABIS`
没有发生,如实记录。
**⑤ 在运行中的 DSH 会话里用插件自己的工具跑完整条链**(2026-09-25 21:00,
`projectDir` 指向同一工程,全部通过 MCP 工具调用,无脚本):
| 工具 | 结果 |
| --- | --- |
| `android_discover_project` | `root=""`、`modules=[:app]`、`appIds=[com.aidoc.helper]`、4 APK、**`warnings=[]`** |
| `android_create_avd` | 首次不传 `device` → 96M/320×640 废 AVD(见 §8.6);传 `medium_phone` 后 1536M/1080×2400 正常 |
| `android_start_emulator` | `serial=emulator-5554` |
| `android_build_app` | **BUILD SUCCESSFUL in 12s**,38 tasks up-to-date;`apkPath` 又是 arm64(§8.5) |
| `android_install_app` | 显式 x86_64 APK → `Success` |
| `android_launch_app` | `launched=true`,`topResumedActivity=com.aidoc.helper/.MainActivity`,crash buffer 为空 |
| `android_screenshot` | 1080×2400 PNG,**141,507 bytes**,主界面正常 |
| `android_stop_emulator` | `stopped=true` |
清理核对:临时 AVD `dsh_tmp_test` 已删、AVD 目录回到空、无残留 `emulator`/`qemu-system`
进程、无 adb 设备、工程 `git status` 0 改动。
> 这轮真实宿主验收抓到了一个只有实机启动才会暴露的 bug:Cordis 对未在 `inject`
> 声明的服务**读取即抛错**(`cannot get property "skills" without inject`),
> 所以 `ctx.skills?.xxx` 这种「防御式探测」根本防不住 —— `?.` 之前就炸了。
> 正确做法是 `ctx.inject(['skills'], inner => …)`(服务可用时才执行)
> 与 `ctx.get('skills')`(不抛错的读取,仅用于诊断)。
> `test/mock-lifecycle.test.mjs` 现在用「直接读服务就抛错」的假 ctx 复现该契约,
> 防止回归。
---
## 8. 已知限制
1. **工程根目录是进程级的(已缓解)。** DSH 官方 MCP 桥接器的 `cwd` 只接受静态字符串
(`z.string()`,传函数会校验失败),而 MCP 服务器进程长驻,所以 `process.cwd()`
在启动时就固定了。影响面仅限两个以 cwd 为基准的调用:
`android_discover_project`(无参数)与 `android_create_app` 的相对 `dir`;
其余工具都接受显式 `projectDir` / 路径参数。
三条改法,按推荐顺序:
- **`android_set_project_dir`(模型自己调)** —— 系统提示词已写明「找不到 Gradle 根就调它」,
所以是自愈的;不带参数即对齐当前会话工作目录。
- `/android dir <path>` —— 给人用的等价命令。
- `projectDir` 设置 —— 想固定成一个路径时用。
三者都是「销毁并按新根目录重建桥接器」(约 1 秒,23 个工具重新注册),
**不是**真正的 per-session 路由:同一时刻只有一个工程根目录。
*彻底解决*:用 `@deepseek-ai/dsh-scope` 的 per-agent scope 为每个会话懒启动一个
cwd 正确的子进程、按会话路由调用。代价是 23 个工具要自己注册与转发,
放弃官方桥接器 —— 在有真实多工作区并发需求之前不值得。
2. **重连预算耗尽后工具会消失。** 这是官方桥接器的既定行为(连续 10 次失败后注销工具,
只能靠重载插件恢复)。排查顺序:`/android` 看 Node 运行时与路径 →
`android_preflight` 看环境。
3. **Linux 不受支持**(上游 P0 决策),`android_preflight` 会报告为不支持的宿主。
4. **上游 TypeScript 源码未公开。** 插件在 ZCode 仓库里以
`apps/zcode-cli/packages/android-emulator-plugin` 出现在 `pnpm-lock.yaml`,
但**不在开源快照中**,zai-org 名下也没有对应仓库。因此本插件 vendor 的是
ZCode 随包分发的编译产物 —— 它是唯一忠实的来源。`dist/providers/*.js` 是未混淆的
esbuild 输出,随包保留以便审计。
5. **ABI 拆分工程:`android_build_app` 不按 ABI 选 APK。** 上游 `pickApk()` 只按
variant 目录匹配,取第一个命中项。工程若开了 `splits.abi`(例如同时出 arm64-v8a 与
x86_64),`android_build_app` / `android_build_and_run` 会返回**arm64 那个**,
在 x86_64 模拟器上属于错配。
实测结论(2026-09-25,Windows + `dj` AVD = android-36 google_apis x86_64):
`android_build_and_run` 确实选了 `app-arm64-v8a-debug.apk`,但 `adb install`
仍返回 `Success` —— 因为该 x86_64 镜像带 **ARM 二进制转译**,arm64 包能装也能跑,
只是走转译层。所以这不是一个必然失败,而是**潜在脆弱点**:换成不带转译的镜像、
或真机 arm64 配只出 x86_64 的包时,就会以 `INSTALL_FAILED_NO_MATCHING_ABIS` 失败。
规避:显式调用 `android_install_app` 并传正确的 `apkPath`(用
`android_discover_project` 的 `apks` 列表挑选),不要依赖 `build_and_run` 的自动选择。
`android-dev` 技能里也写了这一条。
6. **`android_create_avd` 不传 `device` 会建出废 AVD。** 上游把 `device` 做成可选参数,
不传时 `avdmanager` 落到最小默认硬件档案 —— 实测 `hw.ramSize = 96M`、
`hw.lcd 320×640@160dpi`。这种 AVD 能启动、能出现在 adb 里,几秒后自己死掉,
症状很像「工具坏了」。必须显式传 `device`(如 `medium_phone`);
建完核对 `config.ini` 里 `hw.ramSize >= 1024M`。
实测对比:不传 → 96M/320×640;传 `medium_phone` → 1536M/1080×2400/4 核,正常。
7. **死掉的模拟器进程仍占着 AVD 锁。** 若 `android_start_emulator` 返回了 serial
但设备随即消失,通常是上一个 `emulator.exe` / `qemu-system-*` 还挂着。上游的
`runningAvd()` 守卫是问 adb 的,而 adb 已经看不到这具尸体,于是它又启了第二个实例,
两个互踩。处理:杀掉残留的 `emulator.exe` / `qemu-system-*`,`adb kill-server`
重启 adb,再重试 —— 不要反复调 `android_start_emulator`。
---
## 9. 来源与许可
本插件采用 **MIT**,并对上游做双重署名(代码注释 + 本文档)。
| 内容 | 来源 | 许可 |
| --- | --- | --- |
| `vendor/zcode-android-emulator/**` | ZCode 官方 `android-emulator@zcode-plugins-official` v0.1.0,© Z.ai,逐字节复制 | MIT(见其 `package.json` / `.zcode-plugin/plugin.json`) |
| `assets/android-dev/INSTALL_ENVIRONMENT.md` | 同上,逐字节复制 | MIT |
| `assets/android-dev/SKILL.md` | 上游技能文档的**衍生作品**:保留全部工作流,仅改写宿主集成章节 | MIT |
| `src/`、`build.mjs`、`test/`、`tools/` | 本移植新增 | MIT |
上游 `dist/mcp/server.js` 内嵌 `@modelcontextprotocol/server` 与 `zod`;上游以
`legalComments: "none"` 构建,bundle 内不留第三方声明,故在此说明。
ZCode 仓库本体为 Apache-2.0(`LICENSE` / `NOTICE.md` / `THIRD-PARTY-NOTICES.md`)。
`vendor/` 里的原始副本永不被手工编辑 —— `test/vendor-integrity.test.mjs` 会校验哈希,
`npm run vendor:check` 可与上游目录直接比对。
---
## 10. 开发
```bash
node tools/vendor-upstream.mjs --from "<ZCode android-emulator-plugin 目录>" # 重新 vendor
node tools/vendor-upstream.mjs --check # 与上游比对
node tools/link-dev-deps.mjs # 从 DSH profile 链接 @deepseek-ai/*(测试用)
npm run build # esbuild → lib/index.js
npm test # 仅测试
npm run verify # 构建 + 测试
```
`node_modules/`、`lib/` 均不入库;插件**没有运行时依赖**(`@deepseek-ai/*` 全部是
optional peer,由 DSH 宿主提供)。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues