Conversation
官方 computer-use 暴露 10 个 tool,本仓库此前只实现 9 个;本次补上唯一缺口 select_text,description / 参数名 / 参数说明 / enum 与官方逐字对齐。 - 新增纯函数 TextSelectionResolver:按 UTF-16 偏移解析目标文本,支持 prefix / suffix 消歧;命中不唯一时 fail closed,不静默选第一个匹配。 - ComputerUseService.selectText 通过 kAXSelectedTextRangeAttribute 落地选区,写后 读回真实 range;目标 app 未接受时在 result 里附 verification note,不伪装成功。 - fixture 支持 select_text 并导出当前选区,fixture 合成 snapshot 透出 Selected text。 - smoke suite 增加第 8 步 select_text 断言;单元测试新增 11 个 resolver 用例、官方 surface schema 断言与非法 selection 用例,工具数量断言 9 → 10。 - 同步 ARCHITECTURE / QUALITY_SCORE / SKILL.md / references/usage.md 的工具面描述, 并按仓库约定记录 docs/histories/2026-09/20260911-2001-add-select-text-tool.md。 验证:swift build 通过;swift test 179 tests / 0 failures(1 skipped); make smoke 通过,输出包含新增的 8. select_text。 平台差异:Windows(Go + PowerShell UIA)与 Linux(Go + AT-SPI2)runtime 仍是 9 个 tool,select_text 未实现,已在 ARCHITECTURE / QUALITY_SCORE 明确标注。
对照 Codex Computer Use 的 P1-P4 补丁计划:默认关闭快照 activate 恢复、 元素自动滚入视口、目标元素高亮环、click.auto 的 sky_click 灰度路径。 不含任何运行产物改动。
对照 Codex Computer Use 收敛 OCU 的焦点行为。四项改动属于同一条 "后台可操作、不抢前台焦点" 链路,因此放在同一个 commit: - P2:SnapshotBuilder.build / refreshSnapshot / clickActionSnapshotRecoveryPolicy 的默认 recoveryPolicy 从 .allowActivation 改为 .readOnly,unhide / activate / open -b / AXRaise / AXMain / AXFocused 收进显式 opt-in:9 个工具的 schema 与 dispatcher 新增可选布尔 allow_window_recovery,进程级用 OPEN_COMPUTER_USE_ALLOW_WINDOW_RECOVERY=1;显式 false 优先于 env。readOnly 下 仍返回 Apple event error -10005: cgWindowNotFound,并附 "移到当前 Space / 取消最小化" 与 opt-in 方式提示。 - P1:新增 ensureElementVisible。AXScrollToVisible(元素自身 -> 最多 6 层祖先) -> 最近 AXScrollArea 的 AXScroll<Direction>ByPage(最多 8 页,每轮读回 frame, 无进展即停);越界判定只看元素 frame 中心点是否落在窗口可视矩形内, localFrame / windowBounds 缺失或退化时不滚动。滚动后按同一 element_index 重新 解析 AX 句柄并读回 frame,读不回或仍越界时 fail closed。接入 click(element_index) / set_value / select_text。默认开启, OPEN_COMPUTER_USE_SCROLL_TARGET_INTO_VIEW=0/false/no/off 关闭。 - P4:新增 TargetHighlightOverlay,复用无边框 non-activating NSPanel 与 screen-state -> AppKit 全局坐标换算;动作前显示、450ms 后 350ms 淡出; canBecomeKey/Main=false、ignoresMouseEvents=true;localFrame 为空或与窗口 可视矩形不相交时不显示;turn-ended / reset 联动隐藏。接入 click / set_value / select_text / perform_secondary_action / scroll。 - P3:新增 autoSkyClickEnabled / automaticSkyClickEligible。默认行为不变; OPEN_COMPUTER_USE_AUTO_SKY_CLICK=1 时 auto 在 postToPid 兜底前先试一次 sky_click,失败落回 postToPid,绝不动态升级到 global 物理指针。 同步 ARCHITECTURE.md、仓库 skill 与 history;未重建或覆盖任何 .app 运行产物。 验证:swift build(Build complete);swift test(189 tests, 1 skipped live, 0 failures);./scripts/check-docs.sh(通过)。 未执行:真实 GUI 端到端验证(验收轮占用桌面,本轮禁止 GUI 自动化)。
元素级动作此前先调 showTargetHighlight、再 moveVisualCursor,用户看到的是 "先亮环、光标才飞过去"。官方 Codex Computer Use 的同线程日志顺序相反: Move cursor to ... / Start Bezier cursor animation ... / Signal cursor movement completion ... 先于 Moving mouse to ... / Clicking at ...,即软件光标先到位、再发生真实交互 (docs/references/codex-computer-use-reverse-engineering/ software-cursor-overlay.md:192-196,243)。 现在两个 advisory overlay 收进 VisualInteractionChoreographer,元素级动作 统一走 move -> settle(120ms) -> highlight -> action: - click / set_value / select_text 交换高亮与移动的先后; - perform_secondary_action / scroll 按官方 tool 矩阵证据补上移动阶段 (software-cursor-overlay.md:193,195); - OPEN_COMPUTER_USE_VISUAL_CURSOR 关闭时两步一起跳过; - canBecomeKey/Main=false、ignoresMouseEvents=true、localFrame 空或不相交 不显示、turn-ended/reset 联动隐藏均保持不变。 验证:swift build(Build complete);swift test(193 tests / 1 skipped / 0 failures,新增 4 个顺序与开关单测);./scripts/check-docs.sh 通过。
- 光标移动只在目标变化时发生:与上次落点相差 ≤2pt 的目标只重画高亮环, 不再重复 move + settle + 到达节拍(VisualCursorMoveCoalescer,阈值 2pt)。 - 目标变化但距上次动画不足合并窗口时不重播移动动画:直接落位到新目标 (SoftwareCursorOverlay.repositionCursor,不播 Bezier、不播 pulse), 默认窗口 400ms,OPEN_COMPUTER_USE_VISUAL_CURSOR_COALESCE_MS 可调, 0 恢复逐次动画;坐标点击与 fixture 路径共用同一个 coalescer。 - 合并窗口锚定“上一次真正播动画”的时刻,同目标不重新锚定,避免亚像素 漂移把光标慢慢带走;cursor 隐藏 / turn-ended / reset 时清空记忆。 - 高亮环不再依赖主 RunLoop 的 Timer:新增 TargetHighlightLifetime,用后台 DispatchSourceTimer 计硬 TTL(常规 450ms 可见 + 350ms 淡出,菜单/弹层 AXMenuItem 等 300ms),并在 TTL + fade + 50ms 处无条件 orderOut;主线程 忙或 run loop 处于原生菜单 tracking 模式时也不会留下常驻环。 - 环可见期间每 220ms 做一次存活看门狗:AX 元素失效、frame 位移/尺寸变化 超过 8pt、目标窗口消失(原生下拉菜单关闭)立即隐藏;新 approach 先清 旧环,VISUAL_CURSOR=0 时连旧环一起隐藏。 canBecomeKey/Main=false、ignoresMouseEvents=true、localFrame 空或与窗口 不相交不显示、VISUAL_CURSOR=0/false/no/off 全跳过、turn-ended/reset 联动 隐藏等既有约束不变。同步 ARCHITECTURE.md、exec-plan、history 与仓库 skill。 验证:swift build(Build complete);swift test(207 tests / 1 skipped / 0 failures,新增 14 个去抖与高亮生命周期单测);./scripts/check-docs.sh 通过。 未执行:GUI 端到端验证(验收轮占用桌面)。
用户实测光标小箭头在原位小幅抖/跳,且 AI 空闲不动手时也在抖。空闲抖动说明 是周期性更新在驱动 overlay,根因三处: - idle 定时器泄漏:startIdleAnimation 直接覆盖 idleTimer 引用、从不 invalidate 旧定时器,而 settle / pulseClick 只 start 不 stop。每个 animated 动作 (settle → pulseClick)都永久漏掉一个 60Hz writer,多个定时器同时推进共享 静态 idlePhase,原本平滑的微摆变成帧间不等距的阶梯,且泄漏的定时器再也 停不掉(stopIdleAnimation 只认最新一个)。 - 每 tick 强制重排面板:idle tick 第一句就是 refreshActiveOrderingIfNeeded → configureOrdering(forceReorder: true) → panel.order(.above, relativeTo:), 空闲时每 16.7ms 重排一次 NSPanel。 - 亚像素 frame 写入:placeCursor 无条件 setFrameOrigin(弹簧积分器的分数点 tip),idle 期弹簧向 resting 点渐近收敛,每帧写一个亚像素新值,AppKit 量化 到 backing store 时在边界整像素跳变,同时每帧 needsDisplay 全量重绘。 修法(新增 SoftwareCursorIdlePolicy.swift,改 SoftwareCursorOverlay): - CursorIdleDriver 成为 idle 定时器唯一 owner:start 前先 stop;tick 先过 lastInteractionAt + 有界节拍的“空闲守卫”,越界即自 invalidate 并直接 return;move / reposition / settle / pulse 刷新 lastInteractionAt。 - CursorPanelWriteGate 统一 panel 写入:frame origin 取整到整点且数值相等即 整帧跳过;level 相等不写;order(.above, relativeTo:) 只在显示/隐藏或目标 窗口变化时执行,idle tick 不再重排;隐藏 / reset 清空记忆。 - refreshActiveOrderingIfNeeded 只处理目标窗口消失,不再强制重排活着的目标 窗口;render state 未变化时不再写 view / needsDisplay。 - 新增 OPEN_COMPUTER_USE_VISUAL_CURSOR_IDLE_SWAY_MS(默认 1000ms,0 表示动作 落定即冻结)与 OPEN_COMPUTER_USE_VISUAL_CURSOR_DEBUG_STATS=1 计数输出。保留 1 秒有界微摆,因为 runCursorIdleSmoke 仍断言 idle 期 rotation 在变。 已排除:TargetHighlightLifetime 的 220ms 看门狗只碰高亮环自己的 panel; cursor 目标点只在动作时由 visualCursorTarget 从 snapshot 算一次,不存在每 tick 重读元素 frame 的漂移路径。 验证:swift build(Build complete);swift test(214 tests / 1 skipped / 0 failures,基线 207 + 新增 7 个空闲静止单测);./scripts/check-docs.sh 通过。 同步 ARCHITECTURE.md、exec-plan、history 与仓库 skill。
为什么:该技能文档是使用者了解 macOS quiet 默认行为、光标去抖与后台点击开关的唯一入口, 开关说明缺失会导致按默认行为误判工具能力(例如不知道 auto 何时会尝试 sky_click)。 行为变化:macOS Operating Rules 补充 OPEN_COMPUTER_USE_AUTO_SKY_CLICK(默认关;auto 在 accessibility 与 app_post 之间尝试一次 sky_click,失败即回落,绝不升级到 global); 并把 OPEN_COMPUTER_USE_VISUAL_CURSOR_IDLE_SWAY_MS 的默认值由隐式 "1s" 改为显式 1000 ms。 验证:仅文档改动,未执行构建;git diff --cached --check 通过(无 whitespace 错误)。
为什么:文档此前只说明了光标去抖与空闲静止行为,未说明如何整体关闭叠加层, 使用者无法在不改代码的前提下关掉虚拟光标与目标高亮环。 行为变化:在 macOS quiet-mode 段补充 OPEN_COMPUTER_USE_VISUAL_CURSOR(默认开; =0 / false / no / off 同时关闭虚拟光标与目标高亮环)。 验证:仅文档改动,未执行构建;git diff --cached --check 通过(无 whitespace 错误)。
三个根因与修法:
1) 光标整支消失(不是停止摆动)。panel level 取目标窗口 layer(普通窗口 = 0),
而 OCU 是永不 active 的 accessory app,.normal 层级属于非活跃窗口组,任何一次
前台切换都会让活跃 app 的窗口盖到光标之上;当时唯一把 level-0 panel 抬到目标
窗口之上的 order(.above, relativeTo: 外部 windowID) 只在动作开始时执行,系统级
重排后不会重新置前;目标窗口消失时 level 又回落到 0 + orderFront,对非活跃 app
等于不可见;此外 30 秒 idle 隐藏定时器会在模型长思考(>30s 无工具调用)时把整支
光标淡出。修法:新增 cursorOverlayBaseLevel(.floating) 与纯函数
cursorPanelOrdering(普通窗口固定 .floating 且不再相对外部窗口排序,仅当目标
窗口自身 >= .floating 才保留相对排序),删除整套 idle hide(隐藏只剩 turn-ended /
reset / OPEN_COMPUTER_USE_VISUAL_CURSOR=0),新增
didActivateApplicationNotification 触发的重新置前(只重排、不写 frame、不改
level),并把窗口交互面抽成 CursorOverlayPanelHosting /
CursorOverlayEnvironment 注入缝,使「目标窗口消失 / 别的 app 抢到前台后光标仍
visible 且 frame 不变」可无 window server 回归。
2) 仍会抢焦点。element_index 的 .auto 与 .accessibility 两条路径硬编码
allowActivationFallback: true,直接落到 activateClickTarget 的 AXRaise /
AXMain / AXFocused(canUseActivationOnlyClickFallback 恰好允许 AXWindow)。
修法:新增 activationOnlyClickFallbackAllowed,与窗口恢复共用同一个
allow_window_recovery 开关;默认路径只剩 AXPress / AXConfirm / AXOpen /
AXShowMenu、子孙与命中点候选、自动滚入视口、postToPid / sky_click,坐标点击
路径恒定不激活。
3) 目标高亮环「看不见」。核查结论是触发了也画了,只是视觉上等于没有:旧样式
stroke 与 fill 都用 NSColor.controlAccentColor,与浏览器原生 focus ring 同色
同形;重做第一版把光标的白色描边(white 0.90@0.92)当环描边,浅色页面上实测
几乎不可见;环 panel level 也跟随目标窗口 layer (=0),存在与 (1) 相同的被盖 /
被外部窗口带走风险。修法:新增 TargetHighlightStyle(codex 默认 / plain 逐像素
回退,env OPEN_COMPUTER_USE_TARGET_HIGHLIGHT_STYLE),深色描边
0.38/0.36/0.35@0.85(光标主体色)+ 1pt 浅色外缘 white 0.90@0.55(光标边缘色)
+ 33pt 雾状光晕(光标 fog 半径与颜色),环 level 与光标统一取 .floating 下限;
官方 binary 没有「目标高亮」概念,因此只对齐光标视觉语言而不冒充官方行为。
可验证性:新增单屏调试入口 debug-highlight [--seconds N] [--display N](只画本地
overlay、不做 AX 调用、不发事件、不激活任何 app,--seconds 上限 60s,defer 确定性
收尾),打印可直接喂给 screencapture -R 的 screen-state 矩形;
OPEN_COMPUTER_USE_DEBUG_HIGHLIGHT=1 时每次 approach 打一行
[open-computer-use] highlight present/skip,用于区分「没触发」(坐标点击本来就不画
环,无任何行)与「触发了但被几何条件跳过」。
验证:swift build Build complete;swift test 245 tests / 1 skipped / 0 failures
(基线 214,新增 31:CursorOverlayVisibilityTests 7、ClickActivationPolicyTests 4、
TargetHighlightStyleTests 11、DebugHighlightTests 9);./scripts/check-docs.sh 通过;
debug-highlight --seconds 2 --display 1 打印 ring=934,548,180,56 并在 3.1s 内 exit=0,
pgrep -x OpenComputerUse 无残留进程/panel;截图证据
tmp/ocu-highlight-21daf46/{codex-ring,plain-ring,codex-ring-v2}.png。
官方 Codex Computer Use 没有“目标高亮”概念:逆向文档全目录 grep -in highlight 零命中,运行时可视化只有 Software Cursor(126x126 画布、 CursorView/SoftwareCursorStyle/FogCursorStyle、Bezier 移动、click pulse)。 OCU 自建的 TargetHighlight* 属本项目扩展,保留 codex|plain 两种样式属冗余 代码,用户裁定整体物理删除。 - 删除 TargetHighlightOverlay / TargetHighlightLifetime / TargetHighlightStyle - VisualInteractionChoreographer 去掉 showTargetHighlight 注入、 presentTargetHighlight 与 approach 的 record/snapshot 形参,顺序收敛为 move -> settle -> 调用方动作;ComputerUseService.approachVisualTarget 只收 VisualCursorTarget - SoftwareCursorOverlay.reset() 去掉环清理;删除 OPEN_COMPUTER_USE_TARGET_HIGHLIGHT_STYLE / OPEN_COMPUTER_USE_DEBUG_HIGHLIGHT 与 highlight 诊断行 - debug-highlight -> debug-cursor:只画光标与脉冲,仍单屏 / --seconds 上限 60s / defer 确定性收尾 / 打印 screencapture -R 矩形 - 测试:删 TargetHighlightStyleTests(11) 与 DebugHighlightTests(9),改写为 DebugCursorTests(7);编排断言只保留 move/settle/reposition - 文档:ARCHITECTURE、exec-plan、新增 history;SKILL.md 注明只有软件光标与 点击脉冲(与官方一致) - 光标行为与默认参数不变(雾状光晕 / Bezier 到位 / click pulse / 400ms 去抖 / 常驻可见 / 单屏 / .floating 层级) 验证:swift build Build complete;swift test 222 tests / 1 skipped / 0 failures (删除前 245);./scripts/check-docs.sh 通过;debug-cursor --seconds 2 --display 1 截图 tmp/ocu-cursor-only-c434426/ 证明只有光标、无环。
Contributor
Author
Final state of this branch (HEAD
|
根因:currentSnapshot 直接复用 snapshotsByApp 缓存,windowBounds / targetWindowID 停在 get_app_state 时刻。用户在两次动作之间把窗口拖到另一块屏后, windowPointToGlobalPoint 与 makeVisualCursorTarget 仍按旧 frame 算点,overlay 因此留在窗口刚离开的那块屏,坐标点击与截图映射同样偏移;直到动作收尾的 refreshSnapshot 刷新缓存才恢复,观感就是“先留在旧屏、稍后又正常”。overlay 侧也没有任何跨进程窗口移动观察者。抢焦点与坐标陈旧不同源:unhide / activate / open -b / AXRaise / AXMain / AXFocused 已全部收敛在 window recovery 或 global 指针的 opt-in 之后,默认零激活,本次不新增激活路径。 行为变化: - 每次动作前用 CGWindowListCopyWindowInfo(.optionIncludingWindow) 重读目标窗口 几何(同窗口同尺寸仅移动 → 只 patch windowBounds,元素 frame 是窗口相对坐标; 尺寸变化或换了窗口 → 整份 refreshSnapshot;解析不到 → 保持原快照)。 - VisualCursorTarget 带上 screen-state 点与当时的窗口 frame,overlay 保存 CursorRestingAnchor;窗口移动/换屏后由 live frame 重推 tip 并立即落位。 - 新增只读 AXWindowMotionObserver(AXWindowMoved / AXWindowResized, OPEN_COMPUTER_USE_WINDOW_MOVE_WATCH=0 关闭)做事件式跟随;动作前另做兜底校验 “光标所在屏 == 目标窗口所在屏”,不一致就重算重画,两条路径都不隐藏光标。 验证:swift build 通过;swift test 231 tests / 1 skipped / 0 failures(基线 222/1/0,新增 CursorCrossScreenTests 9 个用例);./scripts/check-docs.sh 通过。
SKILL.md 是平台中立正文,只补环境变量与行为结论,不含本地路径或本地流程: - allow_window_recovery=true 会把目标窗口拉回原位且点击成功,但会激活该 App (frontmost 改变)从而打断用户前台,保持默认关闭。 - 显式 click_method="sky_click" 对页面内容区按钮安全(不报错、渲染进程 PID 不变、 frontmost 不变);导航/侧栏链接仍会打崩 Chromium 渲染进程(错误代码 5, 用工具栏重新加载恢复),导航走默认 AXPress。 - click_method="global" 会真实移动系统指针((1392.56, 991.32) → (-1050, 453.5), 跨屏后停留)并改变 frontmost,默认关闭,只在隔离实例验证。 - 窗口移动/换屏后 overlay 按实时 frame 重定位(-1453 → 256/259 → -1453, 未再次点击也已跟随);OPEN_COMPUTER_USE_WINDOW_MOVE_WATCH=0 可关闭观察者。 验证: - 仅改 skills/open-computer-use/SKILL.md(+2/-1 行),无源码改动。
Contributor
Author
|
Follow-up pushed to this branch: Commits
Switch defaults
Measured on a real session
|
Contributor
Author
|
Merge-order note: this branch is stacked on #68 (
Both are independently mergeable against |
Chromium 会把被滚动容器裁出视口的元素上报成退化帧(实测 x=344 y=87 w=42 h=1)。 elementNeedsScrollIntoView 旧实现把“退化几何”一律判为“不需要滚动”,于是 set_value 在 长 Web 表单里从不滚入视口:值写进去了,人的屏幕上却看不到——验收轮的 AI 代操作因此不可见。 - 有位置但宽/高 ≤ 1pt 的裁剪帧 → 视为需要滚动;nil/非有限值/无窗口矩形仍不滚。 - ensureElementVisible 增加 gated 诊断(OPEN_COMPUTER_USE_DEBUG_INPUT_FALLBACKS=1)。 - 测试覆盖退化帧、0×0 有位置帧、.zero、NaN 与既有窗口外场景。 验证:swift test 231 tests / 0 failures(1 skipped);装机后对屏外字段 set_value 实测视口 自动跟随(截图对比),字段值与既有变更计划未受影响。
问题: - Chromium + Radix 弹层打开时,组件把 portal 之外的文档 aria-hidden,Chromium 不再把这些 节点暴露到 AX 树,快照里页面被整体替换成只剩列表框;上层拿不到表单 element_index,只能 反复 get_app_state,真实案例里同一工具被连续调用 5-8 次。 - element_index 只在当次快照内有效,动作后索引漂移,工具面被迫 read -> act -> read。 变更: - 新增 PopupSnapshot.swift:从应用根收集焦点窗口未渲染的临时子树(popover/sheet/menu/ listbox/dialog,以及 floating/dialog/system dialog/system floating 子角色的 window; 排除菜单栏与最小化窗口),用同一 RenderContext 与延续索引渲染并追加到 "--- popup ---" 之后,一次读取同时拿到窗口内容与弹层选项;没有任何打开的弹层时输出与旧版逐字节一致, 焦点窗口自身就是弹层时改为追加它盖住的其它窗口。 - 背景被 app 从 AX 树整体隐藏(Radix aria-hidden 的实测形态:AXWebArea 只剩一个打开中的 弹层且焦点在弹层内)时无法取回节点,改为追加 "--- popup note ---":说明背景索引暂时不可 用、先处理弹层、随后用 selector 继续,而不是让上层把页面消失误判成需要反复重读。 - 新增 ElementSelector.swift:selector 支持 role[name=...] / [name=...] / [role=...][name=...] / 裸名字;role 匹配 AX 角色、去 AX 写法、别名 family 与快照里的 本地化 role 文本;name 依次匹配 title/description/value/identifier/placeholder,先精确 再唯一前缀;按渲染父子链折叠 Chromium 的 wrapper + 文本叶子重复上报;未命中列出最近候选、 多处命中列出全部候选、语法错误直接报错,一律 fail closed。 - click / set_value 增加可选 selector(与 element_index 二选一,同时传报错);选择器在动作 执行的瞬间重新渲染树再解析,element_index 的语义与缓存快照保持不变。 - ElementRecord 增加 title/label/value/roleText/placeholder/parentIndex;MCP instructions 与 skill usage 文档补充弹层段落与选择器用法;get_app_state 的官方描述文案保持不变。 验证:swift build Build complete;swift test 257 tests / 1 skipped / 0 failures (231 -> 257,新增 PopupSnapshotTests 11 + ElementSelectorTests 15); ./scripts/check-docs.sh 通过。未做真机复验(硬约束:不重装/不重启正在运行的验收产物)。
人工验收期间在 Chromium + Radix 上实测两条: - 弹层打开时对背景元素用 selector 会 fail-closed 并列出候选(候选全是弹层项),重试无用, 必须先结束弹层交互:背景标题在弹层关闭前始终不可解析。 - `--- popup ---` 与 `--- popup note ---` 相互独立且可能同时缺席:Chromium listbox 的选项位于 web area 内时,web area 只剩该 listbox(选项完整含 selected),背景字段全部消失。该形态下 “选项可见”本身就是成功信号,不要因缺少标记判定读取失败。
实测教训:窗口被放在已不存在的副屏坐标(harness 日志已给 readback differs, not retried)时, macOS 只给浏览器 chrome、不给 web area(HTML 内容 缺失);同时软件光标因落在所有屏幕之外, 被 screenStatePointToAppKitGlobalPoint 原样返回,画到了另一块屏上。 把顺序写成硬前置:查窗口矩形 → 不在活动屏幕就移过去 → 再读一次并要求目标子树存在 → 最后动作。 并记录两条事实:软件光标默认开启(OPEN_COMPUTER_USE_VISUAL_CURSOR=0 可关);真实指针仅在 OPEN_COMPUTER_USE_ALLOW_GLOBAL_POINTER_FALLBACKS=1 时才会移动。
实测(人工验收期间):runner 把窗口放在记忆的副屏坐标,用户换屏后该屏幕不再覆盖此区域,macOS 只返回浏览器 chrome、不返回 web area;同时 screenStatePointToAppKitGlobalPoint 对不属于任何屏幕 的点原样返回坐标,软件光标被画到了另一块屏上,两个症状都没有任何显式提示。 - 新增纯函数:isPointOnActiveDisplay / windowIntersectsActiveDisplay / offDisplayWindowNote。 - AppSnapshot.renderedText 在窗口与所有活动屏幕无交集时追加 `--- display note --- …`; 窗口在屏幕上时输出逐字节不变。 - makeVisualCursorTarget(at:) 改为可选:点无法映射时返回 nil,调用方(含 debug-cursor)不再画光标。 验证:新增 OffDisplayWindowTests(5 例:点/窗口在屏内、离屏、无 bounds、无屏幕、光标目标跳过), 先 RED(函数未定义)后 GREEN;swift test 262 tests / 1 skipped / 0 failures。已 build+装机+重签 (旧 fa11198f → 新 718679e0)并重连。 live 限制:macOS/Chromium 会把窗口钳制回某块屏幕,无法通过移动窗口造出“不在任何屏幕”的状态; 该状态由屏幕被移除/休眠/重排引起。已用第二屏窗口复核“不产生误报”。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three fixes for driving a GUI with computer-use, aligned with Codex behavior:
Changes
P2 — Quiet window recovery (new default)
.readOnlyinstead of.allowActivation.unhide/activate/open -b/AXRaise/Main/Focusedrun only on explicit opt-in:allow_window_recovery=true, orOPEN_COMPUTER_USE_ALLOW_WINDOW_RECOVERY=1.-10005: cgWindowNotFound, with a hint to move the window to the current Space or un-minimize it.sky_clickre-capture stays always read-only.P1 — Automatic scroll-into-view
ensureElementVisible:AXScrollToVisible→ up to 6 ancestor levels → nearestAXScrollAreapaging (≤ 8 scrolls, frame re-read every round).click(element_index),set_valueandselect_text.OPEN_COMPUTER_USE_SCROLL_TARGET_INTO_VIEW(default on;=0/false/no/offdisables).P3 —
autotriessky_clickfirst (gradual rollout)OPEN_COMPUTER_USE_AUTO_SKY_CLICK=1,autotriessky_clickonce beforepostToPid, and only when: left button, 1–2 clicks, valid windowID/bounds, and the SPI is available.global.P4 — Target element highlight: removed
TargetHighlightOverlay.swift,TargetHighlightLifetime.swiftandTargetHighlightStyle.swiftare physically deleted, together with theOPEN_COMPUTER_USE_TARGET_HIGHLIGHT_STYLE/OPEN_COMPUTER_USE_DEBUG_HIGHLIGHTswitches, the[open-computer-use] highlight ...diagnostics, and the highlight step inVisualInteractionChoreographer(itsrecord/snapshotparameters are gone too).debug-highlight→debug-cursor(cursor + click pulse only; single screen,--secondscap, deterministic cleanup retained).grep -in highlightover the reverse-engineering reference and the runtime assets is empty, and its only visual surface is the software cursor (126×126 canvas, fog halo, Bezier travel, click pulse). Keeping acodex|plainstyle switch for a concept the official client does not have is dead code, so the feature was deleted outright rather than defaulted off.docs/ARCHITECTURE.mdrecords the removal so it is not reintroduced.P5 — Non-activating software cursor (the only visual surface)
OPEN_COMPUTER_USE_VISUAL_CURSOR_COALESCE_MS, default 400,0restores one animation per action).OPEN_COMPUTER_USE_VISUAL_CURSOR_IDLE_SWAY_MS, default 1000;0freezes immediately;OPEN_COMPUTER_USE_VISUAL_CURSOR_DEBUG_STATS=1dumps the write/tick counters when the cursor hides)..floatingpanel level so another app taking focus cannot bury it, and onlyturn-ended/ an explicit reset /OPEN_COMPUTER_USE_VISUAL_CURSOR=0removes it.canBecomeKey/canBecomeMain= false,ignoresMouseEvents= true), so a defaultclicknever raises, mains or focuses a window.NSScreen.screensis used only for coordinate mapping, availability checks andscreen(containing:)clamping, never to build per-screen panels.2b63fe5) — the official log order moves the software cursor and signals completion first, and only then performs the action (docs/references/codex-computer-use-reverse-engineering/software-cursor-overlay.md:190-196,243), so the sequence is now move → settle (120 ms) → action, funneled throughVisualInteractionChoreographer;perform_secondary_action/scrollgained the move stage too. A click pulse is played only when the coalescer reports that a travel animation actually happened (approach.playsClickPulse).open-computer-use debug-cursor [--seconds N] [--display N]paints the cursor and its click pulse on one screen (1-based--display, main by default; capped at 60 s;defercleanup) and prints the screen-state rect forscreencapture -R.P6 — Cross-screen / window-move re-anchor
get_app_statetime: before every actionComputerUseService.currentSnapshotre-reads the target window withCGWindowListCopyWindowInfo(.optionIncludingWindow). Same window and same size (move only) → onlywindowBoundsis patched, because element frames are window-relative; a size change or a different window → the snapshot is fully refreshed; if the window cannot be resolved → the previous snapshot is kept.VisualCursorTargetnow carries the screen-state point together with the window frame of that moment, and the overlay keeps aCursorRestingAnchor(window-local offset + screen-state frame), so a move to another display re-derives the tip from the live frame and re-places the cursor immediately.AXWindowMotionObserver(CursorWindowMotionWatch.swift) is a read-only accessibility watch onAXWindowMoved/AXWindowResizedthat re-places the cursor as soon as a move or resize ends. It is on by default;OPEN_COMPUTER_USE_WINDOW_MOVE_WATCH=0disables it.-1453→256/259→-1453with no further click in between. Before the fix the cursor stayed on the display the window had just left until the next action refreshed the snapshot.SnapshotWindowGeometry.swiftandCursorWindowMotionWatch.swift, changes inAccessibilitySnapshot.swift/ComputerUseService.swift/SoftwareCursorOverlay.swift/VisualInteractionChoreographer.swift, 9 new cases inCursorCrossScreenTests.swift, plusdocs/ARCHITECTURE.mdand the history notedocs/histories/2026-09/20260912-1510-cursor-cross-screen-reanchor.md.P7 — Measured behaviour of the three opt-in risk paths (defaults unchanged)
allow_window_recovery: trueunminimizes or unhides the target window at its previous frame and the click then succeeds, but it also activates that app:frontmostchanges to it, which interrupts the user's foreground. It stays opt-in / off by default for that reason.click_method: "sky_click"is safe for in-page content buttons (no error, unchanged Chromium renderer PIDs, unchangedfrontmost), but must not be used on app navigation or sidebar links: it crashed the Chromium renderer with error code 5 (recovered with a toolbar reload). Navigation keeps the default AXPress click.click_method: "global"(requiresOPEN_COMPUTER_USE_ALLOW_GLOBAL_POINTER_FALLBACKS=1) really moves the system pointer — measured from(1392.56, 991.32)on one display to(-1050, 453.5)on another, where it stayed — and changesfrontmost. Keep it disabled by default and validate only in an isolated instance (separate agent socket namespace plus a separate agent, cleaned up immediately), never in a shared or production session.Verification
swift build— passesswift test— 231 tests, 1 skipped (livesky_click, opt-in), 0 failures, measured on6bc1e9fwhen this update was pushed:Executed 231 tests, with 1 test skipped and 0 failures. That is +9 over the 222 reported after the highlight removal, accounted for by theCursorCrossScreenTestscases added in29934cf../scripts/check-docs.sh— passesdebug-cursor --seconds 2 --display 1prints the target/capture/screen rects and exits deterministically after 2 s; the 2 s capture shows the fog cursor only, with no ring anywhere around the target rect.Risks / not covered
AXScrollAreapaging branch ofensureElementVisibleonly has semantic-level unit tests.click(AXWindow role only) is not covered by the opt-in.frontmostchanges), so P2 keeps the activating path behind an opt-in.Restoring previous behavior
OPEN_COMPUTER_USE_ALLOW_WINDOW_RECOVERY=1— re-enables unhide/activate/raise.OPEN_COMPUTER_USE_SCROLL_TARGET_INTO_VIEW=0— disables automatic scroll-into-view.OPEN_COMPUTER_USE_WINDOW_MOVE_WATCH=0— disables the read-only window-move watch; the pre-action screen-consistency re-anchor stays active.OPEN_COMPUTER_USE_AUTO_SKY_CLICK— disables the sky_click attempt.OPEN_COMPUTER_USE_VISUAL_CURSOR— hides the software cursor (there is no element highlight left to configure; that feature was removed in P4).Upstream overlap: PR #65 (@hyprcat, open) independently makes background operation complete: key_method=sky_key, covered/other-Space window resolution via WindowCapture.resolve, occlusion keep-alive and window_placement=agent_display. It shares this PR's goal (never activate/raise the user's foreground app) and does not conflict semantically with P1/P2/P5; P3 (auto tries sky_click once, OPEN_COMPUTER_USE_AUTO_SKY_CLICK=1, default off, always falls back to postToPid) is orthogonal to #65's sky_key and keeps #65's "explicit opt-in" convention. Merge conflicts with #65 are expected in AccessibilitySnapshot.swift, ComputerUseService.swift, ComputerUseToolDispatcher.swift, ToolDefinitions.swift and docs/ARCHITECTURE.md (both sides add optional tool parameters); resolution is a union, keeping both window_placement/key_method and allow_window_recovery, with sky_click/sky_key result refreshes staying read-only unless allow_window_recovery is explicitly passed. We are happy to rebase on #65 once it lands.