blob: f422c7af6c60bc40efd71dc633c1fd1226ae320b [file] [view]
---
doc_id: computer-use-foundation-contract
title: "Maka Computer Use Foundation Contract"
language: zh-CN
source_language: zh-CN
implementation_status: current
document_status: current
translation_status: source-only
last_verified: 2026-09-11
owners:
- maka-backend
---
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Maka Computer Use Foundation Contract
状态:Accepted
适用范围:Desktop foundationCLI 仅实验性 opt-inEval subject 通过 Runtime Host 继承同一边界
目的:定义 stacked PR 不可破坏的合同与验证门。
外部证据参考(不属于本仓库):
- `codex-computer-use-lab/docs/08-wrapper-policy-and-toctou.md`canonical app approvalpre-await snapshotapproval action freshness 分离;
- `codex-computer-use-lab/docs/13-policy-error-state-machine.md`policy approval fresh observation action,以及 intervention/lock/blocked URL 状态;
- `codex-computer-use-lab/docs/16-service-process-lifecycle-and-retention.md`exact executable ownershipclient/idle lifecycleconnection-loss cleanup
- `codex-computer-use-lab/docs/19-electron-presentation-and-mcp-event-contract.md`presentation native action transport 分离。
- `codex-computer-use-lab/docs/22-native-ax-diff-refetch-and-instance-isolation.md`stable AX revision idsordered differenceno-change full fallback
上述文件位于独立逆向实验仓库。本文只记录由 Maka 测试锁定的合同,
不把外部路径声明为本仓库内链接。
## Contract
1. Observation authority
- 每个可执行 observation 具有唯一 `frameId + epoch`、截图尺寸、`pid + windowId`capture-local 坐标信息,以及适用时的 Electron page identity
- 同一窗口跨 revision 的模型 element id 可以稳定,但它只是 presentation identity;每次 dispatch 仍必须解析到 fresh snapshot opaque token
- WebContent/renderer 元素同时绑定 host process generation actual input-owner generation;唯一真实 WebContent 元素会遮蔽同框叶子 mirror,歧义 mirror 不删除。
- 坐标只能在产生它的截图/窗口 frame 内解释。dispatch 禁止重新选择当前全局坐标下的最高 z-order 窗口。
- observationturn/session 结束、abortuser stopservice loss 和明确 intervention 使旧 action claim keyboard ownership 失效。
2. Action binding
- mutation 在第一次异步边界前完成参数快照、规范化、fingerprintclaim,并绑定 active observation
- 有顺序依赖的动作按 Computer Use session 串行;不同 session 不全局串行。
- stalereplayunclaimedmalformedtargetless action fail closed,不得回退到裸 pixelforeground activation 或当前系统焦点。
3. Exact target validation
- coordinate action dispatch 前验证同一 window identitygeometryscreenshot scalepage identity occlusion
- semantic action 优先按稳定 token refetch;否则仅允许唯一且 identity-preserving 的匹配。缺失、歧义、越界、遮挡或 page 变化必须失败。
- 无关 AX/DOM 内容变化不能合成 `user_intervened`。物理介入和 terminal host state 必须来自明确事件。
- drag/zoom 两端必须属于同一个 bound window
- WebContent click 只有在 host/window renderer generation 都验证后才能走 `skylight_pid`;失败不得退回 AX mirror JavaScript click
4. Execution ownership
- maka-cu 是唯一 native executorwindow/page discoverysemantic preparationinput dispatch effect readback 均留在该边界内。
- agent 不得移动真实鼠标、抢前台焦点、临时 activate 窗口或执行 windowless desktop input
- keyboard ownership 绑定 `session + turn + generation + pid + windowId + page/frame`,并在失败、stale、新 observationinterventionservice generation 变化、turn/session 结束时撤销。
- child process 在未知 action outcome 下退出时必须 re-observe,禁止自动重放。
5. Postcondition
- mutation 成功后旧 observation 被消费并返回 fresh authoritative observation;完整当前元素树保留在 observation 内,可获得视觉状态时向模型返回新截图。
- immediate post-action model text 可以使用 executor 声明的 no-change/difference/full presentation;显式 observe 仍显示完整树,差分不得成为重建当前状态的唯一来源。
- transport success 不等于 business success`verified:true` 必须由 action-specific effect/readback 支撑。
- `supported:true, ok:false` 为本次 terminal failure;仅 side-effect-free `supported:false` 可进行一次显式允许的 fallback
- retry 基于 fresh observation 和新 claim,禁止重试旧 coordinate/fingerprint
6. Service lifecycle
- executableversionhashrole generation 必须 runtime-observabledead/mismatched child 不得复用。
- startuprequestshutdownrestart 均有界;成功恢复后重置连续失败预算。
- process exit 清理 pending requestobservationkeyboard ownershippresentation 和受影响 session lease
- capability 反映实时 `healthy / degraded / unavailable`,不能只检查 binary path
7. Approval and privacy
- approval app capability gate,不是 active observation action freshness 证明。
- Maka 采用分级短 lease`metadata_read``screenshot_read``keyboard_mutation``semantic_mutation` 四类(见 `packages/core/src/computer-use.ts`),分离;目标、action classobservation session generation 变化时重新授权。
- approval 至少标明 action class 与目标 app/window;敏感应用、secure/password field 和不支持的目的地 fail closed
- screenshottyped textcoordinateraw AX label/valuewindow titlesecret raw page content 默认不进入持久 session logtelemetry evaluation report
- 上传截图前验证 model vision capability,并满足对应用户/provider consent policy
8. Presentation isolation
- cursor/PiP 位于 targeting 下游,不能选择、转换、授权或改变执行坐标。
- `readyForInteraction` 只能通过有界 fail-open 策略影响 dispatch 时机;`finished` 不阻塞 native dispatch postcondition
- completion 使用 executor-resolved point;失败、abortteardownsupersede 或缺少 completion point 时必须 cancel
- acknowledgement session + action identity 绑定,stale ack 必须忽略。
## Renderer semantic source contract
Maka 自己的 Electron renderer 也是 Computer Use 的目标。它不能依赖截图或运行时注入
来弥补页面语义;角色、名称、状态和 landmark 必须由页面本身提供。
- 一个可见窗口只暴露一个 `main` landmark`AppShell` 已拥有主 landmark,聊天页、
SkillsMCP、定时任务和每日回顾必须作为有名称的 `region` 出现在其内部,不能再嵌套
第二个 `main`
- 每个可操作 AX node 必须有非空 accessible name。选中导航使用 `aria-current`,标签页
使用 `aria-selected`,展开态、禁用态、忙碌态和表单状态由原生元素或对应 ARIA 状态
暴露。
- 同一层级的 region 不使用相同名称表示不同语义。例如新任务页面 region 与内部空态
hero 分别命名为“新任务对话”和“开始对话”。
- Astryx 自带文案进入中文 accessibility tree 前必须经过
`packages/ui/src/astryx-i18n.tsx`。新增 Astryx surface 时,不能让英文 fallback
静默进入中文 Computer Use observation
- 不添加生产环境 DOM walkerMutationObserver、定时轮询或额外 a11y 依赖来修补以上
问题。语义随现有 JSX 渲染,运行时开销只限原生 DOM/ARIA 属性。
验证分四层:
1. TypeScript Storybook 构建保证语义属性、文案契约和非默认状态 fixture 能随产品 API
一起演进。不要重新引入基于正则的 JSX 源码扫描器;它无法可靠理解组件语义,主干已用
真实 AX 验证取代这类检查。
2. `scripts/ax-tree-audit.mjs` Storybook smoke 的测试侧 AX 规则源(当前唯一消费者,不对 Electron 运行),
拒绝无名或同一语义作用域内歧义的可操作 node、多 `main`、无名 dialog,以及缺少
checked/selected/expanded/value 的状态控件。
3. Electron 侧的广域设置导航 AX 清单(原
`apps/desktop/e2e/accessibility-coverage.spec.ts`)已随 #4803 **退役而非迁
移**:#4803 明确不声称存在等价覆盖,且该缺口是有意接受的。目前没有任何
层对运行中的 Electron 应用做路由清单枚举;Electron E2E 仅保留修订、
WorkHub 与草稿焦点等边界内 journey
4. `scripts/storybook-visual-smoke.mjs` Storybook 全目录读取 AX tree,执行 `play`
函数到最终态,并验证 modal 焦点、隐藏/惰性 surface 和关键 Computer Use story
inventory;名字含 `narrow` 的故事必须在窄视口运行。独立 WebContentsView
`apps/desktop/scripts/browser-observe-act-smoke.mjs` 走真实 observe semantic ref
act effect 闭环,不能伪装成 renderer tree 的一部分。
完整页面、状态、动作与性能边界清单见 `docs/computer-use-ui-coverage.md`
## Validation Matrix
`PASS`:当前证据直接覆盖;`PARTIAL`:组件证据存在但 production 闭环不足;`FAIL`:当前实现违反合同;`UNKNOWN`:缺少足够证据。
本矩阵记录 #857 拆分链建立时的基线状态,用于界定各 stacked PR 的验证责任。拆分链合入后,各领域当前状态以源码与合同测试为准。
| Contract area | 状态 | 当前证据 | 拆分链需要的证据 |
|---|---|---|---|
| Frame/window bindingduplicate rejection | PASS | frame statebound-actionstale/duplicate tests | Runtime slice 保留 focused tests |
| Capture-local coordinate authority | PASS | window-local transformscale/geometryRetina/negative-origin tests | decoy window 下的 cumulative Desktop E2E |
| WebContent / renderer target | PASS | actual PID + start timecoalition readinessmirror 去重;精确 pin 5 OOP 全部 `skylight_pid``isTrusted=true`、单 down-up30 sentinel span 零前台样本 | 扩到真实 Electron/Chromium app matrix |
| Semantic identity refetch | PASS | renderer frame-only reflow 仅允许同进程世代 unique replacementmissing/ambiguous fail closednative frame change 继续拒绝;真机全部零误点 | 保留跨 toolkit 录制回归 |
| Stable AX revision / post-action diff | PASS | DFS stable ID、跨 fresh token 继承、ordered changesremoved rangesno-change/full fallbackhost 显式 observe 保持 full | 增加真实长树 token-saving trajectory 样本 |
| Modal / multi-window routing | PARTIAL | appsheetexact secondarybutton/scroll/close 功能矩阵 5/5;精确 pin 的高频 sentinel 捕获 1,738 target-frontmost 样本,后台安全未通过 | 修复原生 AX press 的瞬时前台抢占,再重跑同一聚合矩阵 |
| Occlusionno foreground/pixel fallback | PASS | coordinate/semantic occlusion fail-closed tests | real-window safety sentinel |
| Fresh postconditioneffect verification | PARTIAL | mutation fresh observation5 primary oracle=1slider 业务值/readback=42scroll tree delta + oracle=76 | 继续补 secondary action 与跨窗口业务 oracle |
| Per-session queuegeneration lease | PARTIAL | session queue/frame claimlease 修复尚在本地 | concurrent-session intervention-before-dispatch tests |
| Physical interventionlockstop | FAIL | 有状态机原型,无 Desktop production event producer | 真实 host wiring transition tests |
| Service recoveryunknown outcome | PARTIAL | 本地 service abstraction unit tests | restart resetattestationchild-crashcleanup E2E |
| Approval semantics | FAIL | 旧实现是整 turn scope | 分级 lease、脱敏 permission eventsensitive-target tests |
| Privacytelemetry | FAIL | observation/tool args 可含敏感内容 | persistence/redaction testsallowlist report schema |
| Presentation lifecycle | PARTIAL | 本地 candidate 存在;远端 #777 与 #699 相同 | 重建 presentation-only PR 与 cumulative E2E |
| Provider/model compatibility | PARTIAL | Desktop 默认走统一 function harness | vision gate;每个准入 model real-runtime evidence |
| Binary provenance | PASS | source/archive/binary/license pinning | 独立 supply-chain verifier |
| Signed packaged app | UNKNOWN | `.app` signing/notarization/Gatekeeper 证据 | nested helperTCC chaincold-start package smoke |
## Split Gate
每个 stacked PR 必须写清:负责的 contract 条款、non-goalsexported interfacefocused verifier cumulative verifier。重建从最终已验证 tree 按目标文件/hunk 提取,不机械重放旧 73-commit 历史。
## 两个执行器留下的教训
Maka 在两个 native executor 上做过真机实测:cua-drivertrycuaRustMCP)和一个
自有的 Swift executor(协议 `maka.cu/2`)。下面每一条都由真机实测得出,写在这里是
因为它们是设计层面的,换执行器不会自动消失。
### 协议要封闭,而不是宽容
cua-driver MCP 面是开放字符串:dispatch tier 要从 path 字符串猜,猜错的每一次
都落到 `coordinate-background`;错误消息可能带应用文本,于是宿主必须整体脱敏,
结果是**模型永远只看到错误码**,看不到那句可操作的话。`maka.cu/2` 把这些收成闭
集(§1.1 的双错误层、§1.2 的固定句子、§6.3 tier/path 配对),宿主才敢把执行器
的句子直接给模型看。
教训:能让模型自救的信息,往往正是"看起来可能不安全所以被丢掉"的那部分。解法是
让它在协议层就不可能不安全,而不是在宿主层一刀切。
### 两端各写一份的东西,一定会分叉
坐标动作 100% 不可用,藏了整个开发期。根因:快照侧 `hostWalkTree` 与校验侧
`HostAXBindingProbe` 各自实现了同一份"摘要输入"字段表,根节点的 `ancestors` 一个
读活链、一个硬编码空数组。65 个元素差 1 个,窗口摘要就不符,而窗口摘要是坐标动作
**唯一**的锚。元素动作因为只校验自身,24/24 一直是绿的,完全遮住了它。
修法不是让两份拷贝再对齐一次(那已经试过一次并且正是这次分叉的来源),而是收敛成
一条代码路径、规则放在里面。
教训:凡是"记录时算一遍、校验时再算一遍"的结构,必须共用一个函数。绿灯不覆盖的
那条路,就是它会坏掉的地方。
### 缓存不是查询
`NSWorkspace.shared.runningApplications` `frontmostApplication` 在没有 AppKit
run loop 的进程里**永不刷新**。执行器因此看不见任何在它之后启动的应用,而
`foregroundTaken` 恒为启动时刻的那个值——一个抢了用户前台的启动会如实报告"没抢"
所有真机测试之所以一直是绿的,只是因为目标应用碰巧早就在跑。
教训:在无 run loop 的进程里,AppKit 的任何"当前状态"访问器都要按缓存对待,改用
`proc_listpids` / 窗口服务这类每次真查的接口。
### WebContent 不是把事件 PID 改掉
WKWebView AX 树和 XPC process 不是同时出现的。只按进程名找 WebContent 会撞到别的
应用,只把 `CGEvent.postToPid` 改投 renderer 又完全没有事件。当前闭环分三层:
- XNU resource + jetsam coalition 只负责证明 host WebContent 的唯一归属,并触发
一次 250ms bounded re-observe
- observation 只删除同框、同语义、唯一且为叶子的 host mirror
- event 仍绑定 host `CGWindowID`,通过单通道 private SkyLight 交给 WindowServer
renderer hopactual PID/start time 是元素身份和 restart fence
教训:OOP targeting process identitywindow identity event routing 的联合
问题,不是一个 PID 字段。
### 上限要有时钟,截断要说出来
`maxElements` 挡不住慢:由另一个进程托管的 open/save 面板走 1500 个元素花了 35 秒,
撞穿宿主 20 秒死线被杀,而宿主报的是"执行器已退出"——把排查引向了错的一侧。而且
截断只进了 trace,模型读到一棵残树会得出"这个控件不存在"
教训:任何遍历都要同时有数量上限和时间上限;任何截断都必须出现在**模型读得到的
地方**,并且要说出它的含义("可能存在但没列出"),而不只是一个 `truncated=true`
### 不变量要请求,而不是假设
`apps.launch` 的类型注释写着"启动的应用不得抢焦点",而实现用的是
`NSWorkspace.OpenConfiguration()` 默认值——`activates` 默认为 `true`,从来没有请求
过后台启动。诚实上报那一半是对的(应用自激活时如实报 `foregroundTaken: true`),
缺的是先去请求。
教训:一条不变量如果只写在注释里、没有对应的一行代码去请求它,它就不是不变量。
### 剪枝要有回退路径才付得起
Codex 剪得很狠(13 层深的通用容器全收),因为它有 `click{x,y}` 兜底:藏错了元素,
模型还能按坐标点。Maka 的坐标路径默认关闭,藏掉的元素就是**够不到**的元素。跨 10
个应用 9129 个元素实测,朴素的"无 label 就剪"会藏掉 3428 个,其中 1023
(占全树 17%)是可操作的。
教训:能不能剪,取决于剪错了有没有第二条路。没有回退的实现必须比有回退的保守。
### 省 token 的地方常常不在编码上
JSON/YAML 不比"一元素一行 + 缩进"省:实测分别是它的 3.5 倍和 2.1 倍,因为后者把
包含关系编码成缩进、把默认状态编码成"不写"。真正的浪费在别处——`list_apps` 无条件
返回 133 个应用(12,933 字节,约 3,600 token,占一个三步回合的 85%),而其中 118
个根本没有窗口、模型碰都碰不到。
教训:先量一次真实回合的 token 分布再动手。最大的一笔开销往往不在你正在优化的那
个字段上。
### 措辞补不上不存在的能力
一条真实任务上的三轮迭代,每轮都把拒绝语句写得更准,模型的调用次数是 32 46 57
任务是「把窗口挪到左边」。移动窗口只能拖标题栏,拖标题栏只能用坐标动作,而坐标动作
要求目标像素属于目标窗口——Computer Use 驱动的是用户没在看的窗口,后台启动的窗口
必然压在 z-order 底部,于是必然被遮挡。**这个任务当时没有解**:记录时协议里没有窗口管理动词(`window_action` move/resize/minimize #1952 之后才加入严格动作集的),
而「移动窗口」也不是任何控件的 AX 动作。
把拒绝语句写清楚之后,模型确实读懂了「这条路不通」,于是去试别的路——而别的路也不
通,所以试得更多。同一批改动对「导出 PDF」是有效的:那里存在一个正确答案(「做不
到,因为菜单快捷键到不了后台应用」),模型说出这句话就停了。
分界线:
- 存在正确答案(包括「做不到」本身就是正确答案)→ 措辞能把模型引到那里,值得改。
- 不存在正确答案 措辞只会让模型更快地把所有错路试一遍。要补的是能力,不是句子。
判断方法:先问「一个熟练的人拿着同样这套动作面,能不能做成」。答不上来就先别改文案。