WebDroid Agent
中文 | English

WebDroid Agent 是一个以浏览器为核心的 Android 手机 Agent 实验项目。静态部署时它完全在前端运行;Docker 部署时会额外启用一个本地 Node API 代理,让模型请求通过容器转发而不是从浏览器直接跨域请求。它在浏览器中通过 WebUSB/WebADB 连接 Android 设备,截取手机屏幕并发送给 OpenAI 兼容的视觉模型,再把模型返回的受控动作解析、校验并通过 ADB 执行。
项目目标不是替代人工长期托管手机,而是提供一个可以在本地浏览器中快速验证「视觉模型 + 手机控制」链路的实验环境。
在线地址 · 英文版 · Tango / WebADB
Chromium WebUSB -> Tango/WebADB -> Android ADB
静态部署:
OpenAI 官方: 浏览器 fetch -> /v1/responses -> 视觉模型
其他提供商: 浏览器 fetch -> /v1/chat/completions 或 Gemini generateContent
Docker:
浏览器 fetch -> 同源本地代理 (/chat/completions 或 /responses) -> 模型 API
目录
核心能力
- 静态部署纯前端运行,适合本地实验、Cloudflare Pages 等静态站点部署。
- Docker 部署内置同源本地 API 代理,可避免模型服务浏览器 CORS 限制。
- 通过 WebADB 在浏览器中连接已开启 USB 调试的 Android 设备。
- 截取手机屏幕,并把截图、当前 App、设备状态、完整已安装应用列表和历史步骤发送给视觉模型。
- 支持 WebDroid JSON 系列动作协议和 canonical JSON 动作格式:
webdroid_json 使用截图像素,webdroid_normalized_json 使用 Vision-Pointer 风格 0-1000 归一化坐标。
- 自动解析、规范化并校验模型返回的下一步动作。
- 通过 ADB 执行启动应用、点击、滑动、输入文本、返回、Home、长按、双击、等待等操作,支持模型显式配置等待时间。
- 支持输入前清空文本、动作后等界面稳定、瞬时模型 API 错误/模型空回复重试,以及按白名单(
action_stale 与瞬时设备错误 device_io / device_timeout,最多三次尝试后转入 awaiting_review)的自动恢复;其余失败类别一次即停。
- 支持可编辑 App Cards、本地 Custom Tools,以及只向模型暴露 ID/标签的安全 Secret 输入。
- 发送聊天消息后默认自动执行,也保留单步计划等高级调试能力。
- 支持敏感动作确认、完全无限制模式、最大步数限制、真正暂停并在断点恢复、停止运行,以及高级区中的上下文重置和运行日志导出。
- 页面配置持久化到本机浏览器
localStorage,Agent thread/turn 历史持久化到 IndexedDB。
- 每一步都用动作前后的控件树判出动作效果(
done / small_change / no_effect / place_changed),并给元素分配跨屏稳定的编号:点元素可以用 tap_ref,编号过期会明确回报而不是点错位置。
- 屏幕不可用时给出确定原因(还在加载、空白、防截屏、完全读不到、需要人工),接管卡片的类型按屏幕事实判定,而不是按模型的措辞。
- 记忆(开启后):模型用
note 记下的事实不会全量灌回提示词,而是按当前任务检索最相关的几条;写入前拦下密码、验证码、身份证号、银行卡号和 API Key。
- 技能:任务跑通后把步骤起草成技能,下次遇到同类任务作为建议注入;技能文本同样标为不可信数据。
- 回答接地校验:最终回答里的引文、带单位数字和日期会按纯代码规则核对本次运行真的读到过;对不上只在日志提示,不改写回答。
- 操作台快捷键:
Cmd/Ctrl+K 聚焦输入框、Cmd/Ctrl+B 开关配置面板、Cmd/Ctrl+J 开关运行日志;Esc 只关闭最上层弹窗(不再误停 Agent,停止请用界面上的停止按钮);手机预览与聊天区之间的分隔条可拖拽调整宽度并记住比例(双击复位)。
适合谁使用
适合用于:
- 验证 OpenAI 兼容视觉模型是否能理解真实手机界面。
- 调试手机 Agent 的 JSON 动作格式、坐标映射和自动执行流程。
- 在本地安全环境中做 Android UI 自动化原型实验。
- 想快速体验 WebUSB + ADB + 多模态模型闭环的开发者。
不建议用于:
- 支付、下单、删除、授权、账号设置等高风险流程。
- 登录、验证码、密码输入等需要人工明确介入的流程。
- 需要后台服务、长期稳定托管或多设备调度的生产场景。
项目状态
当前项目处于实验可用阶段,核心链路已经打通:
- 浏览器端连接 Android 设备并获取截图。
- 调用 OpenAI 兼容视觉模型生成下一步动作。
- 解析 canonical JSON 动作。
- 执行常见 ADB 控制指令并记录运行日志。
- 支持聊天驱动的自动执行、人工确认、停止、上下文恢复,以及
action_stale 与瞬时设备错误的自动恢复。
- 屏幕诊断、动作效果判定、稳定元素编号、记忆检索、技能复用和回答接地校验也在这条链路上。
仍建议把它当作本地实验工具使用。真实设备、模型能力、浏览器权限、CORS 配置和 Android ROM 差异都会影响效果。
工作流程
- 在 Chromium 系浏览器中打开应用。
- 连接开启 USB 调试的 Android 设备,并在手机上授权 ADB 调试。
- 填写 OpenAI 兼容接口的
Base URL、API Key 和 Model,可点“测试连接”先验证(Docker 模式会走同源代理)。
- 在右侧聊天区输入自然语言指令,例如「打开设置并进入 Wi-Fi 页面」。
- 发送后应用截屏并请求模型返回一个动作。
- 前端解析、校验动作,并自动执行安全动作;敏感动作默认仍会请求确认。
- 只有白名单内的失败(
action_stale 与瞬时设备错误 device_io / device_timeout)会自动重试:失败反馈进入下一轮上下文,最多三次尝试后转入 awaiting_review;其余失败类别一次失败即停。元素编号过期属于这一档,重读屏幕即可继续。
- 重复执行,直到模型返回
done、请求 take_over、达到最大步数或用户停止;开启完全无限制模式时不会因 take_over 停止。
环境要求
- 支持 WebUSB 的 Chromium 系浏览器,例如 Chrome 或 Edge。
- 已开启 USB 调试的 Android 设备。
- 可传输数据的 USB 数据线。
- 视觉模型 API:可选 OpenAI 官方(
/v1/responses,默认模型 gpt-5.6)、OpenAI 兼容 /v1/chat/completions、Qwen 兼容接口,或 Gemini 原生 generateContent。
- 支持截图/图像输入的视觉模型。
- 静态部署时,API 服务需要允许浏览器跨域请求,也就是正确配置 CORS;Docker 部署可通过同源本地代理规避这项要求(OpenAI 官方与兼容提供商走代理;Gemini 直连 Google)。
- 页面需要运行在
localhost 或 HTTPS 环境下,WebUSB 才能正常工作。
快速开始
npm install
npm run dev
然后用 Chrome 或 Edge 打开 Vite 输出的本地地址。
常用命令:
npm test
npm run lint
npm run build
npm run build:server # Docker / npm start 需要的 Node 代理
npm run preview
配置说明
应用会把以下配置保存在当前浏览器的 localStorage 中:
Provider:服务商预设。OpenAI 官方 走 Responses API;OpenAI 兼容 / Qwen 走 chat completions;Gemini 走原生 generateContent。
Base URL:模型接口地址。OpenAI 官方默认 https://api.openai.com/v1;兼容接口与 Qwen/Gemini 各有预设。
API Key:模型接口密钥。
Model:模型名称。OpenAI 官方默认 gpt-5.6;未选预设时兼容接口默认 gpt-5.5。
Thinking depth / 推理参数:兼容接口的 reasoning_effort;OpenAI 官方额外支持推理模式(standard/pro)与推理摘要;Qwen/Gemini 使用各自的思考预算或 thinking level。
Action protocol:模型动作协议。webdroid_json 使用截图像素坐标;webdroid_normalized_json 使用 0-1000 归一化坐标。
Max steps:自动执行的最大步数,默认 150。
Confirm sensitive actions:敏感点击是否需要人工确认,默认开启。
安全策略强度(Safety strictness):宽松 / 标准(默认)/ 严格。只在本地区间内调整判定:登录、验证码、支付、不可逆操作各有上下限,宽松不会让支付或删除账号直接执行,严格也不会把需要人工处理的步骤变成无法继续的死结。
完全无限制模式:绕过本地安全策略和敏感确认,模型也会被提示不要请求人工接管。
Stream responses:是否启用流式响应,默认关闭。开启后按 OpenAI 标准附带 stream_options.include_usage 统计流式 Token;兼容网关拒绝该字段时会自动去掉并重试一次,运行继续,只在日志里提示 Token 统计不完整。
Use ADB Keyboard for text:是否优先使用 ADB Keyboard 输入文本,默认关闭。
记忆功能(Memory):是否允许模型用 note 记下持久事实,默认关闭。开启后执行时会按当前任务检索相关条目注入(最多 6 条、1800 字),含密码、验证码、身份证号、银行卡号或 API Key 的内容不会写入。
任务通知:一次运行结束时发系统通知,需要浏览器授权,默认关闭。
自动控制期间屏幕变暗:自动运行时先把手机屏幕调暗以减少旁观,结束或出错后恢复原亮度,默认关闭。
动作工具开关:可以逐个关掉不需要的动作工具;关掉的工具不会再出现在模型的工具列表里。
主题 / 语言:界面主题(跟随系统 / 亮色 / 暗色)和界面语言(跟随系统 / 中文 / English),只影响本机界面。
Action settle、Double tap interval、Keyboard step:Action settle 是动作后的最短等待,实际会继续轮询截图直到画面稳定,最多等到该值的三倍;另外两项是双击间隔和逐字输入节奏。
App Cards:按包名编辑的应用上下文卡片,默认内置 Chrome、Gmail、Settings。
Secrets:本地 Secret 列表,模型只看到 id 和 label,执行时通过 type_secret 在本机解析真实值。
Custom Tools:本地工具定义,模型只看到工具名和描述,执行结果在本地返回给下一轮上下文。
API Key 只保存在浏览器本地。请只在可信设备和本地实验环境中使用。Docker 部署时,浏览器会把模型请求发送到同源的本地代理接口,再由容器内 Node 服务请求模型 API,以绕过浏览器 CORS 限制。
Docker 部署
Docker 镜像会构建同一个前端应用,并额外启用一个本地 Node 服务(TypeScript 编译到 dist-server/):
- 浏览器访问容器页面,WebUSB/WebADB 仍然在浏览器中工作。
- 前端通过同源
/api/openai/chat/completions 代理信封转发请求;OpenAI 官方预设会在信封内指定上游 path /responses,兼容提供商使用 /chat/completions。
- 容器内 Node 服务读取请求里的
Base URL、API Key 和 payload,再转发到模型 API。
- Gemini 预设不经过该代理,浏览器直连 Google API。
- Cloudflare Pages 不使用这个 Node 服务,也不会设置代理构建变量,仍然是静态前端和浏览器直连模型 API。
构建并运行:
npm run docker:build
docker run --rm -p 8080:8080 webdroid-agent
然后用 Chrome 或 Edge 打开:
http://localhost:8080/
如果使用仓库内的 Docker Compose 配置:
docker compose up -d --build
则打开:
http://localhost:8083/
Docker 运行时不需要把 API Key 写进环境变量;继续在页面里的模型配置中填写。请不要把这个容器代理直接暴露到不可信公网,因为它会转发浏览器提交的任意 OpenAI-compatible Base URL。
代理加固(可选)
默认配置已经尽量保守:发布端口只绑定 127.0.0.1(宿主侧),容器内进程监听 0.0.0.0 以便端口映射生效;服务端监听地址默认也是回环。部署在共享网络或需要更强约束时,可用以下环境变量:
| 变量 |
作用 |
PROXY_TOKEN |
设置后,代理请求必须携带 x-proxy-token: <token> 或 Authorization: Bearer <token>,否则返回 401。页面会在运行时从同源 GET /api/proxy-token 获取该 token(仅同源返回、Cache-Control: no-store),无需重新构建前端。 |
PROXY_ALLOW_HOSTS |
逗号分隔的上游主机白名单(如 api.openai.com,dashscope.aliyuncs.com;支持 host:port 与 *.example.com 通配)。设置后,不在名单内的 Base URL 返回 403。 |
CSP_CONNECT_SRC |
追加到前端 Content-Security-Policy 的 connect-src 的额外来源(空格分隔),用于自定义 OpenAI 兼容提供商端点。 |
HOST |
服务端监听地址,默认 127.0.0.1;Docker 镜像内已设为 0.0.0.0。 |
代理还会拒绝跨站浏览器请求(Origin / Sec-Fetch-Site 不匹配)和非 application/json 请求体,避免本机代理被其他网页当作 SSRF/开放中继使用。PROXY_TOKEN 不能替代这些检查,但能在共享本机环境中挡住不了解该端点的进程。
示例:
docker run --rm -p 127.0.0.1:8080:8080 \
-e PROXY_TOKEN=change-me \
-e PROXY_ALLOW_HOSTS=api.openai.com,dashscope.aliyuncs.com \
webdroid-agent
仍然建议不要将该代理暴露到不可信公网。
模型动作协议
推荐让模型只返回一个 JSON 对象,不要包含 Markdown 或解释性文本:
{ "action": "tap", "x": 540, "y": 1280, "reason": "点击搜索框" }
当前只保留 WebDroid JSON 系列协议:
webdroid_json:触控坐标是附加截图里的像素坐标。
webdroid_normalized_json:触控坐标是 Vision-Pointer 风格的 0-1000 归一化坐标,左上角为原点,执行前会先映射回模型截图像素,再映射到设备原始像素。
canonical JSON 推荐使用的标准动作:
| 动作 |
说明 |
launch |
启动应用,可传常见应用名或包名 |
tap |
点击屏幕坐标 |
tap_ref |
点击界面树里带稳定编号的元素(如 e12),执行前换算成坐标;编号过期会回报 stale 而不是点错位置 |
swipe |
从一个坐标滑动到另一个坐标 |
input_text |
输入文本;clear:true 会先清空当前焦点文本框 |
type_secret |
输入本地 Secret;模型只传 secretId,不会看到真实值 |
open_url |
使用 Android ACTION_VIEW 打开网页 URL 或 App deep link |
set_clipboard |
设置 WebDroid 剪贴板文本,并尽力同步到设备剪贴板 |
paste |
将 WebDroid 剪贴板文本粘贴/输入到当前焦点 |
custom_tool |
调用本地配置的 Custom Tool |
key |
发送 Android 按键,如 BACK、HOME、ENTER |
back |
返回 |
home |
回到桌面 |
long_press |
长按坐标 |
double_tap |
双击坐标 |
wait |
等待一段时间,推荐使用 duration 秒数 |
take_over |
请求人工接管;kind 可标记为 login、captcha、payment 或 other |
note |
记录观察,不执行设备动作 |
view_screenshot |
调出之前某一步的截图,附加到下一轮模型请求 |
sequence |
顺序执行一组动作,任一步失败即停止 |
repeat |
重复执行一个动作若干次,可带间隔 |
done |
任务完成 |
示例:
{ "action": "launch", "app": "Settings", "reason": "打开系统设置" }
{ "action": "swipe", "fromX": 540, "fromY": 1700, "toX": 540, "toY": 500, "durationMs": 400, "reason": "向下滚动列表" }
{ "action": "take_over", "kind": "captcha", "message": "需要用户输入验证码" }
{ "action": "type_secret", "secretId": "gmail_password", "clear": true, "reason": "输入已配置的本地密码" }
{ "action": "open_url", "url": "https://example.com/search?q=webdroid", "reason": "直接打开目标网页" }
{ "action": "tap_ref", "ref": "e12", "reason": "界面树里的 Wi-Fi 开关" }
遗留兼容层仍能接收 interact 和 call_api,但它们不是推荐给模型使用的真实执行动作:interact 会转成 take_over,call_api 会转成带有“不支持二次 API 调用”说明的 take_over。
设备控制细节
- 启动应用:优先匹配设备已安装应用列表,也支持内置常见 App 名称映射或直接传 Android 包名。
- 点击/滑动:执行前会校验坐标是否在屏幕范围内。
- 界面结构:每步会尽力通过
uiautomator dump --compressed 读取当前控件树,把可见文本、描述、资源 ID、可点击状态、bounds 和跨屏稳定的元素编号注入模型上下文;整块内容包在 <untrusted> 里,并按控件树质量决定给多少节点。
- 打开 URL:使用 Android
am start -a android.intent.action.VIEW -d <url>,支持网页和设备上已注册的 deep link。
- 长按:使用 Android
input swipe x y x y duration 命令模拟。
- 双击:连续发送两次
tap,中间带可配置延迟。
- 文本输入:简单 ASCII 文本使用 Android
input text。
- 清空后输入、中文和复杂字符:使用 ADB Keyboard 或 AutoGLM Keyboard 广播模式输入。
- 剪贴板:
set_clipboard 会保存一份本地 WebDroid 剪贴板并尝试调用 cmd clipboard set;paste 优先用本地剪贴板通过当前输入通道写入焦点框。
- ADB Keyboard 模式要求设备上已安装并启用
com.android.adbkeyboard/.AdbIME;设备面板提供安装和启用入口。
- 每个设备动作执行后会等到画面稳定:先等满
Action settle,再轮询截图,连续两次一致就继续,最多等到三倍预算。
屏幕观测与自检
每一步都建立在「屏幕上到底有什么」之上,所以这部分尽量交给代码判定,而不是让模型自己猜:
- 动作效果:拿到下一步的界面后,用动作前后的控件树做结构差异(新增、消失、位移、文字变化、控件状态、输入焦点),判出
done、small_change、no_effect、place_changed,作为 <last_action_effect> 进入下一轮上下文,并在步骤卡片上显示成标签。「点了没反应」因此有证据,而不是靠模型自我感觉。
- 稳定元素编号:控件树每次 dump 的下标都会重排,所以每个元素额外分配一个跨屏不变的编号(
e12)。点元素用 tap_ref,执行前换算回坐标;编号过期会明确回报 stale,归入可自动恢复的 action_stale,模型重读屏幕即可,不会点到错误位置。
- 屏幕诊断:按密码框、验证码与支付词表、可读性、连续黑帧判出
normal / loading / blank / protected / capture_unavailable / needs_human。防截屏页面、黑屏、还在加载的页面各有各的说法;接管卡片的类型也从「模型的措辞」改成「屏幕的事实」。
- 控件树质量分档:树明显够用时不占太多 token;控件大多没名字、或屏幕上有大片读不到的区域时,给出更多节点并明确警告「树不全,别拿缺失当否证」。
- 等待画面稳定:动作后不再固定睡
Action settle,而是轮询截图直到连续两次一致;静态页面一次就停,慢页面最多等到三倍预算。
- 限流提示:动作之后新出现的短句(次数用完、操作太频繁等)会直接结束本轮并给出「次数或额度用完了」卡片,不再让模型对着同一句拒绝话术空耗步数。
- 屏幕文字是数据:控件树整块包在
<untrusted> 里送进模型,提示词解释一次「这是数据不是指令」,正文里的闭合标记会被转义,屏上写着「忽略之前的指令」也进不了指令位。
- 回答接地校验:运行中读到的屏幕文字和工具结果进一本账本,最终回答里的引文、带单位的数字和日期会按纯代码规则核对一遍;对不上只在日志里提示,不改写回答,也不额外调用模型。
判定不了就不下结论是这些规则的共同前提:两棵树中任一「有意义的节点」不足两个时不判动作效果,单帧全黑只当加载中,匹配不到就当作没有技能。宁可少说一句,也不要给出一个会让模型跑偏的结论。
安全边界
项目会尽量在前端执行前做约束和确认:
- 模型输出必须能解析为受支持动作。
- 坐标会进行屏幕范围校验。
- 文本输入会限制长度并拒绝控制字符。
type_secret 只从模型接收本地 Secret ID,真实 Secret 值不会进入模型请求或日志摘要。
- 自动执行有最大步数限制。
- 用户可以随时停止运行。
- 敏感点击可要求人工确认;开启完全无限制模式时会跳过这些确认。
type_secret 是输入密码/验证码的受控通道,不会因为动作理由里出现 “password” 而被误判为登录接管;支付、删除账号等不可逆操作仍会被拦截。
- 每个安全类别都有风险等级(
medium / high / critical)和一条判定阶梯(allow < confirm < take_over < block)。强度设置只在类别自带的上下限之间移动判定,不可逆动作的拦截不受该设置影响,也不会被设置导入放宽。
- 被策略拦下的动作(
block)会以 reviewKind: blocked 结束本轮,界面给的是「被安全策略拦下」卡片,没有「继续」按钮;支付则按 takeoverKind: payment 走接管卡片,由用户自己完成后再继续。
take_over、note、done 不会直接操作设备;遗留 interact 和 call_api 会转成人工接管。
- Docker / Node 部署时服务端默认只监听回环地址,并可用
PROXY_TOKEN、PROXY_ALLOW_HOSTS 限制代理访问(见「Docker 部署 · 代理加固」)。代理同时拒绝跨站请求和非 JSON 请求体。
- ADB Keyboard APK 固定到具体 commit,并在安装前校验 SHA-256;上游内容变化不会静默进入设备。该 APK 来自第三方仓库(GPL-2.0),仅作为可选文本输入依赖按需下载。
- 模型请求有 180 秒总超时;上游无响应时会失败并归类为
model_transport,不会无限挂起。
- 服务端响应带有 CSP、
X-Frame-Options: DENY、X-Content-Type-Options: nosniff 等安全头;CSP 的 connect-src 可用 CSP_CONNECT_SRC 扩展。
- 控件树和工具结果一律以
<untrusted> 包裹后进入模型,提示词里说明这是数据而非指令,正文里的闭合标记会被转义。
- 记忆写入前会拦下密码、验证码、身份证号、银行卡号和 API Key;注入时按任务检索,不是把全部条目灌进提示词。
- 最终回答会按纯代码规则核对引文、带单位数字和日期是否真的读到过;对不上只记日志,不静默改写回答。
- 动作效果、屏幕诊断和限流提示都只影响提示与停跑决策,不改变能力边界:它们在安全判定之后才运行。
仍然建议避免让 Agent 操作账号登录、支付、下单、删除、授权、验证码、隐私页面等高风险流程。默认情况下模型返回 take_over 时,自动执行会停止并等待人工接管;开启完全无限制模式后不会因接管请求停止。
项目结构
src/
adapters/
adbKeyboard.ts # ADB Keyboard 安装、检测和编码工具
appPackages.ts # 常见 App 名称和包名映射
deviceParsers.ts # dumpsys 和截图二进制解析
deviceRetry.ts # 设备读取重试和延迟工具
deviceTiming.ts # 设备执行时序默认值
deviceTypes.ts # 设备后端共享类型和错误
inputCommands.ts # ADB 输入命令构建(含 shell 转义)
installedApps.ts # 已安装应用解析、搜索和显示名
lazyWebAdbBackend.ts # 按需加载 WebADB 后端
screenBlackoutCommands.ts # 自动控制期间的屏幕变暗命令
screenshotPreprocess.ts # 截图预处理
sensitiveActions.ts # 敏感动作确认
shellEscape.ts # Shell 参数转义工具
stayAwakeCommands.ts # ADB 连接期间保持唤醒命令
uiAutomator.ts # uiautomator dump 解析和 UI 层级格式化
webAdbBackend.ts # WebADB/WebUSB 实现
webUsbSupport.ts # WebUSB 可用性检测
components/
AgentCursor.tsx # Agent 光标动画
AgentStepCard.tsx # Agent 步骤卡片
AppContext.tsx # 文案和语言上下文
AppErrorBoundary.tsx # 渲染错误兜底
AppTopbar.tsx # 顶栏品牌和状态
ChatHistorySidebar.tsx # 历史会话侧栏
ChatPanel.tsx # 聊天记录和输入区外壳
ConfigSidebar.tsx # 设备和模型配置侧栏编排
ConfirmDialog.tsx # 通用确认对话框
ConversationPanel.tsx # 聊天、历史会话和待执行动作
DeviceHomeOptionsSection.tsx # 设备输入、确认和时序选项
DevicePanel.tsx # 设备连接和执行设置面板
DeviceQuickControls.tsx # 手机预览下方的直接操作条
DeviceToolsSection.tsx # 设备工具区(应用列表、键盘等)
InstalledAppsSection.tsx # 已安装应用搜索和启动
LazyDetails.tsx # 延迟渲染的折叠区域
LazyMarkdownContent.tsx # 延迟加载的 Markdown 渲染
MarkdownContent.tsx # 聊天消息 Markdown 渲染
ModelPanel.tsx # 模型配置面板
NeedsYouCard.tsx # 「需要你处理」卡片
PendingActionCard.tsx # 待执行动作确认卡片
PhoneStage.tsx # 手机截图和动作覆盖层
RunLog.tsx # 运行日志
RunProgressBlock.tsx # 运行进度块
ScreenshotLightbox.tsx # 截图预览弹窗
SensitiveActionDialog.tsx # 敏感动作确认对话框(含焦点陷阱)
SettingsDialog.tsx # 应用设置、仓库信息和可编辑资源
SetupHome.tsx # 首次连接设备/配置模型引导页
ToastViewport.tsx # 轻提示容器
TutorialPanel.tsx # 顶部栏展开的快速上手教程
UnrestrictedModeConfirmDialog.tsx # 完全无限制模式确认对话框
actionDisplay.ts # 动作展示文案辅助
configTargets.ts # 配置面板定位目标
deviceDisplay.ts # 设备展示文案辅助
primitives/ # 按钮、图标按钮等基础控件
hooks/
useAgentRunController.ts # Agent 自动运行和待执行动作控制
useAgentSessionHistory.ts # 会话恢复、保存和历史列表状态
useBodyOverflow.ts # 弹窗/全屏期间共享的 body 滚动锁
useBusyTask.ts # 运行中任务和错误状态管理
useBusyTaskDocumentTitle.ts # 运行中任务写入页面标题
useConfigTargetScroll.ts # 配置侧栏目标定位
useDeviceBackendPreferences.ts # 设备后端偏好同步
useDeviceController.ts # 设备连接、截图和直接动作控制
useDialogFocusTrap.ts # 对话框焦点陷阱
useDisplayImageUrl.ts # 截图 blob URL 生命周期
useDocumentPreferences.ts # 文档主题和语言属性同步
useHotkeys.ts # 全局快捷键注册
useLatestValue.ts # 异步回调读取最新值的 ref
useLocalResourcesState.ts # App Cards / Custom Tools / Secrets 状态
useOverlayLayer.ts # 弹窗层级管理
usePersistedSettings.ts # 设置变更持久化
useRepositoryStats.ts # 设置弹窗中的 GitHub 仓库统计加载
useRunLog.ts # 运行日志状态管理
useSplitPane.ts # 工作区左右分栏拖拽和持久化
useStorageEstimate.ts # 本地存储容量估算
useToasts.ts # 轻提示状态管理
useVirtualWindow.ts # 长列表虚拟窗口
lib/
abortSignal.ts # 中止信号工具
actionAliases.ts # 动作别名归一化
actionCoordinates.ts # 动作坐标换算辅助
actionDefaults.ts # 常用截图动作默认值
actionEffect.ts # 动作前后界面状态差异与效果判定
actionFormats.ts # 动作格式定义
actionParser.ts # 动作解析和规范化
actionPreview.ts # 动作预览文案格式化
actionProtocol.ts # 显式动作协议枚举
actionRisk.ts # 风险等级、判定阶梯与安全强度
actionSafetyPolicy.ts # 本地动作安全策略
actionSecrets.ts # 判断动作是否携带已解析的 Secret 值
actionTypes.ts # 动作类型和校验错误定义
actionValidation.ts # 动作字段校验(包名、URI、控制字符)
agentResources.ts # App 外的本地 Secret 和 Custom Tool 资源
agent.ts # Agent 循环调度
agentThread.ts # 持久化 Agent thread/turn/event 模型
appCards.ts # 可编辑应用上下文卡片
appCopy.ts # 界面文案聚合和语言解析
appCopy.en-US.ts # 英文界面文案
appCopy.zh-CN.ts # 中文界面文案
auditLog.ts # 本地操作审计日志
busyTask.ts # 页面运行中任务标识
chatComposer.ts # 聊天输入框聚焦辅助
chatRun.ts # 聊天驱动的运行状态辅助
contextBuilder.ts # 本轮模型上下文构建和压缩
coordinateSystems.ts # 截图像素与设备像素坐标系统
cursorMotion.ts # Agent 光标运动路径计算
deviceControlTypes.ts # 设备控制共享类型
deviceDoctor.ts # 设备和模型配置诊断
deviceErrors.ts # 设备错误码到界面文案的映射
deviceState.ts # 设备状态展示格式化
elementRefs.ts # 跨屏稳定的元素编号
evidenceLedger.ts # 本次运行读到的证据账本
failureTaxonomy.ts # 失败分类(FailureClass)和可自动恢复类别
fileExport.ts # 本地 JSON 文件导入导出
geminiClie
提示:代码过长已自动截断
单元测试与被测模块同目录(*.test.ts / *.test.tsx),完整清单见各目录。
验证
npm test
npm run lint
npm run build
npm run build:server
npm run css:audit # 扫描不再被引用的 CSS 选择器(启发式,结果需人工确认)
npm run build:budget # 单独检查已有 dist 的包体预算(build 会自动跑)
npm run build 结束时会跑 scripts/bundle-budget.mjs:入口 chunk 上限 400 KiB、单个 chunk 上限 500 KiB、产物总量上限 1100 KiB,超了就让构建失败。临时排查可用 BUNDLE_BUDGET_ENTRY_KB=500 之类的环境变量放宽。
打包时 React 与 @yume-chan/adb 被拆成独立 chunk(见 vite.config.ts),入口 chunk 当前约 348 KiB,全部产物约 941 KiB(预算 1100 KiB)。Markdown 渲染栈不参与手动分块,否则 Vite 会把它预加载回入口,反而丢掉懒加载。
从一次导出的 trajectory 生成重放夹具:
npm run trajectory:fixture -- <trajectory.json>
默认写入 git-ignored 的暂存目录,人工确认后再把夹具放进 test/fixtures/(也可以用 --out <dir> 直接指定输出目录)。生成器默认不覆盖已有夹具,只打印 expected 的差异;加 --force 才会替换。遇到内联图片数据、缺失字段、非法文件名或不可复现的投影时,它会整体拒绝写入。
当前测试主要覆盖:
- 动作解析和动作安全校验。
- OpenAI 兼容与 Responses API 请求体构造、响应解析和网络客户端错误处理。
- Docker 本地代理对
/chat/completions 与 /responses 的转发约束。
- Agent 单步和连续执行流程。
- 白名单失败(
action_stale 与瞬时设备错误 device_io / device_timeout)的失败反馈与自动恢复(最多三次尝试后转入 awaiting_review;其余失败类别一次即停),以及瞬时模型 API 错误/模型空回复重试。
- 失败分类、trajectory 投影与脱敏,以及动作、坐标、命令夹具与设备时序的离线重放(连续三次
device_timeout 后进入 awaiting_review;中止运行保留已执行步骤)。
- 设置持久化和兼容迁移。
- Agent thread/turn 持久化。
- 已安装应用解析、匹配和完整上下文注入。
- 截图坐标映射。
- 运行日志、截图预览和主界面布局组件。
- 安全策略的风险等级、判定阶梯与强度设置,以及被拦截动作的独立卡片。
- 全局快捷键、分栏拖拽、对话框焦点陷阱和 body 滚动锁。
- 动作效果的结构差异判定与
no_effect / place_changed 的区分,以及树太差时不判定。
- 稳定元素编号的沿用、退役、找回和过期原因,
tap_ref 解析与过期后按 action_stale 继续。
- 屏幕诊断的六种状态(含防截屏与单帧黑屏只算加载中)、限流提示只在动作后新出现时成立。
- 控件树质量分档、不可信包裹与闭合标记转义。
- 记忆的 BM25 检索与敏感内容过滤(密码、验证码、身份证号、银行卡号、API Key)。
- 技能起草门槛与匹配,以及技能文本的不可信包裹。
- 回答接地校验对引文、带单位数字、日期和序号的取舍。
真实设备控制仍需要连接 Android 设备进行手动验证。按 docs/device-verification-matrix.md 逐项执行:暂停时机、恢复不新增用户消息、刷新后能否继续、各家 provider 的流式 token 与安全强度,都在这份矩阵里。
路线图
贡献说明
欢迎围绕以下方向提交 issue 或 pull request:
- 新设备、新浏览器或新模型的兼容性反馈。
- 动作解析、坐标映射、ADB 执行稳定性改进。
- 文档、示例任务、故障排查和安全建议补充。
- UI 可用性、日志可读性和本地实验体验优化。
提交改动前建议先运行:
npm test
npm run lint
npm run build
部署到 Cloudflare Pages
项目已创建在 Cloudflare Pages:
重新部署:
git push origin main
也可以本地先验证构建:
npm run build
Cloudflare Pages 使用普通 npm run build,不要设置 VITE_OPENAI_PROXY_URL。这样线上静态站点会继续由浏览器直接请求你填写的 OpenAI 兼容 API,不依赖 Docker 的本地 Node 代理。
License
本项目基于 MIT License 开源。你可以自由使用、复制、修改、分发和二次开发本项目代码,但需要保留原始版权声明和许可证文本。
项目依赖的第三方库仍遵循各自的开源许可证,请在分发或商用前自行确认依赖合规性。
相关项目和社区
评论