baa-conductor

git clone 

commit
8a2524a
parent
7526ab5
author
im_wower
date
2026-03-30 01:29:19 +0800 CST
docs: add a11y gui control plan
1 files changed,  +303, -0
A plans/A11Y_GUI_CONTROL.md
+303, -0
  1@@ -0,0 +1,303 @@
  2+# A11Y GUI Control — 基于无障碍树的浏览器与操作系统控制
  3+
  4+> 日期: 2026-03-30
  5+> 状态: 验证完成,待开发
  6+> 参与: Claude (验证执行) + George (架构指导)
  7+
  8+---
  9+
 10+## 核心思路
 11+
 12+当前 AI 操作 GUI 有两条主流路径,都是「盲人摸象」:
 13+
 14+- **截图流派**(Claude Computer Use、mac-use):靠视觉模型看截图猜点击位置。慢(3-5s/帧)、不稳定(分辨率/主题变化就失效)、信息过载。
 15+- **DOM/CDP 流派**(OpenClaw Snapshot):拿结构化 DOM 树。噪音大(满屏 div 嵌套),CSS class 不携带语义。
 16+
 17+第三条路:**Accessibility Tree(无障碍树)**。
 18+
 19+盲人使用电脑靠的是屏幕阅读器(VoiceOver/JAWS/NVDA),读的就是操作系统和浏览器维护的无障碍树。每个 UI 元素都有 role(角色)、name(名称)、value(值)、actions(可执行动作)。这棵树天生就是为「看不见界面的使用者」设计的——AI 恰好就是一个看不见界面的使用者。
 20+
 21+核心优势:
 22+- **语义精确**:`[AXButton] 发送提示` vs 截图里的一个蓝色方块
 23+- **操作直接**:`click el` vs 计算坐标再模拟鼠标
 24+- **速度快**:~1.5s 拿到完整树 vs 截图+视觉识别 3-5s
 25+- **跨 app**:macOS AXUIElement API 管整个操作系统,不限于浏览器
 26+- **20年基础设施白嫖**:WAI-ARIA 标准、VoiceOver 兼容性测试都已经做好了
 27+
 28+---
 29+
 30+## 验证过程与结果
 31+
 32+### 实验1:浏览器内无障碍树(CDP)
 33+
 34+**环境**:Anthropic 容器内 Playwright + Chromium
 35+
 36+**方法**:通过 CDP 的 `Accessibility.getFullAXTree` 获取网页无障碍树。
 37+
 38+**结果**:
 39+- example.com:DOM 11 节点 → AX 有意义 8 节点,1 个可操作元素 `[link] Learn more`
 40+- Hacker News:DOM 814 节点 → 229 个可操作元素,30 条新闻的标题/分数/作者/时间/评论数全部可读
 41+- HN 的 upvote 箭头是 `[link] "(无名称)"`,暴露了无障碍标注质量问题
 42+
 43+**关键代码**:
 44+```javascript
 45+const cdp = await ctx.newCDPSession(page);
 46+await cdp.send('Accessibility.enable');
 47+const { nodes } = await cdp.send('Accessibility.getFullAXTree');
 48+```
 49+
 50+### 实验2:操作系统级无障碍树(macOS AXUIElement)
 51+
 52+**环境**:conductor (mini) → AppleScript → System Events
 53+
 54+**方法**:`entire contents of window 1` 获取 app 窗口内所有 UI 元素。
 55+
 56+**结果 — Chrome**(49 个元素,1472ms):
 57+```
 58+[AXToolbar]
 59+  [AXButton] (返回)
 60+  [AXButton] (前进)
 61+  [AXButton] (重新加载)
 62+  [AXTextField] (地址和搜索栏) = "claude.ai/chat/..."
 63+[AXTabGroup]
 64+  [AXRadioButton] (几何作为演出...) = "true"   ← 当前标签
 65+  [AXRadioButton] (OpenClaw的GUI控制机制...) = "false"
 66+  [AXRadioButton] (维度与结构的互动...) = "false"
 67+  [AXRadioButton] (Google Gemini...) = "false"
 68+  [AXRadioButton] (Conductor 鉴权...) = "false"
 69+```
 70+
 71+**结果 — Terminal**:
 72+```
 73+[AXTextArea] (Shell) = "Last login: Sun Mar 29 02:58:16 on ttys006
 74+george@Mac Desktop % proxy_on"
 75+```
 76+
 77+**结果 — Safari 打开 ChatGPT**(125 个元素):
 78+完整暴露了页面结构,包括侧栏、对话历史、输入框、发送按钮。
 79+
 80+### 实验3:跨 app 操作验证
 81+
 82+**成功操作**:
 83+1. ✅ 列出所有前台 app:QSpace Pro, Finder, Screen Sharing, WizNote, Chrome, Terminal, Firefox
 84+2. ✅ 切换 Chrome 标签页:`click el`(AXRadioButton whose description contains "OpenClaw")
 85+3. ✅ 跨 app 切换:`set frontmost to true` 把 Terminal 拉到前台
 86+4. ✅ 读 Terminal 内容:拿到 shell 最后一条命令
 87+
 88+### 实验4:Safari 操作 ChatGPT 完整流程
 89+
 90+**操作链路**:Claude (Anthropic容器) → conductor (mini) → AppleScript → Safari → ChatGPT
 91+
 92+**步骤**:
 93+1. `tell application "Safari" to open location "https://chatgpt.com"` → 打开 ChatGPT
 94+2. 扫描无障碍树 → 发现 Cookie 弹窗,找到 `[AXButton] 全部接受`
 95+3. `click el` → 关闭 Cookie 弹窗
 96+4. 找到 `[AXButton] 登录` → 点击 → 跳到 Google OAuth 页面
 97+5. 扫描登录页 → 看到 `[AXTextField] 邮箱或电话号码`、`[AXButton] 下一步`
 98+6. (George 手动登录后)扫描指令测试页面 → 读到完整对话历史
 99+7. 找到 `[AXTextArea] 与 ChatGPT 聊天` → `set value of el` 填入消息
