Files
agentic-mobile-control/docs/superpowers/specs/2026-07-15-gesture-primitives-humanize-design.md
T
q792602257andClaude Opus 4.6 557c8a25ba docs(superpowers): add gesture primitives + humanize design spec
Design for long_press/double_tap atomic gestures and a centralized
humanize layer (coordinate jitter, curved W3C-Actions swipe, timing
jitter) gated by APEX_HUMANIZE_ENABLED.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-15 18:15:43 +08:00

11 KiB

原子手势扩展 + 拟人化抖动 设计

  • 日期:2026-07-15
  • 状态:Draft(待用户 review)
  • 作者:Jerry Yan + Claude
  • 关联代码区:runtime/tool_specs.pyruntime/executor.pyruntime/planner_prompts.pydriver/base.pydriver/wda_driver.pydriver/android_driver.pytools/

1. 背景与动机

当前 Runtime 暴露给 AI Planner 的设备动作只有 5 个:tap / swipe / input_text / launch_app / terminate_app(见 runtime/tool_specs.py:42-145)。Driver 抽象(driver/base.py)与两个实现(WDA / Android UiAutomator2)同样没有长按、双击。

两个缺口:

  1. 原子手势不全:长按(long press)、双击(double tap)无法表达。AI Planner 无法完成依赖这两类手势的任务。
  2. 动作过于"机器化"swipe 在 driver 层走 2 点命令(WDA mobile: dragFromToForDuration、Android mobile: dragGesture),轨迹是完美直线;tap 每次落在精确坐标。这种确定性容易被目标 app 的反自动化检测识别。

本设计同时解决两者:新增两个原子手势,并引入一个集中的拟人化(humanize)层,对所有指针类动作注入坐标偏移、路径曲线、时序抖动。

2. 目标 / 非目标

目标

  • 新增 long_pressdouble_tap 两个工具,端到端贯通(driver → tool → tool_spec → executor registry → planner prompt)。
  • 新增 tools/humanize.py,提供坐标抖动、滑动 waypoint 生成、时长抖动三类纯函数。
  • swipe 在 humanize 开启时改走 W3C Actions 多点曲线;double_tap 始终走 W3C Actions(两 tap + 可调间隔)。
  • 通过 APEX_HUMANIZE_ENABLED env 开关(默认开)控制;关闭时现有 tap/swipe 行为零回归、现有精确坐标断言零改动。

非目标

  • 不做捏合、多指、3D Touch 等更复杂的手势(YAGNI,等真有需求)。
  • 不重构现有 swipe 关闭路径(保留 mobile: dragGesture/dragFromToForDuration)。
  • 不给 tap/input_text 等加新的可调参数(只加抖动,不改语义)。
  • 不引入时序相关的全局 sleep/节流策略。
  • 不做反检测的"高级"维度(设备指纹、传感器模拟等),只做指针轨迹层面的拟人。

3. 平台命令矩阵

实现期必须按 android-driver 变更 task 1.1 的先例,对照已安装的 appium-python-client 源码/官方文档核实命令名,不得凭记忆猜测。

手势 iOS (WDA / XCUITest) Android (UiAutomator2) 备注
long_press mobile: touchAndHold {x, y, duration} mobile: longClickGesture {x, y} Android 时长系统固定、不可控;duration_ms 参数在 Android 实现里被忽略并记入 Risks
double_tap W3C ActionspointerDown→up→pause→down→up W3C Actions(同左) 不用 mobile: doubleTap/doubleClickGesture,理由见 §4.3
swipe(humanize 开) W3C Actions 多点曲线 W3C Actions 多点曲线 新增 Driver.swipe_path
swipe(humanize 关) mobile: dragFromToForDuration(不变) mobile: dragGesture(不变) 保留原 Driver.swipe

4. 设计

4.1 新原子手势

