This commit is contained in:
+27
-118
@@ -273,77 +273,15 @@ uv run --package device-agent-runtime pytest -m integration tests/test_wda_integ
|
||||
该测试只从环境变量读取 server URL、UDID 和 device name,不传签名 capabilities,
|
||||
因此应在 WDA 已成功签名/安装后运行。
|
||||
|
||||
## 8. 启动可控制真机的 Runtime API
|
||||
## 8. 独立 Runtime API 已撤销
|
||||
|
||||
当前不能只运行 README 中的普通 `uvicorn ... --factory` 命令,因为它只会加载已登记
|
||||
设备,不会调用 `DeviceManager.connect()`。使用下面的启动方式,在同一进程中完成
|
||||
设备注册、WDA 连接和 REST API 启动:
|
||||
Runtime 现在是由 Host Agent 在进程内调用的执行库,不再提供 `api.rest`、
|
||||
端口 `8000`、`/ui/` 或 `/console/*`。不要再启动单独的 Runtime 服务,
|
||||
也不要使用无鉴权的 REST 调用控制设备。
|
||||
|
||||
```bash
|
||||
python - <<'PY'
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
|
||||
from api.rest import create_app
|
||||
from device.manager import DeviceManager
|
||||
from driver.registry import build_driver_factory
|
||||
|
||||
device_id = "iphone-1"
|
||||
connection_info = {
|
||||
"server_url": "http://127.0.0.1:4723",
|
||||
"device_name": "iPhone",
|
||||
"udid": os.environ["DEVICE_UDID"],
|
||||
"xcodeOrgId": os.environ["APPLE_TEAM_ID"],
|
||||
"xcodeSigningId": "Apple Development",
|
||||
"updatedWDABundleId": os.environ["WDA_BUNDLE_ID"],
|
||||
}
|
||||
|
||||
manager = DeviceManager()
|
||||
app = create_app(manager=manager)
|
||||
manager.register_device(
|
||||
device_id,
|
||||
build_driver_factory("wda", connection_info),
|
||||
name="Local iPhone",
|
||||
driver_type="wda",
|
||||
connection_info=connection_info,
|
||||
)
|
||||
manager.connect(device_id, max_retries=1)
|
||||
|
||||
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||
PY
|
||||
```
|
||||
|
||||
保持进程运行,在另一个 Terminal 验证:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8000/devices
|
||||
|
||||
curl -s -X POST http://127.0.0.1:8000/devices/iphone-1/tap \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"x": 100, "y": 200}'
|
||||
|
||||
curl -s -X POST http://127.0.0.1:8000/devices/iphone-1/launch \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"app_id": "com.apple.Preferences"}'
|
||||
```
|
||||
|
||||
点击坐标必须按当前设备屏幕坐标选择。先截图或使用 Appium Inspector 确认坐标,避免
|
||||
误操作。
|
||||
|
||||
Runtime API 自带同源 Web Console,无需额外的前端进程、Node 工具链或
|
||||
`RUNTIME_CONSOLE_STATIC_DIR`。保持 Runtime API 运行,浏览器访问
|
||||
`http://127.0.0.1:8000/`(会自动 307 跳转到 `/ui/`)即可:
|
||||
|
||||
- `/ui/`:设备状态面板,约每 10 秒自动刷新一次;
|
||||
- `/ui/tasks`:任务列表与筛选;
|
||||
- `/ui/tasks/{task_id}`:任务详情与逐步 timeline(含截图);
|
||||
- `/ui/config`:登记/移除设备、调整 `max_steps`。
|
||||
|
||||
已由上面启动脚本连接的 `iphone-1` 会出现在设备列表中。不要在 Console 中
|
||||
重复登记同一台设备;当前登记操作只写入配置,不会自动 connect。Console 与
|
||||
`/console/*` JSON API 共用同一份 Runtime 状态,两者行为一致。Runtime Console
|
||||
仅假设受信任本地网络访问,不提供鉴权 / CSRF;如需暴露到非受信网络请另行评估。
|
||||
真机连接和任务执行都由下一节的受管 Host Agent 完成。保持 §6 的 Appium 服务
|
||||
可用,然后启动 Host Agent;本机执行记录、截图、OCR 和 UI 树均从其已鉴权的
|
||||
Console 查看。
|
||||
|
||||
## 9. 启动云端受管 Host Agent
|
||||
|
||||
@@ -459,15 +397,16 @@ http://127.0.0.1:8765
|
||||
`DeviceManager` 上生效,无需重启 Host Agent。
|
||||
- 修改密码:更新本地操作账号密码,需要先输入当前密码。
|
||||
- 最近历史:近期 assignment 与 heartbeat 的执行记录。
|
||||
- **任务进度页面**:`http://127.0.0.1:8765/tasks` 展示本机 Host Agent 上已执行/正在执行
|
||||
的任务列表(状态、设备、时间戳),点击任务 ID 可查看逐步 timeline 含截图。
|
||||
- **任务进度页面**:`http://127.0.0.1:8765/tasks` 是本机实际执行任务的权威查看入口。
|
||||
它展示已执行/正在执行的任务、Cloud task ID 与 attempt;点击执行 ID 可查看每步
|
||||
timeline 的操作、前后截图、OCR 和 UI 树结果。
|
||||
|
||||
完整的 `HOST_AGENT_CONSOLE_*` 环境变量列表(端口、非回环 bind 的显式 opt-in、
|
||||
session TTL、历史记录条数上限等)参见 `docs/CLOUD_DEPLOYMENT.md`;生产/远程场景下
|
||||
应优先使用 SSH 端口转发访问该 Console,而不是直接把它暴露到非回环地址。
|
||||
|
||||
Host Agent 会把每步执行状态与截图持久化到本地 SQLite/文件系统,路径与 Runtime 自身的
|
||||
`tasks/tasks.sqlite3` 不冲突:
|
||||
Host Agent 会把每步执行状态、前后截图、OCR 和 UI 树结果持久化到本地
|
||||
SQLite/文件系统。该 Host Console 是这些实际执行证据的唯一 Web 查看入口:
|
||||
|
||||
```bash
|
||||
# 任务进度持久化路径(默认值,可通过环境变量覆盖)
|
||||
@@ -477,64 +416,34 @@ Host Agent 会把每步执行状态与截图持久化到本地 SQLite/文件系
|
||||
# HOST_AGENT_TASK_RETENTION_MAX_AGE_DAYS=7 # 超过 7 天的任务自动清理
|
||||
```
|
||||
|
||||
Retention 策略取"数量上限与天数上限中更严格的"——即先按 `max_count` 取最近 N 个、
|
||||
Retention 策略取"数量上限与天数上限中更严格的" - 即先按 `max_count` 取最近 N 个、
|
||||
再按 `max_age_days` 过滤掉过老的,最终保留两者中较小的集合。任务完成后,这些记录
|
||||
在 Console 的 `/tasks` 页面可查。
|
||||
|
||||
### 可选:由 Host Agent 托管 Appium 和 Runtime API
|
||||
### 可选:由 Host Agent 托管 Appium
|
||||
|
||||
默认情况下 Host Agent **不会**自动启动 Appium 或本地 Runtime API:必须按
|
||||
§6 在独立 Terminal 中保持 `appium --address 127.0.0.1 --port 4723` 运行,按
|
||||
§8 在另一个 Terminal 中启动 Runtime API。忘记其中任意一个,Host Agent 不会报错,
|
||||
heartbeat 仍会成功,但设备会静默保持 `offline`、所有任务卡在 `queued`。
|
||||
|
||||
`host-agent-dependency-supervisor` 是一个可选模式,让 Host Agent 自己把这两个
|
||||
外部进程作为子进程托管,覆盖单机真机工作流。它默认关闭,需要显式 opt-in:
|
||||
默认情况下 Host Agent 不会自动启动 Appium;可继续按 §6 在独立 Terminal 中保持
|
||||
`appium --address 127.0.0.1 --port 4723` 运行。也可以显式让 Host Agent 托管
|
||||
Appium,避免忘记启动导致设备保持 `offline`:
|
||||
|
||||
```bash
|
||||
export HOST_AGENT_DEPENDENCY_SUPERVISOR_ENABLED="true"
|
||||
|
||||
# 任选其一或两者都开。两者默认 false。
|
||||
export HOST_AGENT_APPIUM_SUPERVISED="true"
|
||||
export HOST_AGENT_RUNTIME_SUPERVISED="true"
|
||||
|
||||
# 可覆盖默认地址/端口(默认值与 §6/§8 手动流程一致):
|
||||
# export HOST_AGENT_APPIUM_HOST="127.0.0.1"
|
||||
# export HOST_AGENT_APPIUM_PORT="4723"
|
||||
# export HOST_AGENT_RUNTIME_HOST="127.0.0.1"
|
||||
# export HOST_AGENT_RUNTIME_PORT="8000"
|
||||
|
||||
# 单次 Host Agent 进程生命周期内允许的最大重启次数,默认 5。
|
||||
# export HOST_AGENT_DEPENDENCY_RESTART_MAX_ATTEMPTS="5"
|
||||
|
||||
uv run --package device-host-agent device-host-agent
|
||||
```
|
||||
|
||||
启用后的行为(详见 `openspec/changes/host-agent-dependency-supervisor/`):
|
||||
|
||||
- **启动顺序**:Host Agent 在第一次 `connect_devices()` 之前,先按上面选中的
|
||||
依赖项依次做"先探测后启动"。这样一旦开启,Appium 不再需要单独的 Terminal。
|
||||
- **Adopt-don't-fight**:探测 `(host, port)` 时若已经有进程在监听并通过健康检查
|
||||
(Appium `GET /status` 返回 200 JSON,Runtime `GET /devices` 返回 200 JSON),
|
||||
Host Agent 会以 *adopted* 方式记录日志,**不会**再 spawn 一个重复进程,也不会
|
||||
在退出/崩溃时杀掉或重启它。如果端口被占但健康检查失败,记一条 port-conflict
|
||||
错误并跳过该依赖,不抢端口、不静默继续。
|
||||
- **崩溃重启**:只有 Host Agent 自己 spawn 出来的子进程才会被监控。子进程意外
|
||||
退出时,按指数退避(1s、2s、4s、8s,封顶 30s)重启;当某个依赖在本进程生命
|
||||
周期内累计达到 `HOST_AGENT_DEPENDENCY_RESTART_MAX_ATTEMPTS` 次重启后,停止
|
||||
再次尝试直到 Host Agent 重启。被 adopt 的进程永远不会被 Host Agent 重启或杀死。
|
||||
- **spawn 失败 ≠ crash**:如果 `appium` 可执行文件不在 `PATH` 上,启动会以
|
||||
"dependency-supervisor: appium spawn failed — executable not found" 形式记一条
|
||||
依赖主管特有的错误,与正常 crash 区分开。请确认按 §4.3 安装好 `appium` 和
|
||||
XCUITest/UiAutomator2 driver。
|
||||
- **退出时**:Host Agent 在自身 graceful shutdown 阶段只会 `terminate` 它自己
|
||||
spawn 的子进程;adopted 进程保留不动。
|
||||
- **不影响 Docker/Compose**:本模式只针对 macOS 单机真机工作流;
|
||||
`compose.yaml` / `compose.deploy.yaml` 完全不受影响。
|
||||
Host Agent 会先探测 Appium;健康实例会被 adopt 而不会重复启动或停止。仅由 Host
|
||||
Agent 自己启动的 Appium 子进程会在异常退出后按有界指数退避重启,并在 Host Agent
|
||||
正常关闭时 terminate。已移除的 `HOST_AGENT_RUNTIME_*` 变量会使启动明确失败;
|
||||
不要再配置或启动独立 Runtime 服务。
|
||||
|
||||
如果偏好保持对 Appium 终端日志的完全控制、或者已经在用其他进程管理工具
|
||||
(launchd、systemd、tmux 等)托管 Appium,可以继续使用 §6/§8 的手动流程,
|
||||
不开启本模式即可。
|
||||
(launchd、systemd、tmux 等)托管 Appium,可以继续使用 §6 的手动流程,不开启
|
||||
本模式即可。
|
||||
|
||||
## 10. 多设备与端口
|
||||
|
||||
@@ -629,16 +538,16 @@ wheel 与 Apple Silicon 兼容性。
|
||||
- [ ] `curl http://127.0.0.1:4723/status` 返回正常。
|
||||
- [ ] 直接 Python 验证能生成 `/tmp/device-agent-runtime.png`。
|
||||
- [ ] 实机 integration test 通过。
|
||||
- [ ] Runtime API 返回 `iphone-1`,并能执行 screenshot/tap/launch。
|
||||
- [ ] Host Agent Console 显示已连接设备,并能在 `/tasks` 查看一次完成任务的完整证据。
|
||||
- [ ] 如需 OCR,另行确认 PaddleOCR 在当前 Mac/Python 架构下可运行。
|
||||
- [ ] 云端受管部署已保存 Host identity,并能在 `/v1/hosts`、`/v1/devices` 中看到。
|
||||
|
||||
## 13. 后续代码改进建议
|
||||
|
||||
为了让后续执行不再依赖内联 Python 启动脚本,建议另开变更实现:
|
||||
为了让后续设备管理和验收更易操作,建议另开变更实现:
|
||||
|
||||
- 为 Console/REST 增加显式 connect/disconnect endpoint。
|
||||
- 增加正式 CLI,例如 `device-runtime serve --device-config ...`。
|
||||
- 为 Host Agent Console 增加显式 connect/disconnect 状态诊断。
|
||||
- 增加正式 CLI,用于校验 Host 的设备配置和 Appium 连通性。
|
||||
- 统一将遗留的 `APEX_WDA_*` 环境变量改名为 `DEVICE_RUNTIME_WDA_*`,并保留兼容期。
|
||||
- 将 PaddleOCR 改成 optional dependency,拆分基础控制与 OCR 安装路径。
|
||||
- 增加 macOS CI 的无真机 smoke test,以及受控环境中的真机验收脚本。
|
||||
|
||||
Reference in New Issue
Block a user