Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ evaluation. `ocu repl` keeps them for the current terminal session until
`.exit`, Ctrl-D, or termination; `.reset` clears its bindings. On macOS, the
separate hidden `Open Computer Use.app` permission agent may remain resident so
later commands reuse the same permission identity. `ocu mcp` continues to expose
the nine core native tools, plus `query` on macOS.
the nine core native tools, plus `query` and `list_windows` on macOS.

The npm launcher itself currently requires Node.js 18 or newer. Its help always lists
`js` / `repl`, while `ocu capabilities --json` reports whether Node, the REPL
Expand Down
3 changes: 3 additions & 0 deletions apps/OpenComputerUseLinux/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ func (s *service) callTool(name string, args map[string]any) toolCallResult {
if windowPlacement != "keep" {
return textResult("window_placement '"+windowPlacement+"' is not supported on Linux", true)
}
if _, ok := args["window_id"]; ok {
return textResult("window_id is not supported on Linux", true)
}
includeAX, err := optionalBool(args, "include_ax")
if err != nil {
return textResult(err.Error(), true)
Expand Down
4 changes: 4 additions & 0 deletions apps/OpenComputerUseLinux/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,10 @@ func TestLinuxWindowPlacementSchemaAndUnsupportedAgentDisplay(t *testing.T) {
if !result.IsError || result.Content[0].Text != "window_placement 'agent_display' is not supported on Linux" {
t.Fatalf("agent_display result = %#v", result)
}
result = newService().callTool("get_app_state", map[string]any{"app": "Text Editor", "window_id": 41})
if !result.IsError || result.Content[0].Text != "window_id is not supported on Linux" {
t.Fatalf("window_id result = %#v", result)
}
}

func TestDoctorJSONRejectsUnsupportedPermissionDiagnostics(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions apps/OpenComputerUseWindows/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ func (s *service) callTool(name string, args map[string]any) toolCallResult {
if windowPlacement != "keep" {
return textResult("window_placement '"+windowPlacement+"' is not supported on Windows", true)
}
if _, ok := args["window_id"]; ok {
return textResult("window_id is not supported on Windows", true)
}
includeAX, err := optionalBool(args, "include_ax")
if err != nil {
return textResult(err.Error(), true)
Expand Down
4 changes: 4 additions & 0 deletions apps/OpenComputerUseWindows/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,10 @@ func TestWindowsWindowPlacementSchemaAndUnsupportedAgentDisplay(t *testing.T) {
if !result.IsError || result.Content[0].Text != "window_placement 'agent_display' is not supported on Windows" {
t.Fatalf("agent_display result = %#v", result)
}
result = newService().callTool("get_app_state", map[string]any{"app": "Notepad", "window_id": 41})
if !result.IsError || result.Content[0].Text != "window_id is not supported on Windows" {
t.Fatalf("window_id result = %#v", result)
}
}

func TestDoctorJSONRejectsUnsupportedPermissionDiagnostics(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
- 对真实 app 的 `get_app_state` / action tool 入口,当前只保留一层密码管理器 bundle denylist:bundle-id 直传时直接返回 safety denial;名称匹配时默认不解析到这些 app。终端、Chrome / Atlas 和系统组件不再属于内置阻止目标。
- 普通 app 的 element frame 当前按“窗口左上角为原点”的 window-relative 坐标输出,便于后续把 `element_index` 和截图坐标统一到同一套参考系。
- 三个平台的显式 `get_app_state` 均支持 `include_ax` / `include_screenshot`,默认 true,至少保留一项。JS 对应 `includeAX` / `includeScreenshot`,CLI snapshot 提供同名连字符参数。关闭 AX 跳过树、焦点和选区,并使旧快照 / 查询索引及别名失效;关闭截图跳过捕获与编码,元素操作仍可用,截图坐标操作须重新获取图片。参数经 macOS app agent 转发;不改变动作回读,也不做自动图片去重。见 [按需观察](selective-observation.md)。
- macOS 额外提供 `query` 和 `select_text`,当前 native surface 为 11 个工具;Linux / Windows 仍为 9 个核心工具。JS `app.selectText(elementIndex, text, {prefix?, suffix?, selection?})` 复用快照或 query 索引,支持选中文字及前后光标。按字面文本和 UTF-16 定位、拒绝歧义;写入前及返回前检查会话,验证文本未变且实际选区等于请求,即使关闭完整回读也不能省略验证。失败通过 native `isError` / JS 异常传出;结果刷新使用 read-only recovery,不通过激活、键盘或真实指针补救。
- macOS 额外提供 `query`、`select_text` 和 `list_windows`,当前 native surface 为 12 个工具;Linux / Windows 仍为 9 个核心工具,并对 `get_app_state` 的 `window_id` 返回不支持错误。`list_windows` 列出 app 在当前 Space 的 AX 窗口及其 CGWindowID;`window_id` 把快照、截图、焦点和后续动作回读绑定到该窗口(不激活、不抬起),直到不带 `window_id` 再次读取或该窗口关闭。JS `app.selectText(elementIndex, text, {prefix?, suffix?, selection?})` 复用快照或 query 索引,支持选中文字及前后光标。按字面文本和 UTF-16 定位、拒绝歧义;写入前及返回前检查会话,验证文本未变且实际选区等于请求,即使关闭完整回读也不能省略验证。失败通过 native `isError` / JS 异常传出;结果刷新使用 read-only recovery,不通过激活、键盘或真实指针补救。
- `click` / `set_value` 在执行真实动作前后,会额外驱动一层透明 `SoftwareCursorOverlay` window:两者的移动阶段现在共用一条 heading-driven 的官方风格 motion 内核,显式把“当前 cursor 朝向”和“最终 resting pose”一起喂给选路器,优先生成需要时先掉头、再沿车头方向推进的 C 形/单侧大弧轨迹;首次显示时按官方 binary 的 fresh state 从 AppKit 全局 `(0,0)` window origin 生成起点,后续动作继续复用上一帧 visible tip。真正显示出来的 cursor 不再直接等于 path sample,而是经过一层独立的 visual dynamics 状态,把 visible tip、velocity、angle 和 fog/offset 持续推进。`click` 结尾会衔接 click pulse 和更明显但仍然很小的 rotate wobble,`set_value` 则只做 settle / idle,不给 pulse;两者收尾后会在目标点继续保持 idle 状态,等待下一次动作时 tip 保持 anchored、只保留可感知的小角度摆动;只有连续 30 秒没有新动作时才做 cleanup,这样连续 tool call 不会反复从 fresh `(0,0)` 起步;如果宿主在任务 / turn 结束时发出 `turn-ended`,cursor 会立即消失并清掉本轮位置状态。
- overlay 用目标点相对窗口的锚点跟随窗口移动;行进、settle、点击反馈和 idle 都读取最新缓存帧,发现位置变化时直接重定位可见 tip 并清掉旧速度。窗口帧在后台串行读取,最多 30 Hz、同时最多一个请求,避免 AX 阻塞渲染线程;优先读取快照持有的 AX 窗口,仅在没有 AX 元素时使用窗口列表,读取失败保留最后有效帧。只保留当前窗口的追踪状态,reset / idle 超时后释放;这层跟随只调整显示位置,输入投递仍走既有路径。
- overlay 的 visual style 不再自己从官方 app bundle 裁 `SoftwareCursor` 小图;主 MCP runtime 现在和 `CursorMotion` 一样优先渲染仓库里沉淀的 `official-software-cursor-window-252.png` baseline,只有资源缺失时才退回 `OpenComputerUseKit` 内部的程序化 pointer/fog fallback。命中点 anchor 仍固定在 `126x126` 画布里的同一组 tip-offset 上;glyph 自身的 neutral heading 继续沿用 `CursorMotion` / 官方 baseline 的 `-3π/4`。主 runtime overlay window 按 AppKit 全局坐标移动,因此在把 AX / `CGWindowList` 产出的 y-down screen-space 点击目标喂给 overlay 之前,会先转换成对应屏幕的 AppKit 全局坐标;路径选路用屏幕上实际可见的 AppKit forward heading,进入 visual dynamics / render state 前则把 velocity 的 y 轴翻回 CursorMotion 的 y-down screen state,再交给 AppKit 绘制层做角度和 `dy` 翻转。程序化 fallback 保留 neutral artwork correction,把它的天然轮廓轴对齐到 `CursorMotion` / 官方 baseline 的 `-3π/4` forward 方向,但不让实验线依赖 runtime 代码。
Expand Down
2 changes: 1 addition & 1 deletion docs/QUALITY_SCORE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

| 区域 | 评分 | 原因 | 下一步 |
| --- | --- | --- | --- |
| 产品面 | B | 已经有 Swift 本地 `computer-use` MCP server、默认 app 模式权限引导,以及共享的 9 个核心 tools;macOS 另有 query、select_text,JS 可调用。 | 继续收敛复杂 AX 场景下的 state rendering 细节、权限 UI 和更清晰的用户错误提示。 |
| 产品面 | B | 已经有 Swift 本地 `computer-use` MCP server、默认 app 模式权限引导,以及共享的 9 个核心 tools;macOS 另有 query、select_text、list_windows(以及 get_app_state 的 window_id),JS 可调用。 | 继续收敛复杂 AX 场景下的 state rendering 细节、权限 UI 和更清晰的用户错误提示。 |
| Windows runtime | C | 已新增独立 Go `.exe`,通过 Windows UI Automation + Win32 window message 暴露同样 9 个 tools、MCP server 和 `call --calls`;默认不再自动启动 app、执行 `SetFocus`,或让 `type_text` 走可能抢前台的 UIA text fallback;截图会过滤隐藏、最小化、cloaked、离屏和退化窗口,并在 Go 层复核 PNG,但整体仍是功能性第一版。 | 维护 WPF 观察 / 输入 / 截图边界 fixture 和 Windows PR smoke,扩展更多工具与 toolkit;补 installer/signing,以及更原生的 Go UIA 实现或更稳定的 bridge。 |
| Linux runtime | C | 已新增独立 Go binary,通过 Python GI / AT-SPI2 暴露同样 9 个 tools、MCP server 和 `call --calls`;Ubuntu GNOME VM 已跑通 `list_apps`、MCP tools list 和 Text Editor 8-tool sequence,并已接入 npm bundled artifact 分发,但截图在 GNOME Wayland 下仍只能 best-effort,coordinate input 也不是通用后台模型。 | 扩展已有的隔离 GTK 动作回读 smoke,补完整工具覆盖、portal/compositor screenshot 路径,以及更原生的 Go D-Bus/libatspi bridge。 |
| macOS Locked Use | D | 已有签名 Broker / 插件、双进程保护、原生租约与恢复入口,离线和遮罩 rehearsal 有回归;安装恢复和无许可双保护释放已实测,独立无遮罩 Return 解锁 / 重锁已实测,受保护自动解锁 / AX / SCK / 重锁及隔离 legacy Keychain 已有一轮实测通过,完整会话认证状态 / 故障链路尚未通过实测;正常启用改为管理员 enable 与签名校验,普通 enable / 正常 MCP 的完整 GUI 闭环已实测。 | 物理覆盖已确认,继续完成 Data Protection / 会话认证状态、Secure Input / 崩溃 / 显示器故障回归及正式发布制品检查。 |
Expand Down
51 changes: 51 additions & 0 deletions docs/histories/2026-10/20261023-0206-list-windows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## [2026-10-23 02:06] | Task: Add list_windows and get_app_state window_id

### Execution Context

- Agent ID: Claude Code 子 Agent(运行时未暴露 ID)
- Base Model: Claude Opus 5.5(`claude-opus-5-5`)
- Runtime: Claude Code(Claude Agent SDK),macOS 本机

### User Query

把 fork 里的 `list_windows` 移植到上游,回应 #83 的第 1 条(多窗口 app 中读取和操作落到错误窗口);再判断 `get_app_state` 能否小而安全地按窗口绑定,能就一起加上,只能列出窗口却不能选中其一只算半个功能。

### Changes Overview

**Scope:** macOS `OpenComputerUseKit`、Linux / Windows Go runtime 的参数校验、Node REPL、文档。

- 新增 native `list_windows`:只解析已运行的 app,不启动、不激活、不抬起;按 AX `AXWindows` 返回当前 Space 的窗口,每项包含 `window_id`(与 `query` 相同的 CGWindowID,经现有 `_AXUIElementGetWindow` 绑定得到)、`title`、`subrole`、`focused`、`minimized` 和屏幕坐标 `bounds`。与 `query` 一样在读取前检查会话租约,输出复用 `query` 的 JSON 编码。
- `get_app_state` 新增 `window_id`:用已有的 `windowElement(for:)` 找到该 AX 窗口作为快照根;不走激活 / 抬起的恢复路径,不按标题回退到别的窗口;找不到时报错并提示调用 `list_windows`。
- 绑定会延续到后续回读:service 按进程记住 `window_id`,动作回读与 `sky_key` 的回读继续读该窗口;再次不带 `window_id` 调用 `get_app_state` 即解除;窗口已关闭时回读回到 key window。
- 绑定窗口不是 key window 时,app 的键盘焦点不算这个窗口的焦点:快照不报告它,`type_text` 因此拒绝写入另一个窗口的输入框,而不是静默写进去。`sky_key` 会把按键送到绑定窗口。
- Linux / Windows:不实现 `list_windows`(与 `query`、`select_text` 一样返回 `unsupportedTool`),`get_app_state` 收到 `window_id` 时明确报不支持,不静默忽略。
- Node REPL:`app.listWindows()`,`getAXState({ windowId })`。
- 更新工具清单与描述测试、会话租约测试、参数校验测试,以及 SKILL、usage、js-repl、README、ARCHITECTURE、QUALITY_SCORE 和功能发布记录。

### Design Intent

#83 的第 1 条是缺功能:快照永远绑定 app 的 focused window,调用方既看不到其他窗口,也选不了。`query` 已经接受 `window_id`,这里补上取得 id 的途径,并让 `get_app_state` 用同一个 id 绑定快照。绑定必须覆盖动作回读,否则点击之后的回读会切回 key window 并替换缓存,后续索引又落到另一个窗口,问题原样回来。键盘输入天然跟随 app 的焦点,因此只在焦点不属于绑定窗口时让快照不报告焦点,避免 `type_text` 写错窗口;更大的“窗口作用域索引”不在本次范围。

### Validation

- `swift test`(在共享 GUI 锁下运行完整套件):383 tests,7 skipped,0 failures。新增 / 更新:工具数量与名单、`list_windows` 只读注解与 schema、`window_id` schema、非法 `window_id`(0、-1、1.5、true、"7")、缺少 `app`、不存在的 app 返回 `appNotFound` 而不启动、会话租约撤销时 `list_windows` 在解析目标前失败。
- Node REPL:45 tests passed(新增 `listWindows` / `windowId` 转发与异常响应)。
- Go:Windows `go test` 通过;Linux 新增用例通过,Linux 包里 6 个需要 Linux python3 bridge 的既有用例在 macOS 上失败,与未改动的 main 相同;两平台 `go vet`(GOOS=linux / windows)通过。
- 真机:一个不激活、不抢焦点的探针 app 打开两个窗口 A(key)/ B。`list_windows` 列出两者且 A 为 focused;不带 `window_id` 读到 A;`window_id=B` 读到 B 的树;按 B 的索引点击后回读仍是 B 且按钮状态已变;A 的输入框持有焦点时,`type_text` auto 报错而不是写入 A;`sky_key` 把文字打进 B;再次不带 `window_id` 读回 A;不存在的 id 明确报错。前台 app 前后不变,探针已退出。
- `make check-docs`、`git diff --check` 通过。

### Files Modified

- `packages/OpenComputerUseKit/Sources/OpenComputerUseKit/AccessibilitySnapshot.swift`
- `packages/OpenComputerUseKit/Sources/OpenComputerUseKit/ComputerUseService.swift`
- `packages/OpenComputerUseKit/Sources/OpenComputerUseKit/ComputerUseToolDispatcher.swift`
- `packages/OpenComputerUseKit/Sources/OpenComputerUseKit/ToolDefinitions.swift`
- `packages/OpenComputerUseKit/Sources/OpenComputerUseKit/MCPServer.swift`
- `packages/OpenComputerUseKit/Tests/OpenComputerUseKitTests/OpenComputerUseKitTests.swift`
- `packages/OpenComputerUseKit/Tests/OpenComputerUseKitTests/TargetedQuerySafetyTests.swift`
- `packages/OpenComputerUseKit/Tests/OpenComputerUseKitTests/LockedUseDispatchTests.swift`
- `apps/OpenComputerUseLinux/main.go`、`apps/OpenComputerUseLinux/main_test.go`
- `apps/OpenComputerUseWindows/main.go`、`apps/OpenComputerUseWindows/main_test.go`
- `scripts/node-repl/open-computer-use-repl.mjs`、`scripts/node-repl/open-computer-use-repl.test.mjs`
- `skills/open-computer-use/SKILL.md`、`skills/open-computer-use/references/usage.md`
- `docs/references/js-repl.md`、`docs/ARCHITECTURE.md`、`docs/QUALITY_SCORE.md`、`docs/releases/feature-release-notes.md`、`README.md`
5 changes: 5 additions & 0 deletions docs/references/js-repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ verification throw through `js`. Verification still reads the applied range when
action read-back is disabled. The tool does not use keyboard/pointer fallback or
activate the application. Linux/Windows report the native unsupported-tool error.

On macOS, `await app.listWindows()` returns the app's windows as
`[{ window_id, title, subrole, focused, minimized, bounds }]`, and
`app.getAXState({ windowId })` reads one of them instead of the key window; later
actions stay on it until a state read without `windowId`.

Discovery is available through `await cua.getState()` and
`await cua.listApps()`. `listApps()` normalizes the native human-readable app
catalog into an array of `{ id, displayName, lastUsedDate?, useCount?,
Expand Down
1 change: 1 addition & 0 deletions docs/releases/feature-release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

| 日期 | 功能域 | 用户价值 | 变更摘要 |
| --- | --- | --- | --- |
| 2026-10-23 | macOS 多窗口选择 | 多窗口 app 中 Agent 能看到全部窗口并指定要读取和操作的那一个,不再只能落在 key window。 | 新增 native `list_windows` 与 JS `app.listWindows()`,返回当前 Space 的 AX 窗口、`window_id`、标题、subrole、焦点、最小化状态和屏幕坐标;`get_app_state` 新增 `window_id`,按 CGWindowID 绑定快照、截图、焦点与后续动作回读,不激活、不抬起窗口,窗口关闭后回到 key window;Linux / Windows 对 `window_id` 明确报不支持。 |
| 2026-10-13 | Windows 滚动与坐标 | Windows 上 `scroll` 不再在默认配置下直接失败;150% 缩放且窗口位于屏幕右侧时,`get_app_state` 也能返回截图和窗口 bounds。 | 修复 `scroll` 请求漏传 `windowBounds` 导致坐标回退必然报 `No usable screenshot coordinates`;滚轮消息保留屏幕坐标并修正横向方向,PowerShell bridge 启动时对齐 DPI 物理坐标;`ScrollPattern` 在当前状态不可滚动时回退到坐标路径而不是让整个 tool call 失败。 |
| 2026-10-13 | Windows 全局输入兜底 | 需要真实指针或键盘的 Windows 动作(`click_method=global`、WinUI 键盘输入、子控件点击)在显式授权后可用,默认路径完全不变。 | Windows 实现 `click_method=global` 真实指针点击;`drag` / `scroll` 的坐标回退在 `OPEN_COMPUTER_USE_ALLOW_GLOBAL_POINTER_FALLBACKS=1` 时使用真实指针,按键另需显式 `key_method=global`,`app_post` 仍保持 HWND 消息语义;未授权时在任何物理输入前拒绝,核对目标前台 / 命中 / 几何并在异常时释放输入。 |
| 2026-10-17 | 1.1.2 修复发布 | 软件光标持续跟随移动窗口,Linux 批量动作可减少回读开销,安装链接统一到新仓库。 | 同步所有版本源至 1.1.2;发布窗口锚点跟踪、Linux 动作回读配置与仓库改名后的 npm / 插件元信息,沿用既有命令和 App 签名身份。 |
Expand Down
Loading