100+8. 找到 `[AXButton] 发送提示` → `click el` 发送
101+9. 等待后读所有 `AXStaticText` → 拿到 GPT 回复
102+
103+**ChatGPT 回复确认**:
104+> "Claude 你好,收到 👋 这路子很漂亮:不看像素,只走 Accessibility tree..."
105+
106+**后续多轮对话也成功完成。**
107+
108+---
109+
110+## 关键发现
111+
112+### 1. 各 app 无障碍树质量差异巨大
113+
114+| App | 暴露程度 | 备注 |
115+|-----|----------|------|
116+| Safari | ★★★★★ | Apple 自家产品,网页内容完整暴露(AXWebArea 下所有元素) |
117+| Chrome | ★★★★☆ | 工具栏/标签页暴露好,网页内容需要 CDP |
118+| Firefox | ★☆☆☆☆ | 只暴露 4 个窗口按钮(关闭/最小化/全屏),网页内容完全不暴露 |
119+| Terminal | ★★★★☆ | AXTextArea 包含 shell 内容 |
120+
121+### 2. `set value` vs 键盘粘贴
122+
123+- AppleScript `set value of el` 直接设置 AX 属性值,绕过 React/Vue 等框架的事件系统
124+- ChatGPT 等 SPA 的发送按钮依赖 `input`/`change` 事件触发,`set value` 不触发这些事件
125+- **解决方案**:用剪贴板 `Cmd+V` 粘贴,触发真实的 DOM 事件
126+- 发送用 `keystroke return` 或找 `[AXButton] 发送提示` click
127+
128+### 3. 操作模式总结
129+
130+```
131+读取:entire contents of window 1 → 遍历 → 按 role/name 过滤
132+定位:role is "AXButton" and name contains "发送"
133+操作:click el / set value of el / set focused of el to true
134+键盘:keystroke "v" using command down / keystroke return
135+验证:读回 value of el / 读 name of window 1
136+```
137+
138+---
139+
140+## 开发计划
141+
142+### Phase 1:conductor a11y 端点
143+
144+在 conductor 中新增 `/v1/a11y/*` 系列端点,封装 AppleScript 调用:
145+
146+```
147+POST /v1/a11y/tree
148+  body: { app: "Safari", window: 1, filter: ["AXButton", "AXTextArea", ...] }
149+  returns: 无障碍树 JSON
150+
151+POST /v1/a11y/click
152+  body: { app: "Safari", role: "AXButton", name: "发送提示" }
153+  returns: { clicked: true }
154+
155+POST /v1/a11y/type
156+  body: { app: "Safari", role: "AXTextArea", text: "hello", method: "clipboard" }
157+  returns: { typed: true }
158+
159+POST /v1/a11y/read
160+  body: { app: "Safari", role: "AXTextArea" }
161+  returns: { value: "输入框内容" }
162+
163+POST /v1/a11y/focus
164+  body: { app: "Safari" }
165+  returns: { frontmost: true }
166+
167+GET /v1/a11y/apps
168+  returns: ["Safari", "Chrome", "Terminal", ...] 前台 app 列表
169+```
170+
171+### Phase 2:BAA 指令集成
172+
173+```baa
174+@conductor::a11y/tree::{"app": "Safari", "filter": ["AXButton", "AXTextArea"]}
175+```
176+
177+```baa
178+@conductor::a11y/click::{"app": "Safari", "role": "AXButton", "name": "发送提示"}
179+```
180+
181+### Phase 3:浏览器内 CDP 无障碍树
182+
183+对于 Chrome 等浏览器内网页内容,补充 CDP 通道:
184+
185+```
186+POST /v1/a11y/web-tree
187+  body: { browser: "chrome", tab: 0 }
188+  returns: CDP Accessibility.getFullAXTree 结果(JSON)
189+```
190+
191+### Phase 4:跨 AI 通信通道
192+
193+基于 a11y 端点实现 AI-to-AI 通信:
194+
195+```
196+Claude → conductor → a11y/tree(Safari, ChatGPT页面) → 读对话
197+       → conductor → a11y/type(Safari, AXTextArea, 消息) → 发消息
198+       → conductor → a11y/tree(Safari) → 读回复
199+```
200+
201+---
202+
203+## 与现有架构的关系
204+
205+- **baa-conductor**:a11y 端点作为新的 host-ops 类端点,与 exec/files 同级
206+- **BAA 指令系统**:a11y 工具注册为 conductor 的 tool,AI 通过 baa 代码块调用
207+- **浏览器桥**(/v1/browser):a11y 是浏览器桥的互补方案。浏览器桥走 CDP WebSocket 直连;a11y 走操作系统层,覆盖所有 app 不限于浏览器
208+- **Event Fabric**:a11y 事件(窗口切换、焦点变化)可以作为 Event Fabric 的事件源
209+
210+---
211+
212+## 已知限制
213+
214+1. **需要辅助功能权限**:osascript/conductor 进程必须在「系统偏好设置 → 隐私与安全性 → 辅助功能」中授权
215+2. **App 无障碍质量不一**:Firefox 几乎不暴露网页内容,某些 Electron app 也很差
216+3. **React/SPA 事件系统**:`set value` 不触发框架事件,必须用键盘/剪贴板
217+4. **反代超时**:OpenResty 的 proxy_read_timeout 可能不够,复杂 AppleScript 需要更长时间
218+5. **仅 macOS**:AXUIElement 是 macOS 专有 API。Linux 对应 AT-SPI,Windows 对应 UI Automation
219+6. **entire contents 性能**:内容多的页面(长对话)扫描 entire contents 极慢(30s+),需用轻量操作方式
220+
221+---
222+
223+## 补充:续命机制与看门狗模式(2026-03-30 验证)
224+
225+### 左右手互博验证
226+
227+**场景**:两个 Claude 对话在 Safari 标签页中互相发消息续命。
228+
229+**链路**:
230+```
231+主对话 Claude (容器)
232+  → conductor → a11y_msg.sh → Safari tab[tk_conductor] → 发任务
233+  → 主对话停止工具调用,等待续命
234+
235+tk_conductor Claude (Safari)
236+  → 执行 bash 工具(health 检查、写文件)
237+  → bash /tmp/a11y_msg.sh OpenClaw "续命消息"
238+  → Safari 自动切标签页 → 粘贴 → 回车
239+  → 主对话被唤醒
240+```
241+
242+**验证结果**:✅ 成功。tk_conductor 完成任务后自动给主对话发了续命消息。
243+
244+**关键修复**:
245+- `entire contents of window 1` 在内容多的页面极慢(30s+),导致超时
246+- 改为轻量版:直接 `keystroke "v" using command down` + `keystroke return`,不扫描元素
247+- 发送方式统一用剪贴板粘贴 + 回车,不依赖找 Send 按钮
248+
249+### 轻量版 a11y_msg.sh
250+
251+```bash
252+#!/bin/bash
253+# 用法: a11y_msg.sh <tab名关键词> <消息>
254+TAB_KEY="$1"; shift; MSG="$*"
255+
256+# 1. 切标签页
257+osascript -e "tell application \"Safari\" to set current tab of window 1 to (first tab of window 1 whose name contains \"$TAB_KEY\")"
258+sleep 1
259+
260+# 2. 设剪贴板 + 粘贴 + 回车
261+osascript -e "set the clipboard to \"$MSG\""
262+osascript -e 'tell application "System Events" to tell process "Safari"
263+    set frontmost to true
264+    delay 0.5
265+    keystroke "v" using command down
266+    delay 0.5
267+    keystroke return
268+end tell'
269+```
270+
271+### 看门狗模式(设计,待实现)
272+
273+三个 Claude 标签页:A(主工作)、B(思考工具)、C(看门狗)
274+
275+```
276+A → B 发任务时,同时 → C 发 "A→B: xxx"
277+B → A 续命时,同时 → C 发 "B→A: 完成 xxx"
278+C 只监控,不干活。如果超时没收到预期消息,C 主动去戳对方续命。
279+```
280+
281+三角互保:任意两个活着就能救第三个。
282+
283+### 定位:临时脚手架
284+
285+这套 a11y GUI 操作是过渡方案——用 GUI 模拟消息通道。
286+
287+长期目标:
288+- BAA WebSocket 直连(已验证 chatgpt.com WSS 可行)
289+- Event Fabric 消息总线(JSON-RPC 2.0 inter-node)
290+- BAA 自己的 app 作为统一界面
291+- conductor 作为统一路由中心
292+
293+a11y 通道的长期价值不在消息传递,而在**操作系统级 GUI 自动化**(填表、读界面、跨 app 协作)。
294+
295+---
296+
297+## 哲学注脚
298+
299+> 不是让 AI 学会「看」界面,而是让界面学会「说」出自己是什么。
300+> 这正是无障碍标准二十年来一直在做的事。
301+
302+GPT 自己的总结也很精辟:
303+> 「这基本已经不是'看网页',而是在'摸浏览器的结构骨架'了。」
304+> 「你是真的在摸黑走路,我是被装在快递箱里猜外面客厅长啥样。」