long_press

  • 参数:xyduration_ms?=1200(加 purpose/expected_outcome 元数据,见 tool_specs.py:13-26ACTION_METADATA_PROPERTIES
  • 实现:driver 新增 Driver.long_press(x, y, duration_ms) 抽象方法;WDA 透传 duration,Android 忽略 duration_ms
  • 工具:tools/long_press.py,薄封装,humanize 抖动坐标(+ WDA 时长)

double_tap

  • 参数:xyinterval_ms?=80
  • 实现:driver 新增 Driver.double_tap(x, y, interval_ms) 抽象方法,两个驱动统一用 W3C Actions 实现(§4.3)
  • 工具:tools/double_tap.py,薄封装,humanize 抖动坐标 + 间隔

4.2 tools/humanize.py(新)

集中式拟人模块,纯函数 + 一个 dataclass config。无 driver 依赖。

@dataclass(frozen=True)
class HumanizeConfig:
    enabled: bool = True            # 读 APEX_HUMANIZE_ENABLED,默认 true
    tap_radius_px: float = 5.0      # APEX_HUMANIZE_TAP_RADIUS_PX
    swipe_curvature: float = 0.15   # 垂直噪声幅度占路径长度的比例
    swipe_waypoints: int = 8        # 中间点数
    duration_spread: float = 0.15   # 时长 ±15%

def jitter_point(x, y, *, radius, rng) -> tuple[float, float]: ...
def swipe_waypoints(start, end, *, curvature, n, rng) -> list[tuple[float,float]]: ...
def jitter_duration(value_ms, *, spread, rng) -> int: ...
def jitter_interval(value_ms, *, spread, rng) -> int: ...
  • 随机源:函数接受 rng: random.Random。模块级单例 _rng 默认 random.Random();测试可注入固定种子构造的 random.Random(seed) 以可复现。
  • swipe_waypoints:在起止两点间用贝塞尔/线性插值生成 n 个点,每个点沿路径法线方向加高斯噪声(幅度 = 路径长度 × curvature)。保证首末点等于(抖动后的)起止点。
  • config 加载沿用仓库既有 load_config 模式(参考 runtime/planner_config.py),env 名统一前缀 APEX_HUMANIZE_*

4.3 W3C Actions:Driver.swipe_pathdouble_tap 的共享实现

Appium Python Client 的 ActionBuilder / W3C pointer action 在 iOS 与 Android 两侧 API 一致,逐点 pointer_move 即可。为避免两个驱动各写一遍,在 driver 层新增一个共享辅助:

  • driver/base.py 新增抽象方法:
    • swipe_path(self, waypoints: list[tuple[float, float]], duration_ms: int) -> None
    • double_tap(self, x: float, y: float, interval_ms: int) -> None
    • long_press(self, x: float, y: float, duration_ms: int) -> None
  • 共享的 W3C Actions 构造逻辑放 driver/_w3c_actions.py(模块级函数,接收 appium client + 参数),两个驱动的 swipe_path/double_tap 实现各自调它。这样:
    • driver 仍是薄适配器(只持 client、调辅助、包 DriverError
    • W3C 序列化逻辑不重复

swipe_path 序列pointerDown(start) → move(p1) → move(p2) → … → move(end) → pointerUp,总时长按 waypoint 数均分(或按段长加权)。

double_tap 序列pointerDown(x,y) → pointerUp → pause(interval_ms) → pointerDown(x,y) → pointerUp,单次 HTTP 往返,interval_ms 完全可控。

为什么 double_tap 不用原生 mobile: doubleTap/doubleClickGesture:它们是固定间隔、一次性机器手势,最易被反检测识别;W3C 两 tap + 高斯抖动间隔反而更像人,且和 swipe_path 共用同一套基础设施。

4.4 应用点(humanize 开/关矩阵)

动作 humanize 开 humanize 关
tap jitter_point 后调 driver.tap 原样(零变化)
swipe swipe_waypoints 后调 driver.swipe_path 原样调 driver.swipe
long_press jitter_point(+WDA jitter_duration) 后调 driver.long_press 直接调 driver.long_press
double_tap jitter_point + jitter_interval 后调 driver.double_tap 直接调 driver.double_tap(仍走 W3C,但不抖动)
input_text/launch_app/terminate_app 不动 不动

关键保证:humanize 关闭时,现有 tap/swipe 走与今天完全相同的 driver 方法与命令,现有精确坐标断言零改动。long_press/double_tap 是新工具,无"原行为"可回归。

4.5 tool_specs 与 executor

  • runtime/tool_specs.py 新增 LONG_PRESS_SPECDOUBLE_TAP_SPEC,加入 ACTION_TOOL_SPECSALL_TOOL_SPECS 自动带上,prompt 的 tool 列表自动生效)。
  • runtime/executor.py::default_tool_registry 注册 "long_press""double_tap"

4.6 planner prompt

runtime/planner_prompts.py:18-22 那段工具枚举("You must then call exactly one tool: - One of tap, swipe, …")补 long_pressdouble_tap,并各加一句使用场景提示(长按用于长按菜单/拖拽预备;双击用于缩放/选中)。仍保持"每回合恰好一个工具"的单步约束不变(多步规划是另一条独立议题,本次不动)。

5. 配置(env)

变量 默认 说明
APEX_HUMANIZE_ENABLED true 总开关;false 时所有抖动关闭,tap/swipe 走原路径
APEX_HUMANIZE_TAP_RADIUS_PX 5.0 tap/long_press/double_tap 坐标高斯偏移半径(px)
APEX_HUMANIZE_SWIPE_CURVATURE 0.15 滑动垂直噪声占路径长度比例
APEX_HUMANIZE_SWIPE_WAYPOINTS 8 滑动中间点数
APEX_HUMANIZE_DURATION_SPREAD 0.15 时长/间隔 ±比例

未设置时全部走默认;任一缺失不影响其它。测试通过在 fixture 里设 APEX_HUMANIZE_ENABLED=false(或注入固定种子 rng)获得确定性。

6. 默认值

  • long_press.duration_ms = 1200
  • double_tap.interval_ms = 80
  • swipe.duration_ms = 500(不变)

7. 测试策略

  1. humanize 单测tests/test_humanize.py):注入 random.Random(42),断言 jitter_point 偏移在半径内、swipe_waypoints 首末点等于端点且点数正确、中间点存在垂直偏移、jitter_duration 在 ±spread 内。
  2. driver 新方法单测(humanize 关闭):mock appium client,断言 swipe_path 发出的 W3C action 序列含 N 个 pointer_move、double_tap 序列含两段 pointerDown/Up + 一个 pause、long_press 调对正确 mobile 命令(WDA touchAndHold 带 duration、Android longClickGesture)。
  3. tool 集成tools/long_press.py/tools/double_tap.py 在 fake driver 上跑通;tools/swipe.py 在 humanize 开启时断言调用的是 swipe_path 且 waypoints > 2,关闭时调用的是 swipe
  4. 回归:现有 tap/swipe 相关测试在 humanize 关闭下全部不变;在 tests/test_executor.py 补两个新动作的 dispatch。
  5. spec 校验runtime/tool_specs.py 的新 spec 满足 _action_parameters 要求(含 purpose/expected_outcome)。

