dsh-plugin-lcu 0.2.9 → 0.3.4
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.
- package/CHANGELOG.md +60 -0
- package/README.md +174 -104
- package/cordis.patch.yml +11 -6
- package/docs/README.zh.md +154 -168
- package/helper/sky-service.mjs +484 -0
- package/lib/app.js +284 -0
- package/lib/app.js.map +1 -0
- package/lib/approval.js +45 -4
- package/lib/approval.js.map +1 -1
- package/lib/connection.js +82 -35
- package/lib/connection.js.map +1 -1
- package/lib/control.js +214 -0
- package/lib/control.js.map +1 -0
- package/lib/diag.js +1 -1
- package/lib/host-guard.js +2 -2
- package/lib/index.js +100 -30
- package/lib/index.js.map +1 -1
- package/lib/session.js +137 -0
- package/lib/session.js.map +1 -0
- package/lib/tool.js +7 -4
- package/lib/tool.js.map +1 -1
- package/lib/types/app.d.ts +118 -0
- package/lib/types/app.d.ts.map +1 -0
- package/lib/types/approval.d.ts +28 -3
- package/lib/types/approval.d.ts.map +1 -1
- package/lib/types/connection.d.ts +27 -13
- package/lib/types/connection.d.ts.map +1 -1
- package/lib/types/control.d.ts +54 -0
- package/lib/types/control.d.ts.map +1 -0
- package/lib/types/diag.d.ts +1 -1
- package/lib/types/host-guard.d.ts +2 -2
- package/lib/types/index.d.ts +26 -6
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/session.d.ts +89 -0
- package/lib/types/session.d.ts.map +1 -0
- package/lib/types/tool.d.ts +6 -5
- package/lib/types/tool.d.ts.map +1 -1
- package/package.json +8 -6
- package/scripts/probe-lcu.mjs +40 -18
- package/scripts/gen-presets.mjs +0 -266
package/docs/README.zh.md
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[English](../README.md) | 中文
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness),让 Agent 能看屏幕、点鼠标、操作真实浏览器标签页。
|
|
5
|
+
用 **ChatGPT 桌面应用内置的 computer-use 运行时**,从 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 驱动桌面和 Chrome。
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
本插件**直接启动那个运行时**并通过 MCP 与它通信。整个集成都在这里:定位并校验应用、构造运行时需要的环境、批准桥、回合生命周期、以及负责逐轮清理的 macOS 服务 wrapper。**不需要额外安装任何东西** —— 不需要别的包装项目、不需要第二个解释器、不需要 Python。
|
|
8
|
+
|
|
9
|
+
**不涉及 Codex 或 ChatGPT 的登录。** 运行时来自你本机的安装,插件从不下载、改写或认证它。
|
|
11
10
|
|
|
12
11
|
## 目录
|
|
13
12
|
|
|
@@ -19,105 +18,76 @@ LCU 从不解包、下载、认证或改写它。
|
|
|
19
18
|
- [批准与安全模型](#批准与安全模型)
|
|
20
19
|
- [实现说明](#实现说明)
|
|
21
20
|
- [排障](#排障)
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
21
|
+
- [与 LCU 的关系](#与-lcu-的关系)
|
|
22
|
+
- [配套工具](#配套工具)
|
|
23
|
+
- [已知限制与未做的工作](#已知限制与未做的工作)
|
|
24
24
|
- [开发](#开发)
|
|
25
25
|
- [许可](#许可)
|
|
26
26
|
|
|
27
27
|
## 能力
|
|
28
28
|
|
|
29
|
-
两个模型可见工具,**schema 完全由服务端提供**,本插件不自己发明:
|
|
30
|
-
|
|
31
29
|
| 工具 | 作用 |
|
|
32
30
|
|---|---|
|
|
33
|
-
| `js` |
|
|
34
|
-
| `js_reset` |
|
|
31
|
+
| `js` | 对 `cua` 桌面/浏览器 API 运行一段 JavaScript。第一次调用会返回 API 文档;选中应用或标签页会返回初始 UI 状态。 |
|
|
32
|
+
| `js_reset` | 丢弃持久的 JavaScript 会话,重新起一个运行时。 |
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
`js_add_node_module_dir`。
|
|
34
|
+
另外两个**只给宿主、永不暴露给模型**的工具:`turn_ended`(逐轮清理)和 `js_add_node_module_dir`。
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
插件自己只加了一个工具:
|
|
40
37
|
|
|
41
38
|
| 工具 | 作用 |
|
|
42
39
|
|---|---|
|
|
43
|
-
| `computer_use_stop` | 不带参数时列出运行时当前为本次会话持有的应用;带 `app
|
|
40
|
+
| `computer_use_stop` | 不带参数时列出运行时当前为本次会话持有的应用;带 `app`(其中一个 bundle id)时释放它。它清除宿主应用对**某一个**应用的"正在使用你的电脑"状态,而不结束会话。 |
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
截图通过 DSH 的附件存储以持久图片返回,所以声明了图像输入的模型路由**真的能看见屏幕**。
|
|
46
43
|
|
|
47
44
|
## 前置条件
|
|
48
45
|
|
|
49
46
|
| | |
|
|
50
47
|
|---|---|
|
|
51
|
-
|
|
|
52
|
-
| ChatGPT
|
|
53
|
-
| Python | 3.12 或更新,位于 `PATH`,或 `/opt/homebrew/bin`、`/usr/local/bin`、`/usr/bin`。 |
|
|
54
|
-
| LCU | 需另行安装,见下。 |
|
|
48
|
+
| 系统 | **Apple Silicon 上的 macOS。** 启动器解析 macOS 应用包,生命周期 wrapper 是 macOS 服务;其他平台这两块都要重做。 |
|
|
49
|
+
| ChatGPT 桌面应用 | 装在 `/Applications/ChatGPT.app`(或用 `app` 指定)。它提供运行时、指令和签名助手。 |
|
|
55
50
|
| DSH | 一个你能装 bundle 的 profile。 |
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
**没有** Python 要求,**没有**需要单独安装的运行时。插件启动的是应用自己的 `node` 和它自己的入口。
|
|
58
53
|
|
|
59
54
|
## 安装
|
|
60
55
|
|
|
61
|
-
### 1.
|
|
62
|
-
|
|
63
|
-
下载对应平台的 release 归档,**校验 checksum**,然后运行它自带的安装器。**不要注册其他 harness** ——
|
|
64
|
-
本插件就是你的 harness。
|
|
65
|
-
|
|
66
|
-
```sh
|
|
67
|
-
TAG=v0.9.6
|
|
68
|
-
TARGET=darwin-arm64
|
|
69
|
-
curl -fLO "https://github.com/amontlabs/lcu/releases/download/$TAG/lcu-${TAG#v}-$TARGET.tar.gz"
|
|
70
|
-
curl -fLO "https://github.com/amontlabs/lcu/releases/download/$TAG/lcu-${TAG#v}-$TARGET.tar.gz.sha256"
|
|
71
|
-
shasum -a 256 -c "lcu-${TAG#v}-$TARGET.tar.gz.sha256" # 必须打印 OK
|
|
72
|
-
tar -xzf "lcu-${TAG#v}-$TARGET.tar.gz" && cd "lcu-${TAG#v}-$TARGET"
|
|
73
|
-
./scripts/install.sh --runtime-only --yes
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
确认运行时能加载:
|
|
77
|
-
|
|
78
|
-
```sh
|
|
79
|
-
~/.local/share/lcu/current/bin/lcu doctor --non-interactive
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
期望看到 `Original Mac provider loaded; app listing and app-state methods are available`。
|
|
83
|
-
隐私权限是**首次使用时**才授予的,不在这里。
|
|
84
|
-
|
|
85
|
-
### 2. 把插件装进 profile
|
|
56
|
+
### 1. 把插件装进 profile
|
|
86
57
|
|
|
87
|
-
|
|
58
|
+
装完这一行配置就在该 profile 里生效。**加载时不启动任何东西。**
|
|
88
59
|
|
|
89
60
|
```sh
|
|
90
61
|
dsh plugin --profile <profile> add dsh-plugin-lcu
|
|
91
62
|
```
|
|
92
63
|
|
|
93
|
-
桌面版 App
|
|
94
|
-
(Settings ▸ Plugins),它执行的是同一个 pnpm 操作。
|
|
64
|
+
桌面版 App 的托管 profile 用 CLI 会被拒;请用 App 内的插件管理器(设置 ▸ 插件),它跑的是同一个 pnpm 操作。
|
|
95
65
|
|
|
96
|
-
###
|
|
66
|
+
### 2. 生成 preset
|
|
97
67
|
|
|
98
|
-
DSH 的
|
|
99
|
-
|
|
68
|
+
DSH 的 agent preset **没有继承**:一个 preset 的 `config.plugins` 就是它完整的插件列表,而 patch 是**整体替换**一个条目而不是合并进去。所以自定义 preset 必须重述它的基底 —— 而基底随应用一起发布,会随 DSH 升级变化。
|
|
69
|
+
|
|
70
|
+
这份"重述"**不属于本包**。它服务的不止一个插件,而它写入的那个区间**DSH 自己的设置 UI 也会写**,所以它独立在外:
|
|
100
71
|
|
|
101
72
|
```sh
|
|
102
|
-
|
|
73
|
+
git clone https://github.com/ckanner/dsh-preset-generator
|
|
74
|
+
node dsh-preset-generator/src/gen-presets.mjs --profile ~/.dsh/profiles/<profile>
|
|
103
75
|
```
|
|
104
76
|
|
|
105
|
-
它会在该 profile 的 `cordis.patch.yml`
|
|
77
|
+
它会在该 profile 的 `cordis.patch.yml` 里写一个带标记的块,含两个 preset:
|
|
106
78
|
|
|
107
|
-
| preset |
|
|
79
|
+
| preset | 基底 | 增加 |
|
|
108
80
|
|---|---|---|
|
|
109
|
-
| `daily
|
|
110
|
-
| `heavy
|
|
81
|
+
| `daily` | 自带的 `ptc` preset | 一套自己的 Codex 委派 provider |
|
|
82
|
+
| `heavy` | 同上,且 `tool-presentation: both` | 以上全部,且本插件接入 |
|
|
111
83
|
|
|
112
|
-
DSH
|
|
84
|
+
DSH 升级后重跑一次,让副本跟上。`--daily-only` 只生成 `daily`;`--dry-run` 只打印不写;`--out FILE` 写到别处。官方的 `tool-subagent-codex` 行保持**禁用**(和基底一致):另有一套 provider 自己注册 `subagent_codex`,而两行在同一个 scope 里注册同名工具会冲突。
|
|
113
85
|
|
|
114
|
-
> `heavy`
|
|
115
|
-
> `js` 就得作为一段 JavaScript 字符串**嵌套**在另一段 JavaScript 程序里。`both` 让 `js` 可以直接调。
|
|
86
|
+
> `heavy` 特意把工具呈现设为 `both`。纯 `ptc` 呈现下模型只看得到 `run_code`,`js` 就得嵌成另一个 JavaScript 程序里的字符串。`both` 让 `js` 可以被直接调用。
|
|
116
87
|
|
|
117
|
-
###
|
|
88
|
+
### 3. 配置哪些模式能用
|
|
118
89
|
|
|
119
|
-
|
|
120
|
-
(或 profile patch)让它与你生成的 preset id 一致:
|
|
90
|
+
插件是一个根行,带 `presets` 白名单。编辑已安装的 `cordis.patch.yml`(或 profile patch),让它匹配你生成的 preset id:
|
|
121
91
|
|
|
122
92
|
```yaml
|
|
123
93
|
- id: lcu
|
|
@@ -127,13 +97,22 @@ DSH 升级后**重跑一次**,让副本跟上。`--dry-run` 只打印不写入
|
|
|
127
97
|
- heavy
|
|
128
98
|
```
|
|
129
99
|
|
|
130
|
-
###
|
|
100
|
+
### 4. 重启,然后做一次调用
|
|
101
|
+
|
|
102
|
+
重启 harness —— 插件**代码和配置都不热更新**。然后在 `heavy` 模式(显示为**重活**)里起一个任务,让它做点无害的事:
|
|
103
|
+
|
|
104
|
+
> 用 `js` 工具运行 `await cua.getState();`,告诉我哪些应用在跑。
|
|
131
105
|
|
|
132
|
-
|
|
106
|
+
第一次触碰某个应用时,运行时会请求批准。见下文。
|
|
133
107
|
|
|
134
|
-
|
|
108
|
+
首次 attach 时插件会解析应用,并把结果写进诊断日志:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
app: /Applications/ChatGPT.app version=26.1002.52244 runtime=0.0.29/20261003001300-807782c586fc
|
|
112
|
+
attach: launching /Applications/ChatGPT.app/Contents/Resources/cua_node/bin/node [...]
|
|
113
|
+
```
|
|
135
114
|
|
|
136
|
-
|
|
115
|
+
如果应用缺失,或者它某个文件**别的账号可以改写**,attach 会被拒绝并写明原因,会话就只是没有这个能力而已。
|
|
137
116
|
|
|
138
117
|
### 可选:启用 Chrome
|
|
139
118
|
|
|
@@ -142,20 +121,13 @@ config:
|
|
|
142
121
|
chrome: true
|
|
143
122
|
```
|
|
144
123
|
|
|
145
|
-
|
|
124
|
+
这会打开运行时的浏览器面。浏览器那一半是 OpenAI 的,驱动页面靠**官方 ChatGPT Chrome 扩展**,它连的是一个 native messaging host。在装了 ChatGPT 桌面应用的机器上,那个 host 已经注册好了,所以**不需要再做别的**:在你想驱动的浏览器 profile 里启用该扩展,并确认它出现在 `cua.getState()` 里。
|
|
146
125
|
|
|
147
|
-
|
|
148
|
-
~/.local/share/lcu/current/bin/lcu browser install
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
在你要驱动的 Chrome profile 里启用**官方 ChatGPT 扩展**,并重启 Chrome(或在 `chrome://extensions`
|
|
152
|
-
把该扩展关掉再开),让它剥离旧的 native host、重连到 LCU 的 relay。
|
|
153
|
-
`lcu browser status` 会告诉你连接器是否已指向本次 LCU 安装。站点仍然逐个精确 origin 批准。
|
|
126
|
+
站点仍然是逐个精确 origin 批准。
|
|
154
127
|
|
|
155
128
|
### 可选:预授权站点
|
|
156
129
|
|
|
157
|
-
|
|
158
|
-
(必须能通过 `new URL(...).origin` 原样往返):
|
|
130
|
+
运行时想用的每个站点都会问一次。要跳过你信任的 origin 的提示,就列出**精确 origin** —— 消息必须经 `new URL(...).origin` 往返后不变:
|
|
159
131
|
|
|
160
132
|
```yaml
|
|
161
133
|
config:
|
|
@@ -163,133 +135,148 @@ config:
|
|
|
163
135
|
- http://localhost:3000
|
|
164
136
|
```
|
|
165
137
|
|
|
166
|
-
诊断日志会记下每一个被问到的 origin
|
|
138
|
+
诊断日志会记下每一个被问到的 origin,这是发现它们最方便的办法。
|
|
167
139
|
|
|
168
140
|
## 使用
|
|
169
141
|
|
|
170
|
-
|
|
142
|
+
起任务时选一个已启用的模式。工具是**按 Agent 挂载**的:其他模式下的会话永远看不到它们,也永远不会起运行时。
|
|
171
143
|
|
|
172
|
-
|
|
144
|
+
典型的请求:
|
|
173
145
|
|
|
174
146
|
```
|
|
175
147
|
截一张 Finder 窗口的图,告诉我分辨率。
|
|
176
|
-
|
|
177
|
-
打开 Safari
|
|
148
|
+
列出我当前的 Chrome 标签页。
|
|
149
|
+
打开 Safari,去 example.com,把页面标题读回来。
|
|
178
150
|
```
|
|
179
151
|
|
|
180
152
|
## 配置
|
|
181
153
|
|
|
182
|
-
| 字段 |
|
|
154
|
+
| 字段 | 默认 | 含义 |
|
|
183
155
|
|---|---|---|
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
156
|
+
| `app` | `/Applications/ChatGPT.app` | 提供 computer use 的应用。 |
|
|
157
|
+
| `command` | 未设 | 用一个显式可执行文件替换计算出的启动方式,用于内置解析够不到的应用。它会跳过运行时自己的环境设置。 |
|
|
158
|
+
| `chrome` | `false` | 打开浏览器面。 |
|
|
159
|
+
| `audio` | `false` | 打开运行时的 computer-audio API。 |
|
|
160
|
+
| `presets` | `["heavy"]` | 允许使用这些工具的 Agent preset id。 |
|
|
161
|
+
| `allowedOrigins` | `[]` | 免问的精确 HTTP(S) origin。非法条目会被丢弃,绝不放宽。 |
|
|
162
|
+
| `allowedApps` | `[]` | 免批准的 bundle identifier。**承载 agent 的应用即使被列进去也会被拒**。 |
|
|
163
|
+
| `sectionOrder` | `0` | 注入的运行时指令在 prompt 中的排序。 |
|
|
190
164
|
|
|
191
165
|
## 批准与安全模型
|
|
192
166
|
|
|
193
|
-
|
|
167
|
+
**模型不能批准任何东西。每个决定都是人的。**
|
|
168
|
+
|
|
169
|
+
- **按应用批准。** 运行时在用某个应用前会问。插件把它的选项 —— *仅此次*、*本次会话*、*始终允许*(运行时提供哪些就渲染哪些)、以及 *拒绝* —— 通过 DSH 的提问界面呈现。回答被精确映射回运行时提供的那个 scope;**运行时没提供的 scope 授不出去**。
|
|
170
|
+
- **站点批准。** 浏览器访问按精确 origin 询问。`allowedOrigins` 只匹配精确 origin;多一个斜杠、带路径、大小写不同都算不同 origin,会被问而不是被授予。
|
|
171
|
+
- **承载 agent 的宿主永不可批准。** Computer use 能点击已批准应用显示的任何东西,**包括批准弹窗本身**。守卫在问任何问题**之前**就拒绝承载 agent 的应用 —— 通过进程祖先和一份 agent 宿主/终端名单。
|
|
172
|
+
- **Fail closed。** 没有提问界面、被忽略的提示、无法识别的请求形状、被中止的调用 —— 全都以 *cancel* 结束,而运行时把它当作拒绝。
|
|
173
|
+
|
|
174
|
+
插件自己不存任何权限缓存;"始终允许"由运行时按应用记住。
|
|
194
175
|
|
|
195
|
-
|
|
196
|
-
*Allow once*、运行时提供时的 *Allow for this session* 与 *Always allow*、以及 *Decline* ——
|
|
197
|
-
经 DSH 的提问界面。选择会映射回**恰好被提供的**那个 scope;运行时没提供的 scope 无法被授予。
|
|
198
|
-
- **站点批准。** 浏览器访问按精确 origin 逐次批准。`allowedOrigins` 只做精确匹配:
|
|
199
|
-
带尾部斜杠、带路径、大小写不同,都是**另一个 origin**,会去问而不是放行。
|
|
200
|
-
- **承载 agent 自己的应用永不可批准。** computer use 能点被批准应用里的任何东西,**包括批准弹窗本身**。
|
|
201
|
-
守卫在问用户之前就拒绝承载 agent 的应用 —— 依据是本进程的祖先链和一份 agent 宿主/终端名单。
|
|
202
|
-
- **Fail closed。** 没有提问界面、用户关掉弹窗、请求形状无法识别、调用被中断 —— 全部以 *cancel* 结束,
|
|
203
|
-
而运行时把 cancel 当作拒绝。
|
|
176
|
+
### 无人值守
|
|
204
177
|
|
|
205
|
-
|
|
178
|
+
每个批准都属于人,而**没人回答就是拒绝** —— 运行时不默认授予。所以无人值守的运行必须两半都提前定好:
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
config:
|
|
182
|
+
allowedApps:
|
|
183
|
+
- com.google.Chrome # 免问使用这个应用
|
|
184
|
+
allowedOrigins:
|
|
185
|
+
- https://example.com # 免问访问这个精确 origin
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- `allowedApps` 按 bundle identifier 匹配,忽略大小写。**承载 agent 的应用在查列表之前就被拒**,所以没有任何条目能授权它。
|
|
189
|
+
- `allowedOrigins` 只匹配精确 origin;带路径、多斜杠或大小写不同都算不同 origin,仍然会问。
|
|
190
|
+
- 没列进去的都会被问,而没人在场时就是拒绝。
|
|
191
|
+
|
|
192
|
+
诊断日志会记下每一个被问到的应用和 origin(`approval: site https://example.com asking (add it to allowedOrigins to skip this)`),这是发现一次运行需要哪些确切值的方法。
|
|
206
193
|
|
|
207
194
|
## 实现说明
|
|
208
195
|
|
|
209
196
|
```
|
|
197
|
+
src/app.ts 定位并校验应用;构造运行时环境;规划启动
|
|
210
198
|
src/connection.ts MCP 客户端:握手、工具发现、调用、elicitation、生命周期
|
|
211
199
|
src/approval.ts 批准形状识别与 label→value 映射
|
|
212
|
-
src/host-guard.ts
|
|
213
|
-
src/tool.ts
|
|
214
|
-
src/index.ts 插件:按 Agent
|
|
215
|
-
src/
|
|
200
|
+
src/host-guard.ts 防自我批准的守卫
|
|
201
|
+
src/tool.ts 工具定义、文本投影、持久截图
|
|
202
|
+
src/index.ts 插件:按 Agent 挂载、指令、turn_ended、批准
|
|
203
|
+
src/control.ts relay:把运行时的控制 API 在插件与 wrapper 之间搬运
|
|
204
|
+
src/session.ts 连接门面,跟随应用更新
|
|
205
|
+
src/diag.ts attach/批准 诊断日志
|
|
206
|
+
helper/sky-service.mjs 运行时的 Sky 服务:turn-ended 钩子 + 控制通道
|
|
216
207
|
```
|
|
217
208
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
209
|
+
**运行时是被直接启动的。** `src/app.ts` 找到 `ChatGPT.app`,确认它需要的部件都在、且**别的账号改不了**,然后算出运行时需要的环境 —— 它自己的 `node` 和 `node_repl`、模块根、trusted code paths、API 面、以及 macOS native pipe 用的签名助手。它**不**把 `NODE_REPL_TRUSTED_SERVICES` 设成应用默认值:原因见下。
|
|
210
|
+
|
|
211
|
+
**turn-ended 钩子是运行时唯一缺的东西。** 一个永不结束的回合,就是一个**永不释放**的按应用 Stop,之后应用会拒绝每一个后续回合。运行时向它的 trusted services 暴露 `addTurnEndedHandler`,但自己**没有装任何 handler**,所以 `helper/sky-service.mjs` 是**作为** `sky` trusted service 被加载的:它把每个请求原样转发给应用自己的服务,并加上那个钩子 —— 钩子通过**应用自己的签名客户端**请应用清理已结束的回合。
|
|
212
|
+
|
|
213
|
+
这**刻意少于**那个显而易见的实现。让一个监督者进程去做、再让它 spawn 签名客户端并传 `turn-ended` 参数,需要 **Apple Events** —— 而当负责进程是一个 hardened-runtime 的 harness 时,macOS **既拒绝授予、也拒绝弹窗询问**。那一步**必然超时**。应用自己的 IPC 才是执行清理的那一步,不需要 Apple Events,**几十毫秒就完成**。所以这里没有监督者进程,也没有 lifetime socket。
|
|
214
|
+
|
|
215
|
+
**控制通道是一个 relay,而且由插件自己 serve。** 提前释放某个应用需要用运行时的控制 API,而它活在运行时的进程里。wrapper 连上插件 serve 的 socket,自称 *service*,上报它见过的回合 context,并在那里回答 `status` 与 `stop`。**relay 不做任何判断**:某个会话和回合是否真实、某个应用是否真被持有,只有 wrapper 能回答 —— 因为只有它手里有运行时用来标记这些状态的回合元数据。
|
|
216
|
+
|
|
217
|
+
这条通道有两个细节猜不出来。wrapper 运行在运行时的 JavaScript 沙箱里,而沙箱**拒绝普通 socket 连接(`EPERM`)** —— 无论 socket 在每用户临时目录还是 `/private/tmp` —— 所以它走运行时自己的 `nativePipe` API。而且那条管道**会静默丢弃字符串写入**,消息必须以 Buffer 写出;这一点极易被误判成"对端从未应答"。
|
|
218
|
+
|
|
219
|
+
**没有 MCP SDK 依赖。** harness 自带的 MCP 桥声明 `capabilities: {}`,因此**无法回答 elicitation** —— 而那正是运行时请求批准的方式;把第二个 SDK 拉进 profile 插件又会钉住一个宿主并不拥有的版本。MCP over stdio 就是按行分隔的 JSON-RPC,所以这里自己拥有这条线。加上对 DSH 包只用类型导入,本插件**没有任何运行时依赖**。
|
|
222
220
|
|
|
223
|
-
**工具是按 Agent
|
|
224
|
-
连接在 Agent 创建时、或它提交 preset 选择时建立;插件贡献的一切都注册进**该 Agent 自己的 context**,
|
|
225
|
-
因此会随其销毁而回退。
|
|
221
|
+
**工具是按 Agent 注册的,不是在挂载时。** 工具 schema 归服务器所有,所以只能握手之后取。一个 Agent 的连接在它被创建时、或它确定了 preset 选择时打开,插件贡献的一切都注册进**该 Agent 自己的 context**,所以销毁时一起回退。
|
|
226
222
|
|
|
227
|
-
**两种 preset 时序都处理了。**
|
|
228
|
-
所以只看 `agent/created` 会看到错误的组合;注册表会重新发出 `agent-preset/selected`,插件同时也监听它。
|
|
223
|
+
**两种 preset 时序都处理了。** 新任务先用部署默认值创建,picker 的选择之后再应用,所以只看 `agent/created` 会看到错误的组合;注册表会重新发出 `agent-preset/selected`,插件也响应它。
|
|
229
224
|
|
|
230
|
-
|
|
225
|
+
**构造上就是懒的。** 加载时不启动任何东西。没有启用的会话,就没有运行时进程。
|
|
231
226
|
|
|
232
|
-
|
|
233
|
-
它本身很短 —— API 手册在 `js` 的工具描述和首次调用结果里。
|
|
227
|
+
**指令是注入的。** 服务器的 `initialize.instructions` 变成 Agent 上的一个 prompt 段。它刻意很短 —— API 手册在 `js` 工具描述和第一次工具结果里。
|
|
234
228
|
|
|
235
229
|
## 排障
|
|
236
230
|
|
|
237
|
-
插件关于 attach
|
|
231
|
+
插件关于 attach、启动和批准的每个决定都会追加到:
|
|
238
232
|
|
|
239
233
|
```
|
|
240
234
|
~/.dsh/lcu-diag.log
|
|
241
235
|
```
|
|
242
236
|
|
|
243
|
-
超过 1 MB
|
|
244
|
-
harness 没有当前会话可读的插件日志出口,而失败的 `agent/created` 监听器否则会被静默吞掉。
|
|
237
|
+
超过 1 MB 就重头开始。`LCU_DIAG=0` 关闭它。**这是第一个该看的地方**:harness 没有运行中的会话能读的插件日志界面,而一个抛错的 `agent/created` 监听器否则会被静默吞掉。
|
|
245
238
|
|
|
246
239
|
| 现象 | 原因与处理 |
|
|
247
240
|
|---|---|
|
|
248
|
-
|
|
|
249
|
-
| `
|
|
250
|
-
| `
|
|
251
|
-
|
|
|
252
|
-
|
|
|
253
|
-
|
|
|
254
|
-
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
##
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
-
|
|
287
|
-
而 harness 派生的一切,负责进程就是 harness 本身。所以必须在系统设置里给 **DeepSeek Harness**
|
|
288
|
-
开启屏幕录制与辅助功能;只给 ChatGPT 或 "Codex Computer Use" 是不够的,而且 macOS 不会自己弹出缺失的那项。
|
|
289
|
-
- **每个 Agent 一条 LCU 连接。** LCU 的 JavaScript 会话是按连接隔离的,其批准绑定真实 session 与 turn,
|
|
290
|
-
跨 Agent 共用会互相串扰。
|
|
291
|
-
- **`chrome` 需要扩展。** 只开开关、没装官方 ChatGPT 扩展、或没跑 `lcu browser install`,都不会有浏览器面。
|
|
292
|
-
- **未在 Linux 上验证。** 见[前置条件](#前置条件)。
|
|
241
|
+
| 已启用的模式里工具从不出现 | 看日志里的 `decide … composed=`。如果组合出的 preset 不在 `presets` 里,修白名单。如果没有 `agent-preset/selected` 行,说明模式从未被确定。 |
|
|
242
|
+
| 加载时报 `computer use is unavailable (…)`,或没有工具 | 应用解析失败。日志里有原因和路径;检查 `app`。 |
|
|
243
|
+
| `no userQuestions service -> cancel (fail closed)` | 这个 profile 里没挂载提问界面。 |
|
|
244
|
+
| `refusing to approve the app hosting this agent` | 按设计工作;换一个应用。 |
|
|
245
|
+
| ChatGPT 应用仍显示某应用正被 computer use 占用 | 让模型调 `computer_use_stop`,或关掉会话 —— 连接拥有运行时进程树,退出时会释放。 |
|
|
246
|
+
| `chrome: true` 但 Chrome 标签页一直不出现 | 那个浏览器 profile 里没启用官方扩展。打开 `chrome://extensions` 启用它,并确认它出现在 `cua.getState()` 里。 |
|
|
247
|
+
| 某应用后续每个回合都报 "explicitly stopped by the user" | 宿主应用的回合清理没跑成。检查日志里的 `turn cleanup` 失败;退出并重启 ChatGPT 应用可清除该状态。 |
|
|
248
|
+
|
|
249
|
+
`node scripts/probe-lcu.mjs` 会在**完全不涉及 harness** 的情况下启动运行时,打印协议版本、服务器身份、指令长度和工具列表 —— 用来区分是插件问题还是运行时问题。
|
|
250
|
+
|
|
251
|
+
`helper/sky-service.mjs` 有三个诊断开关(默认全关),因为它运行的地方**显而易见的通道都不可用**:运行时的 JavaScript 沙箱拒绝文件写入,而且它会捕获 console 输出。`DSH_SKY_DEBUG=1` 追踪钩子,`DSH_SKY_REPORT=1` 让下一次调用带着上一次清理的结果失败,`DSH_SKY_FORCE_ERROR=1` 用来证明模块确实被加载了。
|
|
252
|
+
|
|
253
|
+
## 与 LCU 的关系
|
|
254
|
+
|
|
255
|
+
[LCU](https://github.com/amontlabs/lcu)(MIT,Amont Labs)是证明这条路可行的项目:*Codex computer use, decoupled from the app*。它定位同一个运行时并把它作为 MCP 暴露出来。
|
|
256
|
+
|
|
257
|
+
**本插件自己做了这件事,不再安装或调用 LCU。** 有意义的差别:
|
|
258
|
+
|
|
259
|
+
- **不需要第二个解释器。** LCU 的启动器是 Python,要求 3.12+;这里是 TypeScript,唯一的要求就是应用本身。attach 因此快了大约一个数量级。
|
|
260
|
+
- **没有监督者进程,也不需要 Apple Events。** 回合清理走应用自己的 IPC,而不是一个去 shell 出签名客户端的监督者。
|
|
261
|
+
- **在失败那一刻给出诊断。** 应用在加载时就解析并报告,每个 attach、启动和批准决定都是日志里的一行。
|
|
262
|
+
|
|
263
|
+
## 配套工具
|
|
264
|
+
|
|
265
|
+
`scripts/` 还带了两个给姊妹 Codex 子代理 bundle 的工具,因为同一个 profile 通常两个都要:
|
|
266
|
+
|
|
267
|
+
- **`update-codex.mjs`** —— 把 profile 的 `@openai/codex` 保持在仍能通过三道闸(握手、协议 schema 断言、真实回合)的最新版本,失败时自动回滚。已发布的 `@deepseek-ai/dsh-subagent-codex` 钉在 `0.153.4`,它并不服务当前所有 ChatGPT 账号模型;这个脚本通过 profile 级、限定范围的 pnpm override 把它顶上去。`node scripts/update-codex.mjs --help` 里有 `--check`、`--verify-only`、`--to` 和 `--rollback`。
|
|
268
|
+
- **`codex-baseline.json`** —— 最后一次三道闸全过的版本。
|
|
269
|
+
|
|
270
|
+
## 已知限制与未做的工作
|
|
271
|
+
|
|
272
|
+
- **被委派的子代理无法被询问批准。** DSH 只接受来自活跃 runtime root 的人的回答,所以子代理的批准会 fail closed。子代理可以做不需要批准的只读工作;需要批准的事必须从顶层会话驱动。
|
|
273
|
+
- **按应用的 Stop 会被"请求它的那个回合"释放。** `computer_use_stop`(以及在宿主应用的"正在使用你的电脑"横幅上按 Esc)会请求运行时**在当前回合内**停用某一个应用。插件通过应用自己的 IPC 执行宿主应用的 turn-ended 清理,所以**下一个回合可以重新使用那个应用**;不带参数的 `computer_use_stop` 会报告当前持有哪些。如果某应用后续每个回合仍被拒绝,说明那次清理失败了,日志会写明。**常规使用 —— 截图、点击、输入、浏览器标签页 —— 不受影响**;插件的工具描述也这么写,所以模型不会自行去停用某个应用。
|
|
274
|
+
- **`js` 沙箱不能写文件。** 每一次写入都以 `EPERM` 失败,**包括临时目录**,所以截图无法从 `js` 里保存。图片改为以附件形式交给 harness,每张存下来的图都会在工具结果里带上它的宿主文件系统路径;把它复制进工作区是一行 `bash`(`install -m 644 '<path>' <target>` —— 存储对象的模式是 400)。插件在工具结果和注入的指令里都写了这两点。
|
|
275
|
+
- **macOS 权限属于 harness,不属于 OpenAI 助手。** macOS 把权限请求归给**负责进程**,而 harness 派生的一切都归给 harness 自己。所以屏幕录制和辅助功能必须给 **DeepSeek Harness** 打开;只给 ChatGPT 或 "Codex Computer Use" 是不够的,而且 macOS **不会自己弹窗**询问缺失的那些。
|
|
276
|
+
- **没有 Codex 账号时的浏览器面还没接。** 驱动页面靠桌面应用注册的 native messaging host。那台"没有 Codex 应用"的机器上需要的 relay(强制扩展的 agent-request header)还没有实现。
|
|
277
|
+
- **应用在会话运行期间更新,只做了警告。** 运行时是从应用更新时会替换的文件里执行的,所以长会话可能同时跑两代文件。应用更新后请重启 harness。
|
|
278
|
+
- **每个 Agent 一个连接。** 运行时的 JavaScript 会话是按连接隔离的,它的批准绑定到真实的会话和回合,所以让多个 Agent 共用一个连接会让两者交错。
|
|
279
|
+
- **未在 Linux / Windows 上验证。** 见[前置条件](#前置条件)。
|
|
293
280
|
|
|
294
281
|
## 开发
|
|
295
282
|
|
|
@@ -300,11 +287,10 @@ npm run build # 产出 lib/
|
|
|
300
287
|
npm test # node --test,无需构建
|
|
301
288
|
```
|
|
302
289
|
|
|
303
|
-
|
|
304
|
-
CI 上也能通过。批准、投影、守卫三组是纯函数测试,永远会跑。
|
|
290
|
+
连接与启动器测试会与**真实的**已安装计算机使用运行时通信,应用不存在时自动跳过,所以 `npm test` 在本地有意义、在 CI 上也能过。批准、投影、守卫和 wrapper 那几套是纯函数,始终会跑。
|
|
305
291
|
|
|
306
|
-
|
|
292
|
+
插件代码和配置**都不被 harness 热更新**:运行中的进程保留它加载的那个模块。改完要重新构建并重启。
|
|
307
293
|
|
|
308
294
|
## 许可
|
|
309
295
|
|
|
310
|
-
MIT
|
|
296
|
+
MIT。**没有内联任何东西**:原生那一半是应用自己的,本包没有运行时依赖。ChatGPT 应用及其指令仍按其自身条款,来自你本机的安装。
|