packages/cua-driver/docs/0.7.1-coord-norm-fixes.md
PR: https://github.com/QwenLM/qwen-code/pull/6610
CUA_DRIVER_RS_COORDINATE_SPACE=1 开启 0–1000 归一化坐标模式后,多个工具的坐标转换和描述存在缺陷。以下逐一说明。
原因:normalize_result 在归一化模式下把 get_window_state 和 get_desktop_state 返回的 screenshot_width/height 强制改写为 1000。设计意图是让模型把截图当作 1000×1000 虚拟网格,直接用 0–1000 坐标。但 elements[].frame 坐标保持真实像素值(如 x=1444),两者矛盾。
表现:模型看到 screenshot_width=1000 但 element frame 坐标远超 1000,拿到了两套不一致的坐标信息。get_desktop_state 的 screen_width/height 也没被改,同样矛盾。
修复:删掉 normalize_result 的改写逻辑,函数改为 no-op。查询类工具返回真实像素值,模型通过工具 schema 描述和 MCP instructions 知道使用 0–1000 坐标。
原因:denormalize_args 在 from_zoom=true 时直接 return 跳过了整个转换逻辑。注释说"core 层没有 zoom 图片尺寸,无法转换"——但这个尺寸其实可以从 zoom 工具的返回值中缓存下来。
表现:模型发送 0–1000 归一化坐标(如中心点 500,500),被当作像素值直接传给 zoom_to_window,点击位置完全偏移。
修复:
ZOOM_SIZE_CACHE,在 zoom 工具执行后由 ingest_zoom_size 缓存图片的 width/height(keyed by pid)denormalize_args 的 from_zoom 分支改为用缓存的 zoom 图片尺寸做反归一化ToolRegistry::invoke 的 output hook 中增加 ingest_zoom_size 调用原因:rewrite_coord_desc 只重写了 input_coord_fields 中列出的坐标字段(x/y)的描述,没有处理 from_zoom 字段本身的描述。
表现:click 工具的 from_zoom 字段描述为 "x and y are pixel coordinates in the last zoom image",模型按此发送像素坐标而非归一化坐标。即使 x/y 的描述已被改为 "0–1000 normalized",from_zoom 的描述与之矛盾。
修复:rewrite_coord_desc 中对所有含 from_zoom 字段的工具,将其描述改写为 "x and y are 0–{scale} normalized coordinates in the last zoom image"。
原因:denormalize_args 在 SIZE_CACHE、SCREEN_SIZE、DESKTOP_SCREENSHOT_SIZE 未命中时用 continue 跳过该字段,归一化值原样传给工具。
表现:模型发送 500(归一化中心)但被当作 500 像素处理。坐标错误且不可预测,可能触发意外的误操作,且没有任何报错信息。
修复:denormalize_args 返回类型改为 Result<(), String>。缓存未命中时返回明确的错误信息,引导模型先调用对应的查询工具:
ToolRegistry::invoke 中处理该 Result,未命中时直接返回 ToolResult::error。
原因:input_coord_fields 的注释写着 "scroll / page / set_value carry no coordinates → excluded",但实际上 macOS 和 Windows 的 scroll 工具都有 x/y 参数,用于指定滚轮事件的触发位置(不是滚动量)。
表现:归一化模式下模型传 0–1000 坐标给 scroll 的 x/y,被当作像素值处理,滚轮事件投递到错误的位置。
修复:将 scroll 加入 input_coord_fields,window-basis。
原因:mouse_button_down、mouse_button_up、mouse_drag、parallel_mouse_drag 四个 Linux 专用工具不在 input_coord_fields 中。它们的 x/y 都是 window-local 坐标。
表现:归一化模式下这些工具的坐标不转换,行为与 click/drag 不一致。
修复:
mouse_button_down、mouse_button_up、mouse_drag 加入 input_coord_fieldsparallel_mouse_drag 的坐标嵌套在 drags[] 数组内,在 denormalize_args 末尾单独处理:每个 drag item 用自己的 (pid, window_id) 查 SIZE_CACHE 获取转换基准;转换 from_x/from_y/to_x/to_y、path[[x,y]]、x_from/x_to(fn 表达式域边界)原因:这三个工具的 x/y 用于 focus-by-pixel(内部委托给 click),input_coord_fields 中没有列出,所以 rewrite_coord_desc 跳过了它们的字段描述。
表现:归一化模式下这三个工具的 x/y 描述仍然写着 "Screenshot-pixel X",模型可能据此发送像素坐标。
修复:将 type_text、press_key、hotkey 加入 input_coord_fields,使其 x/y 描述被重写,同时坐标值也会被正确转换。
原因:rewrite_coord_desc 的顶层描述替换只匹配 "window-local screenshot pixels" 一个固定字符串,但各平台的工具描述中有多种变体:"screenshot pixel coordinates"、"scaled-image coordinates"、"true screen pixels"、"zoom-image pixel coordinates" 等。此外替换后会产生冗余尾部(如 "0–1000 normalized coordinates — the same space get_window_state returns")。
表现:zoom、drag、get_desktop_state 等工具的描述在归一化模式下仍然包含像素相关措辞,误导模型。
修复:
rewrite_coord_desc 不再跳过空 input_coord_fields 的工具,使 parallel_mouse_drag、get_desktop_state 等工具的顶层描述也能被处理protocol.rs 的 MCP instructions 中 "issue a pixel click" 在归一化模式下改为 "issue a coordinate click"