8. 风险

  • R1(中):W3C Actions 在某些 Appium/驱动版本上对 pause 的时长支持不一致。缓解:double_tap 的 interval 先用 pause(duration_ms),实现期在真实 Appium 上验证;若不可靠,退化为两次 mobile: tap + time.sleep(记入 Open Questions)。
  • R2(低):Android longClickGesture 时长不可控,与 WDA 行为不对称。接受:参数保留,Android 忽略并文档化。
  • R3(低):W3C Actions 曲线滑动比原生 2 点命令慢(多点序列化 + 执行)。接受:反检测优先于延迟,且单次滑动仍是一次 HTTP 往返。
  • R4(低):humanize 默认开可能让未设 env 的既有测试出现非确定坐标。缓解:现有 tap/swipe 测试若断言精确坐标,需确认它们跑在 APEX_HUMANIZE_ENABLED=false 下;实现期 grep 评估影响面,必要时在测试 conftest 默认关 humanize。

9. 开放问题(实现期核实)

  • OQ1:mobile: touchAndHold(WDA)与 mobile: longClickGesture(Android)的精确参数名/duration 单位——对照已安装 appium-python-client 核实。
  • OQ2:W3C pause action 在目标 Appium 版本是否可靠传递 interval——真机验证,必要时按 R1 回退。
  • OQ3:是否需要在 Driver 抽象为 swipe_path/double_tap/long_press 提供 fake/mock 默认实现(现有 driver/registry.py 不注册 mock driver,保持一致——只做抽象方法 + 两个真实实现 + 单测里的 mock)。