← Back to home@Thatgfsj

dsh-phone

dsh-phone:给 DeepSeek Harness 提供运行在后端、经浏览器右侧栏呈现的 Android 实例(含 phone_* 工具)

Stars
0
Language
TypeScript
Created
Oct 24, 2026
Updated
Oct 26, 2026

Introduction

dsh-phone

肥鱼的手机:给 DeepSeek Harness(DSH)提供一台运行在后端、经浏览器右侧栏呈现的 Android 实例,并注册 9 个 phone_* 工具供 Agent 直接操作。

DSH version license: MIT files ≤1000 lines

版本与状态

v0.3.0(已发布):开箱即用 + 启动速度——首次使用可「一键准备」运行环境(安装 emulator/系统镜像/AVD,进度日志,完成后自动启用);emulator 温启动 ~15s、暂停恢复 ~19s(冷启动 33–95s 有客观下限)。同时包含 attach.endpoint 生效修复、Agent 通道安全收紧、视频流双页互踢/WS 背压修复(#73)、Release tgz/install-check 安装可用性。发布说明见 CHANGELOG.md,验收状态见 docs/verification.md。

能力

  • 一键准备运行环境:首次使用/设置页可一键安装 Android SDK Emulator + system image + 创建 AVD(Provisioning API,进度/日志/幂等),完成后自动启用;仅用户点击、浏览器同源触发,Agent token 不能自动安装软件。
  • 9 个 phone_* 工具:phone_status / phone_screenshot / phone_tap / phone_swipe / phone_key / phone_input_text / phone_app_launch / phone_uiautomator_dump / phone_shell。工具出现在 Agent 工具列表 = 插件开关已打开。
  • 三种运行时 + auto 探测:redroid(Docker 容器)/ Android SDK Emulator(KVM / WHPX / HVF,headless AVD)/ attach(连接用户自己的模拟器或真机)。
  • 懒启动与分级休眠:首次工具调用或首次打开面板才拉起实例;20 分钟无交互 → paused,30 分钟或资源紧张 → stopped。attach 永不被插件停止;lifecycle.prewarm(默认 false)可在插件加载后预热。booting 时 phone_status 返回 bootMs 与 phase(launching/waiting-adbd/connecting-adb/ready),面板展示启动进度。
  • 双链路解耦:Agent 只看按需单帧截图(约 100–300ms、零持续开销);人看 scrcpy H.264 视频流(默认 720p / 15fps / 2Mbps,仅面板可见时传输)。Host 零转码;浏览器 WebCodecs 硬解,TinyH264 WASM 软解兜底。
  • 右侧栏面板与设置页:面板是右侧栏的一个 tab(不遮蔽官方右栏),可从会话头部手机按钮或设置页打开;加载后 best-effort 自动打开(pending 时由面板首屏直接征询同意)。设置页维护「一句话」、显示解析状态/enabled 只读状态、提供重置入口与「打开手机面板」按钮。
  • 首次接入「一句话」:用户填一句话(如「用的雷电模拟器,装在默认位置」),操作 Agent 用本地 token 通道完成探测、连通验证与定点配置写入。

平台与前置条件

平台首选运行时前置条件
Linuxredroid(Docker)Docker Engine;binder_linux 已加载(/dev/binder 存在)
WindowsAndroid SDK Emulator(WHPX)Android Studio / SDK Emulator;开启「虚拟机平台 / Hyper-V」,BIOS 打开虚拟化
macOSAndroid SDK Emulator(HVF)Android Studio / SDK Emulator(Hypervisor.framework 默认可用)
任意attach(本机模拟器或真机)实例已在运行且 adb TCP 端口可达(如 127.0.0.1:5555)
  • runtime: auto 的探测顺序(不跳步):redroid → emulator → attach → 失败并给出平台安装指引。显式配置 runtime 时跳过探测。
  • Windows 下 WSL2 + redroid 属进阶路径,不进入默认推荐,仅在用户主动询问时提供指引。
  • 插件不安装任何软件、不修改模拟器自身配置;前置条件不满足时只输出安装指引。

安装

v0.3.0 已可用。 要求:DSH >=0.2.1-rc.2 <0.3.0、Node >=20;宿主浏览器需支持 WebCodecs(Chrome / Edge 等),不支持时自动回退 TinyH264 软解。

推荐安装 GitHub Release 的预构建 tgz(包内已有 lib/,无需 npm run build):

# 1) 从 Releases 的 Assets 下载 dsh-phone-<version>.tgz(或 gh CLI)
gh release download v0.3.0 -R Thatgfsj/dsh-phone -p 'dsh-phone-*.tgz'
# 2) 安装到 web profile
dsh plugin --profile web add ./dsh-phone-0.3.0.tgz
# 3) 必需:确认 bundle 真的被选中,再重启 DSH + 刷新页面(F5)
dsh --profile web --dump-config | grep -n -A 8 'dsh-phone'
  • 没有 dsh-phone 行 = 半安装(dependency 已装、但 dsh.profile.bundles 缺):在 Plugins 页启用/重载, 或把 dsh-phone 加入 profile package.json 的 dsh.profile.bundles 后重启;四项对齐检查与 scripts/install-check.mjs 见 docs/install.md §1.2。
  • 不要执行 dsh plugin add dsh-phone:npm 上同名包属于另一个项目(PonyDevAI/dsh-phone),本仓库未发布 registry。
  • 改源码/调试:源码目录安装需 npm install && npm run build(否则无 lib/ → 半安装);从 github: 安装 需要 prepare 构建且 profile 批准构建脚本,仍推荐 Release tgz(见 docs/install.md §2)。
  • 已有一台运行中的 emulator/真机:优先 attach 复用(写 attach.endpoint),不要再起第二台;见 §1.4。
  • 安装/更新/启用后必须重启 DSH 并刷新页面(F5):两者作用不同,只做其中一个会出现「工具可用但没入口」或「页面有 UI 但 API 404」,详见 docs/install.md §1.3。
  • 完整步骤(临时 profile 验证、升级、卸载、离线安装)见 docs/install.md。

首次接入(一句话)

  1. 用户在「设置 → 手机」的「一句话」里描述模拟器位置(原文永久保留,插件不做规范化)。
  2. 操作 Agent 读取这句话(dsh --profile <profile> --dump-config 中 dsh-phone 行的 config.attach.userHint),在宿主机做候选路径/端口探测(仅幂等只读命令 + 必要时启动模拟器)。
  3. Agent 用本地通道完成 R5/R1/R4:
# 本地 Agent 凭据(插件加载时生成,0600;卸载/重载会轮换)
cat "${DSH_HOME:-$HOME/.dsh}/storages/dsh-phone/agent.json"   # -> {"token":"…","baseUrl":"http://127.0.0.1:3080"}
BASE=$(node -e "console.log(JSON.parse(require('fs').readFileSync(process.env.HOME+'/.dsh/storages/dsh-phone/agent.json','utf8')).baseUrl)")
TOKEN=$(node -e "console.log(JSON.parse(require('fs').readFileSync(process.env.HOME+'/.dsh/storages/dsh-phone/agent.json','utf8')).token)")

# 1) 先验证(R5,不落盘)
curl -sS -X POST "$BASE/dsh-phone/api/attach/verify" -H 'content-type: application/json' \
  -H "x-dsh-phone-token: $TOKEN" -d '{"endpoint":"127.0.0.1:5555"}'

# 2) 取 revision(CAS 必需);单键写入(R1);resolved=true 前当前 endpoint 必须在同一进程内已验证(TTL 5min)
REV=$(curl -sS "$BASE/dsh-phone/api/config" -H "x-dsh-phone-token: $TOKEN" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).value.revision")
curl -sS -X POST "$BASE/dsh-phone/api/agent/config" -H 'content-type: application/json' \
  -H "x-dsh-phone-token: $TOKEN" -d "{\"path\":[\"attach\",\"endpoint\"],\"value\":\"127.0.0.1:5555\",\"revision\":$REV}"
curl -sS -X POST "$BASE/dsh-phone/api/agent/config" -H 'content-type: application/json' \
  -H "x-dsh-phone-token: $TOKEN" -d '{"path":["attach","resolved"],"value":true,"revision":'$(($REV+1))'}'

# 3) 复验(R4b)
curl -sS "$BASE/dsh-phone/api/status" -H "x-dsh-phone-token: $TOKEN"
  • Agent 通道只允许写 attach.endpoint / attach.resolved(且 revision 必填);写 attach.userHint 或 onboarding.* 返回 403 E_FORBIDDEN(R2 + 防 Agent 自我授权,issue #71)。onboarding 只能由用户在设置页/引导卡片确认。
  • attach.resolved == true 时直接连现有 endpoint(R3),禁止重新探测/重写配置。
  • 面板随时可接管操作;敏感操作(支付确认、发送/删除)前 Agent 应先提示用户。

工具

工具参数说明
phone_status—任何任务第一步;返回 state/runtime/platform/bootMs/phase/endpoint 等(cpuPct/memPct 尽力而为,可能缺省)
phone_screenshot—单帧截图,图片内容块;附件不可用时降级文本
phone_tapx, y坐标必须来自最近一次截图或控件树
phone_swipex1, y1, x2, y2, durationMs滚动、解锁、切页
phone_keynameHOME/BACK/RECENTS/ENTER/DEL 等 keyevent
phone_input_texttext支持中文(scrcpy INJECT_TEXT,非 shell input text)
phone_app_launchpkg按包名启动(monkey LAUNCHER intent)
phone_uiautomator_dump—控件树 XML,与截图构成双通道校验
phone_shellcmd默认 ask 审批;仅 getprop / wm size / dumpsys 只读子集免确认

其中 phone_status/phone_screenshot/phone_uiautomator_dump 无参数;其余必填参数如上。state=booting 时工具返回可重试的 E_BOOTING(带 bootMs),不要当失败中断任务。

首次使用守卫:onboarding.status 为 pending/declined 时,9 个工具都返回 E_ONBOARDING_REQUIRED/E_ONBOARDING_DECLINED(retryable:false,pending 带 onboarding 提问与安装指引), 不触碰设备/运行时;用户明确答复后写 onboarding.status(+ choice)即放行。详见 docs/usage.md。

配置

dsh-phone 配置节点(完整说明与变更权见 docs/config.md):

字段默认值变更权
enabledtrue仅用户;非 volatile,改动需重载插件
runtimeauto仅用户;auto|redroid|emulator|attach
attach.endpoint""volatile;接入流程验证后单键写入
attach.userHint""volatile;仅用户在设置页写,原文保留
attach.resolvedfalsevolatile;缓存锚点,重置权属用户
onboarding.status"pending"volatile;首次使用守卫(pending/accepted/declined),旧配置无该段也按 pending
onboarding.choice""volatile;""|emulator|redroid|attach,accepted 且非空时等价显式 runtime
redroid.image / memLimit / vcpuLimitredroid/redroid:14-latest / 2g / 2用户(模拟器参数)
emulator.avdName / resolution"" / 720x1280用户;空 avdName 表示首次自动创建
stream.maxSize / maxFps / bitrate720 / 15 / 2000000归插件管,Agent 不调整
lifecycle.idleTimeoutMs / pausedTimeoutMs1200000 / 1800000用户(20 分钟无交互 → paused)
lifecycle.prewarmfalse用户(插件加载后预热,加速首次打开)
limits.maxInstances1固定(首版单实例)

配置写入只走 ctx.settings.mutate(单键 + revision),落盘由 DSH configEditor 用 YAML Document 保留注释/顺序、原子写、失败回滚;插件不读写 cordis.patch.yml。

本地 HTTP 与 Agent 通道

前缀 /dsh-phone/api;浏览器请求经 ctx.connection.requestRejection 同源鉴权,响应 {ok:true,value} / {ok:false,error:{code,message,retryable}},config/status/screenshot 一律 no-store:

方法/路径用途
GET /status实例状态;onboarding pending/declined → 200 结构化 E_ONBOARDING_*(不碰设备)
GET /config配置快照(含 onboarding)+ settings revision(浏览器/设置页)
POST /config单键写入(浏览器设置页;支持 attach 三键 + onboarding 两键)
POST /attach/verify连通验证(connect + getprop ro.product.model),不落盘(R5);仅 loopback + 端口 ≥1024
GET /provision一键准备状态(state/step/percent/log/error)
POST /provision启动/复用一键准备(浏览器同源;emulator/redroid/attach;幂等)
POST /agent/config本地 Agent 单键写入(loopback + 无 Origin + x-dsh-phone-token)
GET /screenshot按需单帧 PNG
  • Agent 通道文件:$DSH_HOME/storages/dsh-phone/agent.json(默认 ~/.dsh/...,权限 0600;插件卸载即删除)。token 只在本机 loopback 且无 Origin 时生效,重载后轮换。
  • WS /dsh-phone/stream:首条 hello,下行 DPH1 8 字节头 + H.264 Annex-B,详见 docs/protocol.md。
  • 安全:只监听 loopback;不提供跨机访问面;不打印完整 shell 命令。

缓存与写入规则(SPEC §6.2)

规则实现
R1 定点合并允许写 attach.endpoint / attach.resolved / attach.userHint 与 onboarding.status / onboarding.choice;其余路径 400 E_BAD_PATH(Agent 通道仍禁写 userHint)
R2 原文保留userHint 永不规范化/删除;本地 Agent 通道写它返回 403 E_FORBIDDEN
R3 缓存命中resolved=true 直接连现有 endpoint,不重新探测、不重写配置
R4 写后双验证写后 describe 回读比对;写 endpoint/resolved 后强制重连新端点并在新连接上 getprop 复验(不满足 → 502,issue #70)
R5 先验证后落盘写 endpoint 前必须 /attach/verify 成功(loopback 且端口 ≥1024;记录 TTL 5 分钟),未验证/过期 409 E_NOT_VERIFIED

错误码摘要

code场景可重试
E_BOOTING实例启动/唤醒中(带 bootMs)是
E_RUNTIME_UNAVAILABLE三种运行时均不可用,附平台安装指引否
E_DEVICE_COMMAND设备命令/截图/输入失败是
E_TIMEOUTADB/操作超时是
E_DEVICE_DISCONNECTED写配置后复验未 running;或运行中断连是
E_NOT_VERIFIED未通过 /attach/verify 就写 endpoint/resolved=true否
E_CONFIG_CONFLICTsettings revision 冲突(自动重试一次后)是
E_BAD_PATH / E_BAD_REQUEST写入路径不在白名单 / 请求非法否
E_FORBIDDENAgent 通道试图写 userHint否
E_AUTH / E_ORIGIN / E_NO_WEB未认证 / 跨站 / 无 web carrier 或插件关闭否
E_STREAM_START_FAILEDscrcpy server 启动/推包失败是

目录结构

.
├── docs/            # 需求原文、校准、架构/协议、实现说明与用户文档
├── locale/          # 中英文展示元数据(meta.title / meta.description)
├── scripts/         # 构建、行数检查、scrcpy-server 下载
├── src/             # Host(config/http/phone/tools)+ runtime/adb + stream + client
├── tests/           # node --test(host/runtime/stream/client)
├── cordis.patch.yml # bundle 配置层(insert dsh-phone)
├── icon.svg         # 插件图标
└── package.json     # DSH 插件元数据(dsh.bundle / dsh.client / engines)

开发

npm install
npm run check     # tsc --noEmit + node --test + 单文件行数(1000 硬线)
npm run build     # Host 逐文件 esbuild 转译 + Client __ModuleLoader__ 单文件 bundle
npm pack --dry-run

文档随代码同 PR 更新;docs/SPEC.md 禁止改写。详见 CONTRIBUTING.md 与 AGENTS.md。

已知限制

  • 首版单实例(maxInstances: 1):工具无 serial 参数,不支持多设备。
  • 安装/更新/启用后需重启 DSH 并刷新页面:重启让 Host 行与 bundle 生效,F5 让客户端模块图重算(HMR 不会向已打开页面注入新模块);只做一半会出现「工具可用但没入口」或「有 UI 但 API 404」。刷新不中断会话/手机实例。
  • npm 包名冲突:npm 上的 dsh-phone 属于另一个项目(PonyDevAI/dsh-phone),本仓库未发布 registry;禁止按名字安装,必须用 Release tgz、源码目录或 git(见 docs/install.md §1.1)。
  • 源码/git 安装可能半安装:源码目录必须先 npm run build;从 github: 安装需要 prepare 且 profile 批准构建脚本,否则得到没有 lib/ 的包;推荐 Release tgz,并按 §1.2 检查 dsh.profile.bundles。
  • 已有 emulator 实例不会自动复用:启动 emulator runtime 可能再起一台同 AVD(端口顺延、资源翻倍);有运行中的实例时优先 attach(见 docs/install.md §1.4)。
  • 未完成的审查项:#72(phone_key 白名单/screenshot 掩码/booting 断连/control 单飞)仍在跟踪;#73(viewer 心跳/引用计数代际/WS 背压/双页 4001)已随 v0.3.0 修复。
  • 启动时间:温启动 ~15s / 暂停恢复 ~19s 为本机实测;冷启动 33–95s 有客观下限(Android 14 google_apis),aosp_atd 未实测。
  • 一键准备需用户点击且浏览器同源触发(Agent token 不允许自动安装软件);下载体积/时长取决于网络与 system image。
  • #40 部分完成:真实 emulator attach 与 scrcpy 实连已通过(#58:Host WS 102 帧/10s、控制通道/中文输入/截图 PASS);仍待人工/环境的项:emulator 自启(B 分支)、真实浏览器渲染/WebCodecs/TinyH264、redroid(本机无 Docker)、审批 UI 点击。详见 docs/verification.md §2/§7 与 §4/§5 待办。
  • cpuPct/memPct 为尽力而为:emulator/redroid/attach 各自采样(emulator /proc、redroid docker stats、attach dumpsys),取不到就省略字段,不阻断 status;不要当精确监控。
  • phone_screenshot 的模型图片能力:仅捕获 attachments.saveImage 失败并降级文本,未探测模型路由是否接受图片。
  • 一句话的候选路径/端口探测由操作 Agent 完成:插件不扫盘,只提供 /attach/verify 与单键写入。
  • 设置页 enabled 只读:设置页展示开关状态但不可编辑(非 volatile);关闭插件请用「插件管理」禁用该行,或在 profile 配置中改 enabled: false 后重载(见 docs/config.md)。
  • scrcpy-server jar 不随包分发:需运行 node scripts/fetch-scrcpy-server.mjs(或设 DSH_PHONE_SCRCPY_SERVER 指向已有 jar)。离线/镜像可用性见 docs/install.md。
  • Windows WSL2 + redroid 属进阶路径;Windows/macOS 的 emulator 参数未在本机验证。

文档

想做什么读哪个
安装 / 升级 / 卸载 / 临时 profile 验证docs/install.md
日常操作 SOP 与首次接入流程docs/usage.md
配置字段、变更权、落盘位置docs/config.md
报错与常见现象docs/troubleshooting.md
第三方依赖与许可docs/third-party.md
给 Agent 的可安装技能skill/dsh-phone/SKILL.md
需求 / 架构 / 协议 / 校准docs/SPEC.md / docs/architecture.md / docs/protocol.md / docs/calibration.md

协议

代码 MIT,见 LICENSE。第三方组件许可(含随 lib/client.js 分发的 Apache-2.0 tinyh264、运行时下载的 Apache-2.0 scrcpy-server)见 docs/third-party.md。