# PhoneAgent 架构

本文档描述当前源码结构及其运行时契约。

## 1. 架构不变量

PhoneAgent 是一个严格纯视觉的 Android Agent 运行时。模型决策只能依赖：

- 用户任务；
- 当前截图；
- 一个已冻结的非语义模型坐标契约，以及该契约必需的几何信息；
- 模型自己此前的动作，以及经过模型安全处理的执行与视觉验证结果；
- 显式备注，以及用户确认或接管的结果。

模型永远不会接收 Android 包名、Activity、已安装应用清单、Accessibility/UI 控件树、
WindowManager 状态、语义化应用启动结果或外部 API 工具。这一不变量适用于规划、协议重试、
任务完成复核和动作风险复核。

## 2. 视觉证据与审计元数据

`ScreenObservation.to_screen_info()` 是模型上下文使用的基础屏幕元数据序列化入口。
`phoneagent.model.context` 会移除与已冻结坐标协议无关的几何信息，并加入唯一权威的
`coordinate_contract`。截图本身作为视觉输入发送给模型。

`ScreenObservation.to_trajectory_info()` 会为审计输出增加截图摘要，但不会查询或序列化包名、
Activity、应用名称、Accessibility 控件树或 WindowManager 状态。在构造模型请求之前，
`phoneagent.model.context` 还会防御性地移除外部传入或旧版执行证据中的 Android 语义字段。

### 模型坐标边界

模型坐标是不可信的供应商输出。`MODEL_COORDINATE_SPACE` 可以显式选择
`relative_0_999`、`image_pixels` 或 `display_pixels`。默认的 `auto` 模式会在第一次规划请求
前协商稳定契约：使用合成靶标图片先测试归一化坐标，再测试输入图片像素，整个过程绝不会向
设备派发动作。只有全部探针都满足限定的平均误差和最大误差时，候选契约才会被冻结；否则
运行会以 fail closed 方式终止。成功的坐标配置会按照 endpoint、模型、thinking 模式和截图
尺寸策略缓存。

`CoordinateMapper` 将冻结的模型坐标空间先转换为规范 `[0,1]` 坐标，再转换为运行时内部的
0..999 动作协议坐标。它会验证声明的 x/y 边界，不会通过截断数值掩盖越界错误。因此动作处理器
始终接收一种不变格式；动作与执行事件则同时保留模型原始坐标、规范坐标、运行时坐标、坐标系
几何信息和最终设备坐标。坐标协议由当前观测中的 `coordinate_contract` 持续声明；模型历史不再
保留只对旧截图有效的原始坐标。

## 3. 运行时循环与所有权

标准执行流程如下：

```text
观察截图
→ 协商或加载唯一模型坐标配置
→ 构造纯视觉上下文
→ 等待模型输出唯一动作
→ 解析动作、校验契约并映射为规范运行时坐标
→ 必要时进行风险复核或请求人工确认
→ 重新观察坐标动作的执行前置条件
→ 执行输入等价动作
→ 再次观察并比较像素
→ 恢复或继续
→ 独立复核模型提出的成功完成声明
```

`PhoneAgent` 拥有流程编排权。`AgentState` 拥有唯一实时生命周期阶段和最新工作值。
`AgentEvent` 是回调与轨迹持久化共同使用的权威审计历史。设备适配器、验证器、恢复策略和模型
客户端都不能维护第二套生命周期状态机。

内部实现以异步为主。`run_async()` 和 `step_async()` 是标准实现；`run()` 和 `step()` 只是
基于 `asyncio.run(...)` 的同步薄封装。模型传输只使用一个异步客户端。阻塞式 ADB 调用和同步
回调通过共享且有界的 blocking executor 桥接。

同一个 `PhoneAgent` 实例不允许并发运行多个任务。完整运行与单步运行共享任务目标、最大步数、
最大运行时长、取消和终态语义；未完成的单步任务不能静默更换目标。所有仍可继续的恢复路径在
返回调用方前统一落到 `observing`，使返回阶段同时成为下一步的合法重入点。

