jev-codex-cua 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +24 -0
  2. package/NOTICE.md +18 -0
  3. package/README.md +254 -0
  4. package/THIRD_PARTY_LICENSES.md +25 -0
  5. package/dist/add-app.d.ts +1 -0
  6. package/dist/add-app.js +19 -0
  7. package/dist/add-app.js.map +1 -0
  8. package/dist/app-grants.d.ts +10 -0
  9. package/dist/app-grants.js +104 -0
  10. package/dist/app-grants.js.map +1 -0
  11. package/dist/ax.d.ts +9 -0
  12. package/dist/ax.js +77 -0
  13. package/dist/ax.js.map +1 -0
  14. package/dist/cua-driver.d.ts +17 -0
  15. package/dist/cua-driver.js +66 -0
  16. package/dist/cua-driver.js.map +1 -0
  17. package/dist/eval.d.ts +1 -0
  18. package/dist/eval.js +28 -0
  19. package/dist/eval.js.map +1 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.js +7 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/install-skill.d.ts +1 -0
  24. package/dist/install-skill.js +44 -0
  25. package/dist/install-skill.js.map +1 -0
  26. package/dist/jev.d.ts +73 -0
  27. package/dist/jev.js +123 -0
  28. package/dist/jev.js.map +1 -0
  29. package/dist/loop.d.ts +28 -0
  30. package/dist/loop.js +244 -0
  31. package/dist/loop.js.map +1 -0
  32. package/dist/native-state.d.ts +17 -0
  33. package/dist/native-state.js +39 -0
  34. package/dist/native-state.js.map +1 -0
  35. package/dist/native-tools.d.ts +17 -0
  36. package/dist/native-tools.js +37 -0
  37. package/dist/native-tools.js.map +1 -0
  38. package/dist/pi-config.d.ts +8 -0
  39. package/dist/pi-config.js +31 -0
  40. package/dist/pi-config.js.map +1 -0
  41. package/dist/pi-extension.d.ts +13 -0
  42. package/dist/pi-extension.js +295 -0
  43. package/dist/pi-extension.js.map +1 -0
  44. package/dist/policy.d.ts +13 -0
  45. package/dist/policy.js +99 -0
  46. package/dist/policy.js.map +1 -0
  47. package/dist/sky/client.d.ts +68 -0
  48. package/dist/sky/client.js +274 -0
  49. package/dist/sky/client.js.map +1 -0
  50. package/dist/sky/runtime.d.ts +7 -0
  51. package/dist/sky/runtime.js +32 -0
  52. package/dist/sky/runtime.js.map +1 -0
  53. package/dist/sky-driver.d.ts +7 -0
  54. package/dist/sky-driver.js +42 -0
  55. package/dist/sky-driver.js.map +1 -0
  56. package/dist/trace.d.ts +33 -0
  57. package/dist/trace.js +77 -0
  58. package/dist/trace.js.map +1 -0
  59. package/dist/types.d.ts +112 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/docs/action-trace.md +66 -0
  63. package/docs/design.md +55 -0
  64. package/docs/local-acceptance.md +43 -0
  65. package/docs/npm-release.md +58 -0
  66. package/docs/requirements.md +40 -0
  67. package/docs/sky-diagnostics.md +29 -0
  68. package/fixtures/cases.json +26 -0
  69. package/package.json +39 -0
  70. package/skill/deskhand/SKILL.md +42 -0
  71. package/skills/jev-codex-cua/SKILL.md +47 -0
  72. package/skills/jev-cua-add-app/SKILL.md +43 -0
  73. package/src/add-app.ts +15 -0
  74. package/src/app-grants.ts +71 -0
  75. package/src/ax.ts +71 -0
  76. package/src/cua-driver.ts +62 -0
  77. package/src/eval.ts +23 -0
  78. package/src/index.ts +6 -0
  79. package/src/install-skill.ts +37 -0
  80. package/src/jev.ts +123 -0
  81. package/src/loop.ts +232 -0
  82. package/src/native-state.ts +40 -0
  83. package/src/native-tools.ts +41 -0
  84. package/src/pi-config.ts +30 -0
  85. package/src/pi-extension.ts +236 -0
  86. package/src/policy.ts +77 -0
  87. package/src/sky/LICENSE.pi-codex-cua +21 -0
  88. package/src/sky/client.ts +248 -0
  89. package/src/sky/runtime.ts +30 -0
  90. package/src/sky-driver.ts +41 -0
  91. package/src/trace.ts +89 -0
  92. package/src/types.ts +82 -0
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LonelyFellas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ Third-party portions retain their respective licenses; see NOTICE.md and
24
+ THIRD_PARTY_LICENSES.md.
package/NOTICE.md ADDED
@@ -0,0 +1,18 @@
1
+ # 来源说明
2
+
3
+ ## pi-codex-cua 桥接
4
+
5
+ `src/sky/runtime.ts` 与 `src/sky/client.ts` 改编自 https://github.com/manaflow-ai/pi-codex-cua ,参考本地提交 `6850a286c399b9b3d483278fe9f602644b101022`,版权归 Manaflow AI(2026)。完整 MIT 授权保存在 `src/sky/LICENSE.pi-codex-cua`,随源码分发。
6
+
7
+ 保留官方签名客户端启动链与 turn metadata,增加超时、取消、协议边界及隐私处理。不包含或再分发 Codex/Sky 私有二进制,仍从用户安装的官方运行时加载。
8
+
9
+ ## Jev-cu 编排
10
+
11
+ 本项目的 AX 解析/候选筛选、Jev 四问、策略门槛、Codex driver 和任务循环参考并改编自 Sac-Y/Jev-cu:
12
+
13
+ - https://github.com/Sac-Y/Jev-cu
14
+ - 参考提交:e2cc92d731fac6e6aeb7acbd3c105cf23552cdec
15
+ - 原 package.json 声明:`"license": "ISC"`;`author` 字段为空。
16
+ - 此提交未包含单独 LICENSE 文件或版权声明;不补造原作者身份或原版权声明。依据其 package.json 的 ISC 声明,在 THIRD_PARTY_LICENSES.md 保留来源及标准 ISC 条款。
17
+
18
+ 本项目新增代码经维护者确认采用 MIT,见根目录 LICENSE;第三方部分继续保留各自的 ISC/MIT 条款,不因 TypeScript 迁移而抹去。当前发行仍为实验原型,未声称与上游完全等价或性能优于上游。
package/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # jev-codex-cua
2
+
3
+ > **v0.1.0 实验原型**:用于保存当前实现和联调证据,不代表达到官方 Codex Computer Use 的可靠性。已验证部分计算器任务;多步任务、浏览器控件和菜单状态仍有兼容性缺口。不要将单元测试通过视为任意应用可用或生产验收通过。
4
+
5
+ **一个 pi 包,两种模式,共用同一套 Codex/Sky 桥接。** 默认 `native`,不需要 TypeSafe Key;显式选择 `jev` 后才使用参考 [Sac-Y/Jev-cu](https://github.com/Sac-Y/Jev-cu) 的文字决策循环。
6
+
7
+ ```text
8
+ native(默认):pi 主 Agent → cua_* 原生工具 → Codex/Sky
9
+ jev(可选): pi 主 Agent → Jev 决策/策略 → 同一个 Codex/Sky
10
+ └ 不确定时交回主 Agent,不自动重放
11
+ ```
12
+
13
+ 不依赖自研原生引擎。优先支持 **pi 插件**,通过复用 pi-codex-cua 的 MIT 桥接代码连接已安装的 Codex/Sky;不需要切换到 Codex 会话或使用 `cua_repl`。原 Codex driver 仍可用于独立脚本。来源及授权情况见 [NOTICE.md](NOTICE.md)。
14
+
15
+ ## 开发
16
+
17
+ Node >=22.19,TypeScript 严格模式。pi 插件已在 pi 0.86.1 验证加载;pi 核心包和 typebox 为宿主 peer dependencies。核心编排库只使用 Node 内置模块。
18
+
19
+ ```bash
20
+ npm ci
21
+ npm run check
22
+ npm run build
23
+ npm test
24
+ # 可选,需用户明确允许真实计算器操作;官方批准不会被模拟:
25
+ # npm run accept:handoff -- --live
26
+ ```
27
+
28
+ 测试使用模拟 driver 和 HTTP 响应,不访问外网、不控制真实应用。原生引擎实验留在独立 `native-mvp` worktree,不属于本版。
29
+
30
+ ## npm 安装(发布后)
31
+
32
+ 目标分发形式遵循 [pi 官方包规范](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md):
33
+
34
+ ```bash
35
+ pi install npm:jev-codex-cua
36
+ # 固定版本:pi install npm:jev-codex-cua@0.1.0
37
+ ```
38
+
39
+ 以 npm registry 实际可查询的版本为准。包包含源码与预构建产物,具备 prepack 构建及隔离安装检查;本项目采用 MIT,引用部分保留 ISC/MIT 声明,见 [LICENSE](LICENSE) 与 [第三方授权](THIRD_PARTY_LICENSES.md)。发布门禁、版本固定和配置迁移见 [npm 分发](docs/npm-release.md)。npm 安装后无需本地编译;密钥和授权配置应放在包外,通过 `JEV_CUA_ENV_FILE` 指定。
40
+
41
+ ## 在 pi 中本地安装使用
42
+
43
+ ```bash
44
+ cd /absolute/path/to/jev-orchestration
45
+ npm ci --ignore-scripts
46
+ npm run check
47
+ pi install /absolute/path/to/jev-orchestration
48
+ ```
49
+
50
+ 这是本地路径安装,不复制代码。然后在 pi 执行 `/reload` 或打开新会话。不要运行下面的 Codex skill 安装命令来代替 pi 安装。
51
+
52
+ - `/cua-mode`:查询模式;`/cua-mode native` 或 `/cua-mode jev` 显式切换,不执行桌面动作。
53
+ - `cua_status`:查看模式、配置及原生接管剩余预算。旧 `jev_cua_status` 和 `/jev-cua-status` 保留兼容。
54
+ - `cua_get_app_state`:读取原生窗口/菜单状态和可用截图;其他 `cua_*` 工具由主 Agent 直接操作,不调用 Jev。
55
+ - `jev_cua_observe`:兼容的纯文本观察入口,不调用 Jev;原生动作前仍需 `cua_get_app_state` 的 stateId。
56
+ - `jev_cua_run`:**只在 jev 模式启用**,默认执行已授权短任务,`dryRun:true` 为预览。会把文字候选及上下文发送到 TypeSafe,可能计费。原生模式下即使直接调用这个工具也会拒绝,不能隐式启动 Jev。
57
+ - `/skill:jev-codex-cua`:加载 pi 使用流程。
58
+
59
+ 可在 pi 中说:
60
+
61
+ > 使用 jev-codex-cua,在 Calculator 计算 6 + 7,并读取实际结果确认。
62
+
63
+ 明确的任务请求授权其相关低风险步骤,不等于授权其他应用或删除、发送、购买等额外操作。需要预览时再明确要求 dry-run,不必每次先预览。
64
+
65
+ Sky 仍可能发出 **“官方 Computer Use 授权”** 弹窗,由用户决定;插件不伪造批准或绕过它。等待官方确认时暂停网络期限,Esc 仍可取消。无 UI 时若官方请求授权就拒绝,而不是自动接受;若官方无需新授权,普通 observe/run 无需插件弹窗。完整轨迹仍须交互确认。
66
+
67
+ ### 模式与原生操作
68
+
69
+ ```text
70
+ /cua-mode native
71
+ /cua-mode jev
72
+ ```
73
+
74
+ 默认 native;可用 `JEV_CUA_MODE=native|jev` 设置会话初始值。用户通过命令做的选择保存在 pi 当前会话分支,reload 恢复。缺少/丢失 Jev Key 时转 native,不发送请求;之后补 Key 不会偷偷重新启用 Jev,必须再次显式选择。运行中拒绝切模式,先等待或正常取消。不提供模糊任务分类或 auto 模式。
75
+
76
+ 原生工具:`cua_list_apps`、`cua_get_app_state`、`cua_click`、`cua_drag`、`cua_perform_secondary_action`、`cua_press_key`、`cua_scroll`、`cua_select_text`、`cua_set_value`、`cua_type_text`。前缀避免覆盖 pi-codex-cua 的同名无前缀工具。
77
+
78
+ `cua_get_app_state` 返回 stateId。动作必须带同一应用的 stateId;它在当前 agent turn 内最多有效 60 秒,一次动作尝试后即消费。模式切换、另一次观测、异常或 agent 结束会清理旧状态。优先使用该快照里的元素编号;坐标动作必须有本次返回的原生截图。原生观察接受菜单/弹窗/部分状态,不强制 standard window 根;不能将局部状态误称为完整页面。stateId 并不保证界面原子性,操作后仍要重新读取验证。
79
+
80
+ Jev 对目标不确定时返回 needs_planner,交回主 Agent。仅此结果在当前 turn 内开放**原应用、剩余预算范围内**的原生动作;主 Agent 先读取新状态,再作独立判断,不自动重放 Jev 动作。原生接管消耗剩余预算,错误、agent 结束或新 Jev 任务会清理它。`confirm`/取消/未知结果不启用这个接管通道。
81
+
82
+ Native 的判断与后果性操作授权由主 Agent 负责,不使用 Jev 的置信度/关键词分类;两者都经过应用白名单、官方授权和状态检查。用户显式切模式不代表可以绕过拒绝或扩大任务授权。
83
+
84
+ ### pi 配置
85
+
86
+ 扩展自动读取**包目录**的 `.env.local`,不是启动 cwd 的任意环境文件。密钥只在内部使用,不写进 process.env 或 Sky 子进程,不在工具参数和结果里传递。
87
+
88
+ ```dotenv
89
+ JEV_CUA_MODE=native
90
+ JEV_CUA_ALLOWED_APPS=Calculator
91
+ # 只有 jev 模式需要:
92
+ # TYPESAFE_API_KEY=your-key
93
+ ```
94
+
95
+ 文件必须属于当前用户,权限为 `600`,已被 Git 忽略。也可使用进程环境的 `TYPESAFE_API_KEY`;使用 `JEV_CUA_ENV_FILE` 指向其他私有文件。基础白名单遵循环境变量优先于文件,默认允许 Calculator。新增应用也可通过下面的专用 skill 单条追加,不必编辑含密钥的文件。普通桌面任务不得自行扩展名单。
96
+
97
+ ### 只添加应用的 Skill
98
+
99
+ 首次更新后执行 `/reload`,然后可明确授权单个应用:
100
+
101
+ ```text
102
+ /skill:jev-cua-add-app Wechat Devtools
103
+ ```
104
+
105
+ 它只追加指定应用,不删除/重置名单,不读取或修改 API 密钥,不联网、不启动应用,不修改系统/Codex 原生权限。普通的“帮我操作某应用”或白名单错误,不自动触发新增授权;用户必须明确要求添加。
106
+
107
+ 专用脚本也可手动使用:
108
+
109
+ ```bash
110
+ npm run allow-app -- 'Wechat Devtools'
111
+ ```
112
+
113
+ 新授权写到独立的 `.env.local.apps.json`;配置了 `JEV_CUA_ENV_FILE` 时,路径为该环境文件名加 `.apps.json`。有效名单是原基础名单与该授权文件的并集;即使环境变量覆盖基础名单,明确的附加授权仍生效。两份文件不会互相覆盖。授权文件权限为 600,重复添加不改文件;拒绝批量、通配符、删除/替换模式、符号链接和异常配置。更新使用独占锁及原子替换;遇到锁冲突停止,不擅自删除锁。
114
+
115
+ 运行时每次调用重读名单,完成首次代码 `/reload` 后,追加应用无需再次重载。本 skill 不提供撤销权限功能,撤销须另行明确处理。默认授权文件同样被 Git 忽略且不打包发布。
116
+
117
+ `verify` 在 pi 工具中为 `{role, labelEquals}`:对重新读取的 AX 元素完整标签做精确匹配,不能执行 JS。例如先从 observe 确认计算器结果的实际文本格式,再设置对应标签;不要猜 `Value:` / ID / 本地化格式。
118
+
119
+ **数据去向**:native 将 AX 和原生截图提供给当前 pi 主模型,不调用 Jev,但不等于纯本地或没有主模型费用。native 文本最多 50 KB/2000 行;兼容文本观察及 Jev 结果最多 20 KB/400 行。Jev 不接收截图。
120
+
121
+ 插件默认不额外写完整轨迹;`jev_cua_run(fullTrace:true)` 经知情确认后只记录这一次 Jev 循环,原生接管不会悄悄续写该文件。native 工具使用 pi 正常的会话记录(可能含截图),应按 pi 设置管理;不能把“没有 fullTrace 文件”理解为会话不留存。日志文件为 600、目录为 700,可能敏感,不自动上传。详见 [完整动作轨迹](docs/action-trace.md)。
122
+
123
+ 原生和 Jev 共用连接、串行锁和官方授权。切勿同时使用其他 Computer Use 通道操作同一桌面。可以与 pi-codex-cua 共存,但同一包的新旧安装来源要去重,避免 `jev_cua_*` 兼容名称重复注册。
124
+
125
+ Sky 仍要求官方运行时及其权限,支持原来的 `PI_CODEX_CUA_CLIENT` / `PI_CODEX_CUA_CODEX` / `PI_CODEX_CUA_SERVICE` / `PI_CODEX_CUA_SOCKET_DIRECTORY` / `PI_CODEX_CUA_RESOURCES` 路径覆盖。不会绕过系统提示、应用授权、屏幕锁定或站点限制。取消或超时会关闭连接,不自动重试写操作;已派发动作可能已生效。首次联调的授权回调问题及修复证据见 [Sky 排查记录](docs/sky-diagnostics.md)。
126
+
127
+ ## 在 Codex cua_repl 中使用(可选旧入口)
128
+
129
+ 1. 配置 Codex Computer Use 及其 macOS 权限。
130
+ 2. 在运行时环境设置 `TYPESAFE_API_KEY`。本项目不自动读取 `.env.local`,不把 key 写进源码;也可通过 `jevOptions.apiKey` 从安全的外部来源传入。
131
+ 3. `npm run build`。
132
+ 4. 首次在 `cua_repl` **单独**执行 `await cua.getApp("Calendar")` 并阅读当前接口文档。此 adapter 按参考项目的 `getAXState/click/setValue/typeText/pressKey/scroll` 契约编写;如果本地接口不同,停止并适配,不能猜。
133
+ 5. 在后续调用导入编译产物,默认先预览:
134
+
135
+ ```js
136
+ var deskhand = await import((await import("node:url")).pathToFileURL(
137
+ "/absolute/path/to/deskhand/dist/index.js"
138
+ ).href);
139
+ var driver = deskhand.createCuaDriver(cua);
140
+ var result = await deskhand.runTask({
141
+ driver,
142
+ appName: "Calendar",
143
+ goal: "Switch to the previous month",
144
+ allowedApps: ["Calendar"],
145
+ dryRun: true,
146
+ maxSteps: 2,
147
+ emit: line => nodeRepl.write(line + "\n"),
148
+ });
149
+ nodeRepl.write(result);
150
+ ```
151
+
152
+ **dry-run 不执行桌面动作,但会把候选文字和上下文发送到 TypeSafe,并可能产生 API 费用。** 不发送截图不代表没有隐私风险。日志默认不写盘。
153
+
154
+ 真实执行需要用户授权,把 `dryRun` 改为 `false`,并尽量提供从当前 AX 确定的 `verify(ax)`。例如翻月任务应匹配**目标月份的显示值**,不能只检查“下一月”按钮存在。不能复用示例假定的月份或元素编号。
155
+
156
+ TypeSafe 合成样例 3/3 通过。实际计算器已完成并读取确认 12+3232=3244、323423×323244=104544544212;12+30232 的早期尝试曾失败。Music 已能提交搜索词,但上次最终读取被用户取消,尚未确认匹配结果。新单包双模式另已实测 native 的 6+7、真实 Jev 的 12+30232,以及明确含模拟的受控实机接管链路。测试边界和仍未覆盖项见 [本地验收](docs/local-acceptance.md),不能据此宣称超过官方 Codex。Codex cua_repl 旧入口未联调,不把模拟测试当作桌面验收。
157
+
158
+ ## 核心 `runTask()` 参数(pi 工具提供受限子集)
159
+
160
+ | 参数 | 说明 |
161
+ |---|---|
162
+ | `driver` | `createCuaDriver(cua)` 或自定义 Driver |
163
+ | `appName`, `goal` | 应用名和单阶段目标;建议英文目标 |
164
+ | `allowedApps` | 推荐每次显式只允许目标应用;默认沿用参考项目的 7 个应用名 |
165
+ | `dryRun` | 核心 `runTask()` 默认 `true`;pi 工具默认 `false`,只有明确预览才传 true |
166
+ | `maxSteps` | 默认 30,上限 100 |
167
+ | `candidateMax` | 默认 40,上限 200 |
168
+ | `resources` | `{text, key, direction}`,或无副作用的 `(step, decision) => resources`,每步最多调用一次 |
169
+ | `plan`, `constraints` | 主 agent 提供的计划及约束,不作为持久化任务进度 |
170
+ | `verify` | 只读验证函数;传入全量 AX,返回 boolean |
171
+ | `decide` | 注入替代决策函数,用于离线测试 |
172
+ | `jevOptions` | apiKey、model、HTTPS endpoint、单次超时(默认 20 秒)、HTTP 重试上限(默认 2) |
173
+ | `traceDir` | 显式配置才写本地 JSONL,返回 `tracePath`;pi 的 `fullTrace` 使用包目录 `runs/`,不接受模型指定任意目录 |
174
+ | `traceMode` | 默认 `metadata` 不保存 AX/目标原文/文本资源;`full` 保存完整文本轨迹,必须有 `traceDir` 和用户知情同意。两者均排除 API 密钥 |
175
+ | `verifySpec` | 可选的声明式验证说明,用于轨迹记录;不替代 `verify` 的实际验证函数 |
176
+ | `emit` | 接收不含界面正文的进度字符串;默认不输出 |
177
+ | `plannerHandoff` | pi 默认启用;将目标不确定/输入目标或焦点不匹配交给主 Agent,包含剩余预算。核心库默认仍返回原有 stop/escalate |
178
+
179
+ 默认应用名单:Calendar、Calculator、TextEdit、NetEaseMusic、Figma、Google Chrome、Codex In-app Browser。白名单不是对这些应用的所有操作授权。
180
+
181
+ ## 动作与状态
182
+
183
+ - `click_element`:当前候选索引点击。
184
+ - `set_value`:对可编辑角色设置显式文本,允许显式空串清空。
185
+ - `type_text`:当前选中元素必须等于 AX 报告的焦点;不自动发送。
186
+ - `press_key`:必须显式给 key,并绑定当前焦点。仅导航键可自动放行,Return 和快捷键交回人工/主 agent。
187
+ - `scroll`:显式给 up/down/left/right,每次一页。
188
+ - `wait`:等待 500ms 后重读。
189
+ - `click_at`、`drag`:可由 planner 给坐标用于预览,但总是请求确认和接管,不自动执行。
190
+ - `ask_user`:停止并请求确认。
191
+
192
+ | 返回 status | 含义 |
193
+ |---|---|
194
+ | `dry_run` | 返回计划,没有执行动作 |
195
+ | `done` | `verify` 已通过,`verified: true` |
196
+ | `model_done` | 仅模型声称完成,未通过确定性验证 |
197
+ | `confirm` | 敏感操作、模型风险、特殊按键或坐标操作,需接管 |
198
+ | `needs_planner` | pi 的目标不确定/目标类型或焦点问题,交给主 Agent 判断,附 handoff 上下文与剩余预算;不自动重试 |
199
+ | `escalate` | 其他信息不足、状态变化、连续无变化等;核心库未启用 plannerHandoff 时也用它返回低置信度 |
200
+ | `stop` | 应用不允许或置信度过低 |
201
+ | `max_steps` | 动作预算耗尽,任务未确认完成 |
202
+ | `error` | bind/observe/decide/execute/verify 等阶段失败,查看 `reason`;不回显可能含敏感信息的底层错误正文 |
203
+
204
+ `steps` 是已返回成功的动作调用数(含 wait),不是业务成功数。`outcomeUnknown: true` 表示动作派发或后续观测异常,不能假定什么都没发生。先读新状态,禁止自动重跑整段任务。
205
+
206
+ ## 相对参考项目的小改动
207
+
208
+ 保留 AX 筛选、四问、driver 注入和循环结构,未新增编排框架:
209
+
210
+ - TypeScript 类型覆盖公开接口及动作参数。
211
+ - Calculator 按钮点击恢复参考项目的 0.40 目标置信度门槛;其他应用/动作仍为 0.50,敏感标签与风险检查先执行,不受此门槛放宽影响。
212
+ - 最近操作使用当时的按钮语义和前后可见文本,不把旧元素编号当作稳定身份;仅显式开启完整轨迹时会保存这些模型输入。
213
+ - 应用白名单先于读取;执行前再取全量 AX,变化则停止。
214
+ - 策略与执行使用同一份已准备动作,坐标不能覆盖元素目标。
215
+ - 不默认填入空文本或 Return;输入焦点不匹配时停止。
216
+ - 完成概率与实际验证分开报告,概率缺失/越界不放行。
217
+ - 动作永不自动重试;只读瞬时错误和特定 HTTP 错误有限重试。
218
+ - 记录默认关闭,支持原有元数据日志和显式同意的完整文本轨迹;不自动安装 skill。
219
+
220
+ 这些保护不构成完整安全保证。AX 两次一致不保证界面原子性;模型分数未经校准;敏感词无法识别所有危险语义。敏感操作必须交给用户或主 agent 独立审查,不能通过改阈值、白名单或伪造决策绕过。不要并行运行多个 driver 或混入其他 CU 调用。
221
+
222
+ ## Skill 与在线评测(手动启用)
223
+
224
+ ```bash
225
+ npm run install-skill # 可选旧 Codex 入口,写入 ~/.codex/skills/deskhand;pi 不需要此命令
226
+ npm run uninstall-skill # 仅卸载属于当前 checkout 的安装
227
+ npm run eval # 默认拒绝网络调用,显示启用说明
228
+ npm run eval -- --live # 需要 TYPESAFE_API_KEY;会向 TypeSafe 发送合成样例并可能计费
229
+ ```
230
+
231
+ 如使用本地环境文件启动评测,可在构建后运行 `node --env-file=.env.local dist/eval.js --live`。不要提交真实密钥。
232
+
233
+ 评测只检查少量合成 AX 样例的目标及动作选择,不证明端到端桌面成功率。不写死价格,API usage 原样返回。外层 cua_repl 超时应覆盖全部观测和 HTTP 重试预算;本项目不能可靠取消已派发原生动作。
234
+
235
+ ## 当前已知限制
236
+
237
+ - Jev 仍按总目标逐轮选择动作,没有可靠的结构化任务进度管理;曾在计算器任务中耗尽预算而未完成。
238
+ - AX 候选解析依赖角色和标签,Music 已发现并修复可选行和 search text field 漏识别,其他控件仍可能遗漏。
239
+ - Chrome 管理后台联调中,实际页面已经跳转,Sky 却持续返回侧栏子菜单而非完整页面。Jev 的 full-tree 校验会拒绝此类结果;新 native 模式直接返回菜单,不加这一拒绝,但不保证 Sky 会返回完整页面。复杂表单与批量发布仍未验收。
240
+ - `needs_planner` 提供交接上下文和剩余预算,不是保证自动恢复的执行器;敏感操作、取消或未知结果不能被自动重放。
241
+ - 本机 Codex/Sky 为私有运行时,版本变化可能影响协议和授权。插件不包含这些二进制,也不绕过其授权。
242
+
243
+ 该版本保存了工作代码和失败证据。尚未完成的后台创建/发布测试,不记录为成功。
244
+
245
+ ## 文件结构
246
+
247
+ ```text
248
+ src/ AX、Jev、策略、driver、循环、pi 扩展、Sky 桥接、配置、日志及入口
249
+ skills/ pi 的 jev-codex-cua skill
250
+ skill/ 可选旧 Codex skill 模板
251
+ fixtures/ 合成候选评测样例
252
+ test/ 离线回归测试
253
+ docs/ 需求与设计
254
+ ```
@@ -0,0 +1,25 @@
1
+ # Third-party licenses
2
+
3
+ ## Sac-Y/Jev-cu — ISC
4
+
5
+ Source: https://github.com/Sac-Y/Jev-cu
6
+
7
+ Referenced revision: `e2cc92d731fac6e6aeb7acbd3c105cf23552cdec`.
8
+
9
+ The referenced package.json declares `"license": "ISC"` and an empty `author` field. The snapshot provides no separate LICENSE file or copyright notice. This attribution preserves that declaration without inventing an upstream copyright holder. The standard ISC permission and warranty text follows:
10
+
11
+ > Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
12
+ >
13
+ > THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ Adapted concepts/code include AX parsing and candidate ranking, Jev question construction, policy checks, the Codex cua adapter and the task loop. Original provenance is also retained in source comments and NOTICE.md.
16
+
17
+ ## Manaflow AI pi-codex-cua — MIT
18
+
19
+ Source: https://github.com/manaflow-ai/pi-codex-cua
20
+
21
+ Referenced revision: `6850a286c399b9b3d483278fe9f602644b101022`.
22
+
23
+ The full original MIT notice, including `Copyright (c) 2026 Manaflow AI`, is distributed at `src/sky/LICENSE.pi-codex-cua`.
24
+
25
+ The native MCP bridge and runtime discovery were adapted from this package. Official Codex/Sky binaries are not distributed by this package and retain their own terms.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { addAppGrant } from "./app-grants.js";
2
+ const args = process.argv.slice(2);
3
+ if (args.length === 1 && args[0] === "--help") {
4
+ console.log('Usage: node --experimental-strip-types src/add-app.ts "Exact App Name"\nOnly adds one app to the separate local grant file. Does not read credentials or change official permissions.');
5
+ }
6
+ else if (args.length !== 1) {
7
+ console.error("Provide exactly one application name. No remove, replace, bulk or wildcard mode is supported.");
8
+ process.exitCode = 1;
9
+ }
10
+ else {
11
+ try {
12
+ console.log(JSON.stringify(addAppGrant(args[0])));
13
+ }
14
+ catch (error) {
15
+ console.error(error instanceof Error ? error.message : "App grant failed; no credential file was accessed.");
16
+ process.exitCode = 1;
17
+ }
18
+ }
19
+ //# sourceMappingURL=add-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-app.js","sourceRoot":"","sources":["../src/add-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,uLAAuL,CAAC,CAAC;AACvM,CAAC;KAAM,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IAC7B,OAAO,CAAC,KAAK,CAAC,+FAA+F,CAAC,CAAC;IAC/G,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;KAAM,CAAC;IACN,IAAI,CAAC;QAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAC3D,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oDAAoD,CAAC,CAAC;QAC7G,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ export declare function appGrantsPath(envFile: string): string;
2
+ export declare function configuredGrantPath(env?: NodeJS.ProcessEnv): string;
3
+ export declare function validateAppName(value: string): string;
4
+ export declare function readAppGrants(path: string): string[];
5
+ /** Append one explicit grant. Never opens the credential file, replaces the allowlist or removes entries. */
6
+ export declare function addAppGrant(app: string, path?: string): {
7
+ added: boolean;
8
+ app: string;
9
+ file: string;
10
+ };
@@ -0,0 +1,104 @@
1
+ import { closeSync, constants, fstatSync, fsyncSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { randomUUID } from "node:crypto";
3
+ import { fileURLToPath } from "node:url";
4
+ export function appGrantsPath(envFile) { return `${envFile}.apps.json`; }
5
+ export function configuredGrantPath(env = process.env) {
6
+ return appGrantsPath(env.JEV_CUA_ENV_FILE ?? fileURLToPath(new URL("../.env.local", import.meta.url)));
7
+ }
8
+ export function validateAppName(value) {
9
+ const name = value.trim();
10
+ if (!name || name.length > 200 || /[\p{Cc}\p{Cf},*?\[\]{}]/u.test(value) || name.startsWith("--")
11
+ || /^(all|all apps|全部|所有|所有应用|全部应用)$/i.test(name)) {
12
+ throw new Error("Specify exactly one application name, bundle ID or .app path; bulk grants, wildcards and control characters are not allowed.");
13
+ }
14
+ if (name.startsWith("/") && !/\.app\/?$/.test(name))
15
+ throw new Error("An application path must end in .app.");
16
+ return name;
17
+ }
18
+ export function readAppGrants(path) {
19
+ let fd;
20
+ try {
21
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
22
+ }
23
+ catch (error) {
24
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
25
+ return [];
26
+ throw new Error("Cannot open the app grants file safely.");
27
+ }
28
+ try {
29
+ const info = fstatSync(fd);
30
+ if (!info.isFile() || info.size > 16_384 || (info.mode & 0o077) !== 0 || (process.getuid && info.uid !== process.getuid())) {
31
+ throw new Error("App grants must be a small owner-only file (600).");
32
+ }
33
+ let data;
34
+ try {
35
+ data = JSON.parse(readFileSync(fd, "utf8"));
36
+ }
37
+ catch {
38
+ throw new Error("Invalid app grants JSON; no changes were made.");
39
+ }
40
+ if (!data || typeof data !== "object" || Array.isArray(data))
41
+ throw new Error("Invalid app grants format.");
42
+ const record = data;
43
+ if (record.version !== 1 || !Array.isArray(record.apps) || record.apps.length > 100
44
+ || Object.keys(record).some((key) => key !== "version" && key !== "apps"))
45
+ throw new Error("Invalid app grants format.");
46
+ const apps = [];
47
+ for (const item of record.apps) {
48
+ if (typeof item !== "string" || validateAppName(item) !== item)
49
+ throw new Error("Invalid application entry in app grants file.");
50
+ if (!apps.includes(item))
51
+ apps.push(item);
52
+ }
53
+ return apps;
54
+ }
55
+ finally {
56
+ closeSync(fd);
57
+ }
58
+ }
59
+ /** Append one explicit grant. Never opens the credential file, replaces the allowlist or removes entries. */
60
+ export function addAppGrant(app, path = configuredGrantPath()) {
61
+ const name = validateAppName(app);
62
+ const lock = `${path}.lock`;
63
+ let lockFd;
64
+ try {
65
+ lockFd = openSync(lock, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
66
+ }
67
+ catch {
68
+ throw new Error("Cannot lock app grants; another update may be running or the directory is not writable. No changes were made.");
69
+ }
70
+ let temporary;
71
+ try {
72
+ const apps = readAppGrants(path);
73
+ if (apps.includes(name))
74
+ return { added: false, app: name, file: path };
75
+ if (apps.length >= 100)
76
+ throw new Error("App grants limit reached; no changes were made.");
77
+ const body = JSON.stringify({ version: 1, apps: [...apps, name] }, null, 2) + "\n";
78
+ if (Buffer.byteLength(body) > 16_384)
79
+ throw new Error("App grants size limit reached; no changes were made.");
80
+ temporary = `${path}.${randomUUID()}.tmp`;
81
+ const fd = openSync(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
82
+ try {
83
+ writeFileSync(fd, body);
84
+ fsyncSync(fd);
85
+ }
86
+ finally {
87
+ closeSync(fd);
88
+ }
89
+ renameSync(temporary, path);
90
+ temporary = undefined;
91
+ return { added: true, app: name, file: path };
92
+ }
93
+ finally {
94
+ if (temporary) {
95
+ try {
96
+ unlinkSync(temporary);
97
+ }
98
+ catch { /* leave original grants intact */ }
99
+ }
100
+ closeSync(lockFd);
101
+ unlinkSync(lock);
102
+ }
103
+ }
104
+ //# sourceMappingURL=app-grants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-grants.js","sourceRoot":"","sources":["../src/app-grants.ts"],"names":[],"mappings":"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"}
package/dist/ax.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { AXElement, Candidate } from "./types.ts";
2
+ export declare function parseAX(ax: string): AXElement[];
3
+ export declare function selectCandidates(elements: readonly AXElement[], goal?: string, max?: number): {
4
+ candidates: Candidate[];
5
+ total: number;
6
+ clipped: boolean;
7
+ };
8
+ export declare function buildContext(ax: string): string;
9
+ export declare function focusedIndex(ax: string): number | null;
package/dist/ax.js ADDED
@@ -0,0 +1,77 @@
1
+ const ROLES = [
2
+ "standard window", "split group", "scroll area", "HTML content", "content list",
3
+ "menu bar main-menu-bar", "menu bar", "toolbar", "radio button", "close button",
4
+ "minimize button", "full screen button", "search text field", "search field", "text field", "text area",
5
+ "pop up button", "toggle button", "stepper", "combo box", "menu item", "button",
6
+ "checkbox", "heading", "image", "link", "text", "grid", "list", "date time area", "row", "tab", "container", "Event",
7
+ ].sort((a, b) => b.length - a.length);
8
+ const ALIASES = {
9
+ "search text field": "search field",
10
+ 标准窗口: "standard window", 分离组: "split group", 滚动区: "scroll area", 文本: "text", 按钮: "button",
11
+ 文本栏: "text field", 文本区域: "text area", 搜索栏: "search field", 复选框: "checkbox", 单选按钮: "radio button",
12
+ };
13
+ const CLICKABLE = new Set([
14
+ "button", "radio button", "close button", "minimize button", "full screen button", "link", "menu item",
15
+ "text field", "text area", "search field", "checkbox", "pop up button", "toggle button", "stepper", "combo box", "tab", "list", "date time area", "scroll area",
16
+ ]);
17
+ export function parseAX(ax) {
18
+ const elements = [];
19
+ const seen = new Set();
20
+ for (const raw of ax.split(/\r?\n/)) {
21
+ const match = raw.match(/^(\s*)(\d+)\s+(.*)$/);
22
+ if (!match)
23
+ continue;
24
+ const index = Number(match[2]);
25
+ if (!Number.isSafeInteger(index) || seen.has(index))
26
+ throw new Error("AX contains invalid or duplicate element indexes; request a full tree.");
27
+ seen.add(index);
28
+ const rest = match[3].trim();
29
+ const sourceRole = ROLES.find((role) => rest === role || rest.startsWith(role + " ")) ?? rest.split(" ")[0];
30
+ elements.push({ index, role: ALIASES[sourceRole] ?? sourceRole,
31
+ label: rest.slice(sourceRole.length).trim().replace(/,?\s*Secondary Actions:.*$/i, "").trim(),
32
+ depth: match[1].replace(/\t/g, " ").length, raw });
33
+ }
34
+ return elements;
35
+ }
36
+ export function selectCandidates(elements, goal = "", max = 40) {
37
+ if (!Number.isInteger(max) || max < 1 || max > 200)
38
+ throw new Error("candidateMax must be an integer in [1, 200].");
39
+ const tokens = goal.toLowerCase().split(/[^a-z0-9\u4e00-\u9fff]+/).filter((token) => token.length >= 2);
40
+ const scored = [];
41
+ for (const element of elements) {
42
+ const states = new Set((element.label.match(/^\(([^)]*)\)/)?.[1] ?? "").split(",").map((state) => state.trim().toLowerCase()));
43
+ const selectableRow = element.role === "row" && (states.has("selectable") || states.has("selected"));
44
+ if ((!CLICKABLE.has(element.role) && !selectableRow) || states.has("disabled") || /\(disabled\)/i.test(element.raw))
45
+ continue;
46
+ let score = selectableRow ? 2 : 0;
47
+ if (["button", "toggle button", "radio button", "menu item", "pop up button", "combo box"].includes(element.role))
48
+ score += 3;
49
+ if (["text field", "text area", "search field"].includes(element.role))
50
+ score += 2;
51
+ if (element.role === "link")
52
+ score += 1;
53
+ const label = element.label.toLowerCase();
54
+ for (const token of tokens)
55
+ if (label.includes(token))
56
+ score += 4;
57
+ if (!label || /^javascript:;?$/.test(label))
58
+ score -= 4;
59
+ if (/previous month|next month|today|搜索|search/i.test(label) && /month|搜索|search/i.test(goal))
60
+ score += 6;
61
+ if (element.role === "toggle button" && /toolbar|tool\b/i.test(goal))
62
+ score += 5;
63
+ scored.push({ ...element, score });
64
+ }
65
+ scored.sort((a, b) => b.score - a.score || a.index - b.index);
66
+ return { candidates: scored.slice(0, max), total: scored.length, clipped: scored.length > max };
67
+ }
68
+ export function buildContext(ax) {
69
+ const lines = ax.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
70
+ return [...lines.slice(0, 2), ...parseAX(ax).filter((e) => e.role === "text").slice(0, 6).map((e) => e.raw.trim()),
71
+ lines.find((line) => /focused UI element/i.test(line))].filter(Boolean).join("\n").slice(0, 1500);
72
+ }
73
+ export function focusedIndex(ax) {
74
+ const match = ax.match(/focused UI element is\s+(\d+)\b/i);
75
+ return match ? Number(match[1]) : null;
76
+ }
77
+ //# sourceMappingURL=ax.js.map
package/dist/ax.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ax.js","sourceRoot":"","sources":["../src/ax.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Direction, Driver } from "./types.ts";
2
+ export interface CuaApp {
3
+ getAXState(options: {
4
+ emit: false;
5
+ disableDiffing: true;
6
+ }): Promise<string>;
7
+ click?(index: number): Promise<unknown>;
8
+ setValue?(index: number, value: string): Promise<unknown>;
9
+ typeText?(text: string): Promise<unknown>;
10
+ pressKey?(key: string): Promise<unknown>;
11
+ scroll?(index: number, direction: Direction, pages: number): Promise<unknown>;
12
+ }
13
+ export interface CuaRuntime {
14
+ getApp(name: string): Promise<CuaApp>;
15
+ }
16
+ export declare function withReadRetry<T>(read: () => Promise<T>): Promise<T>;
17
+ export declare function createCuaDriver(cua: CuaRuntime): Driver;