规划默认保留完整的任务级文本记忆，而不是使用固定轮次窗口。持久目标和显式备注只保存一次；
已完成动作与净化后的结果持续可用，但只有当前用户轮次携带图片。仅当配置的输入 Token 预算
要求裁剪时，才会删除最旧的完整轮次。协议恢复不会引入独立的记忆所有者或三轮历史窗口，
只会丢弃当前无效的待处理观察。

每个规划动作都要求提供 `screen_description` 和 `reason`：前者较完整地记录当前截图中与任务
有关的页面身份、控件、数值、加载/弹窗状态和必要空间关系；后者说明动作目标、与用户任务及
上一结果的关系、预期界面效果和有关不确定性。协议不再接受语义重复的 `description`。历史
消息会保留这两项和动作类型，但删除
`element`、`start`、`end` 等只对旧截图有效的坐标。它们属于规划记忆，不参与 ADB 执行、重复
动作签名或隔离语义复核的放行判断，避免模型自述影响确定性控制与独立审核。安全策略只能用
`reason` 保守地追加阻断或人工确认，不能据此降低风险等级。

## 4. 动作协议

可执行协议只接受一个完整的 `do(...)` 或 `finish(...)` 调用。当前设备动作范围为：

- `Tap`、`Double Tap`、`Long Press`；
- `Swipe`；
- `Type`；
- `Back`、`Home`；
- 有界 `Wait`；
- `Take_over`、`Interact`、`Note`；
- `finish`。

运行时不存在直接应用动作、包名/应用解析器、已安装应用查询、系统面板语义命令或外部 API
动作。打开应用、操作通知栏或 Quick Settings 都必须通过普通可见 GUI 动作表达。保留 Home 和
Back 是因为它们等价于物理键或导航键，而不是 Android 语义捷径。

解析器使用 AST 检查和逐动作封闭 Schema。未知动作、未知字段、重复关键字、动态表达式、多个
调用、Markdown/JSON 包装、尾随输出和不完整调用，都会在设备执行前被拒绝。系统最多进行一次
受限的同一步协议重试，请求模型修正动作；重试成功前不会触碰设备。

`screen_description` 与 `reason` 是上限 4000 字符的规划元数据。执行器忽略它们；模型未按提示
输出时，解析器仍保持向后兼容，不会因为供应商偶发漏字段而把本来安全的动作误判为设备错误。
旧的 `description` 作为未知字段被协议拒绝，并进入同一步格式重试。

系统提示按手指在截图上的运动定义 Swipe 方向：左滑时 x 递减、右滑时 x 递增、上滑时 y 递减、
下滑时 y 递增；水平手势的 x 变化必须占主导，垂直手势的 y 变化必须占主导。`reason` 声明的
方向必须与 `start/end` 数值关系一致；用户明确要求的方向又必须同时与二者一致。即使另一方向
看起来更适合导航，或当前已到页面边界，也不能把用户的右滑替换成左滑，避免出现“文字说左滑、
坐标实际向上”或“用户要求右滑、模型自行改成左滑”的规划错误。

## 5. Android 适配层

`phoneagent.devices.AndroidDevice` 暴露截图捕获和输入等价操作，不提供应用启动、包名发现或
系统面板语义控制。

`phoneagent.adb` 包含参数化的 ADB 基础操作：

- 设备就绪检查；
- 截图；
- 点击、长按、双击和滑动；
- Home 与 Back 按键事件；
- 文本输入和临时切换 ADB Keyboard。

任务结束时，适配层会恢复原输入法。它不会调用 `dumpsys window`，也不会采集另一份描述当前
可见屏幕的语义信息。

## 6. 视觉新鲜度

坐标动作必须绑定到规划该动作时使用的截图。完成风险复核和用户确认后，运行时会在动作派发前
获取新截图，并比较：

- 显示尺寸；
- 动作目标区域；
- 高阈值的大范围画面替换信号。

包名和系统面板状态被刻意排除在判断之外。像素兼容时动作仍然新鲜；目标区域变化时动作失效。
当全屏稳定而目标区域存在短暂局部动画时，运行时会进行少量短间隔复查，但不会降低原有兼容
阈值；只有某次新截图重新满足严格条件才会派发。失效动作会以零触摸方式取消，并复用最新截图
重新规划。只有真实派发的命令才参与重复动作保护，零触摸候选不会污染重复计数。

## 7. 动作验证

命令成功只表示输入命令已经完成。动作后验证会比较前后像素，并将可观察效果与语义正确性分开
表达。

- 坐标导航动作和文本动作通常需要产生有意义的视觉变化；
- `Wait` 需要完成有界等待，并取得可信的等待后观察；
- `Home` 需要按键派发成功并取得可信观察，不会根据包名声称桌面已位于前台；
- `Note` 只有命令语义，不要求屏幕发生变化；
- `finish` 不会修改设备。

任何动作都不会因为包名或 Activity 身份而获得语义批准。画面发生变化也不能证明用户要求的
语义目标已经完成。

## 8. 语义与安全复核

规划模型提出 `finish(success=True)` 时，运行时会获取新截图并发起隔离的任务完成复核。复核器
接收相同的纯视觉屏幕信息和紧凑、模型安全的运行时证据。复核失败或无法判断时，流程返回有界
恢复或重新规划。

高后果坐标动作会接受隔离的视觉风险复核。显式否定任务边界可以在零触摸条件下阻止 `reason`
明确指向禁止效果的
冲突坐标动作。敏感或无法确定的效果需要人工确认。这些复核仍然是模型判断；真实设备上的最终
验收由人工测试负责。

## 9. 恢复机制

恢复只有五种结果：重新规划、重新观察、重试安全动作、请求人工接管或终止。只有有界 `Wait`
和 `Home` 可以自动重试。Tap、Type、Swipe、Back 以及其他导航或有副作用的动作不会被盲目
重放。命令已派发但没有可测像素变化时，系统记录非阻断警告，而不是自动触发重试。在这种不确定
状态下重复显式高后果动作，必须重新请求确认。

恢复键只使用结构化错误码和动作类型。当前包名、目标包名、应用别名或隐藏系统状态都不参与
恢复决策。

重新观察或重新规划等流程性恢复成功，并不会抹除触发它的硬失败。只有动作及其验证真正成功，
才会重置单类失败计数。新截图竞态和语义完成复核失败属于软重新规划：它们保持可审计，但不消耗
设备或运行时的硬恢复预算。连续被拒绝的动作会在模型历史中替换为不可执行的拒绝摘要，迫使
规划器改变策略，而不是把已阻止动作当作后续范例。

## 10. 轨迹与可观测性

轨迹 Schema `1.3` 记录运行时策略、坐标元数据和彼此分离的执行事实：

```json
{
  "policy_observation": "screenshot_only",
  "android_semantics_visible_to_model": false,
  "deterministic_app_launch": false,
  "external_api_action": false
}
```

事件流记录阶段、观察、模型请求与响应、动作、执行前置条件、执行、视觉验证、语义复核、恢复、
指标和终态。命令派发、命令成功、可观察效果、恢复需求、警告、根错误和终止代码都是独立字段；
硬恢复与软重新规划也分别累计。`coordinate_calibration` 事件用于证明坐标配置是显式配置、命中
缓存、动态协商还是被拒绝；坐标动作事件则携带完整的模型原始坐标到设备坐标映射。校准请求也会
产生指标事件，使 Token 和成本统计覆盖能力协商过程。运行时完成与团队对真实设备可见状态的
最终验收始终分开。

## 11. 入口与源码布局

- `phoneagent`：CLI、设备/模型预检、任务执行和交互模式；
- `phoneagent-web`：消费同一 `AgentEvent` 事件流的本地 Web Console。

`src/phoneagent/` 包含 Agent 运行时；Web 资源位于 `webui/`；静态项目站点和设计文档位于
`docs/`。

## 12. 主动移除的旧接口

严格纯视觉迁移过程中主动移除了以下旧接口：

- 确定性首应用启动和模型发起的应用启动；
- 静态应用别名和应用/包名 CLI 列表；
- 包安装检查和运行时直接包名启动命令；
- 由 WindowManager 或包名驱动的新鲜度与验证；
- 语义化通知栏/Quick Settings 动作和隐藏状态栏命令；
- 模型可调用的外部 API 回调；
- 应用启动超时和启动延迟配置。

这些属于破坏性变更：保留兼容层会继续留下特权控制路径，使严格纯视觉声明失效。
