@whaletalk/dsh-codex-collab 0.1.4 → 0.1.6
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/README.md +150 -29
- package/codex-plugin/plugins/dsh/.codex-plugin/plugin.json +1 -1
- package/cordis.patch.yml +39 -14
- package/harness/dsh-bridge.mjs +7 -5
- package/harness/session-target.mjs +14 -0
- package/mcp/dsh-mcp.mjs +1 -1
- package/package.json +2 -1
- package/templates/codex-mcp/README.md +84 -0
- package/templates/codex-mcp/cordis.patch.yml +46 -0
- package/templates/codex-mcp/package.json +12 -0
package/README.md
CHANGED
|
@@ -6,38 +6,51 @@
|
|
|
6
6
|
|
|
7
7
|
**Codex 主导,DeepSeek 执行,Codex 验收。** 一个插件体系,让 ChatGPT(Codex)像项目经理一样把编码任务派给本机 DeepSeek Harness 的编码子代理,实时回传结果,读文件验收,并由独立评审员实际跑通构建/测试后出具评审报告。
|
|
8
8
|
|
|
9
|
+
派活有**两种目标**,必须显式选一种——"接进用户正在用的那条对话"和"新建一个干活的子代理"是两件事:
|
|
10
|
+
|
|
11
|
+
| 目标 | 什么时候用 | 效果 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `worker`(默认) | 全新任务、独立项目目录 | 在 `--cwd` 里新建持久编码子代理(多工作区 · 多 lane · fast/pro 模型) |
|
|
14
|
+
| `session` | 用户已经在某条对话里推进这件事,你只是替他继续 | 指令**投递进那条会话**,沿用它的上下文,**不新建会话/工作区** |
|
|
15
|
+
|
|
9
16
|
```
|
|
10
17
|
Codex 工作区 (ChatGPT)
|
|
11
|
-
│
|
|
18
|
+
│ dsh_task / dsh_sessions / dsh_review / dsh_read_file
|
|
12
19
|
▼
|
|
13
|
-
DeepSeek Harness 网关
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
DeepSeek Harness 网关 /api/dsh-bridge
|
|
21
|
+
├─ worker 目标 ──→ 新建编码子代理(多工作区 · 多 lane · fast/pro)─┐
|
|
22
|
+
├─ session 目标 ─→ 已有那条会话(prompt 投递,queue / steer) ──┤
|
|
23
|
+
└─ 独立评审子代理(必须跑通构建/测试)───────────────────────────┴→ 共享目录 / 原会话
|
|
17
24
|
```
|
|
18
25
|
|
|
19
26
|
```mermaid
|
|
20
27
|
flowchart LR
|
|
21
28
|
subgraph Codex["Codex (ChatGPT) — 项目经理 + 验收员"]
|
|
22
|
-
A1[
|
|
23
|
-
A2[
|
|
24
|
-
A3[
|
|
29
|
+
A1[dsh_sessions 找会话]
|
|
30
|
+
A2[dsh_task 派活]
|
|
31
|
+
A3[dsh_review 评审]
|
|
32
|
+
A4[dsh_read_file 验收]
|
|
25
33
|
end
|
|
26
34
|
subgraph DSH["DeepSeek Harness"]
|
|
27
35
|
B1[HTTP 网关 /api/dsh-bridge]
|
|
28
|
-
B2[
|
|
29
|
-
B3[
|
|
36
|
+
B2[worker 目标<br/>新建编码子代理]
|
|
37
|
+
B3[session 目标<br/>投递进已有会话]
|
|
38
|
+
B4[独立评审子代理<br/>必须跑通构建/测试]
|
|
30
39
|
end
|
|
31
|
-
subgraph FS["
|
|
32
|
-
C1[
|
|
40
|
+
subgraph FS["工作现场"]
|
|
41
|
+
C1[共享工作目录]
|
|
42
|
+
C2[已有会话与其上下文]
|
|
33
43
|
end
|
|
34
|
-
A1
|
|
44
|
+
A1 -->|只读查找| B1
|
|
35
45
|
A2 --> B1
|
|
36
|
-
A3 -->
|
|
46
|
+
A3 --> B1
|
|
47
|
+
A4 --> C1
|
|
37
48
|
B1 --> B2 --> C1
|
|
38
|
-
B1 --> B3 -->
|
|
49
|
+
B1 --> B3 --> C2
|
|
50
|
+
B1 --> B4 --> C1
|
|
39
51
|
B2 -.实时回传汇报.-> B1
|
|
40
|
-
|
|
52
|
+
B3 -.实时回传汇报.-> B1
|
|
53
|
+
B1 -.任务结果.-> A2
|
|
41
54
|
```
|
|
42
55
|
|
|
43
56
|
---
|
|
@@ -48,7 +61,10 @@ flowchart LR
|
|
|
48
61
|
|---|---|---|
|
|
49
62
|
| `codex-plugin/` | Codex 本地市场插件包 | 标准 marketplace 结构(`.agents/plugins/marketplace.json` + 插件目录),含协作技能 `dsh-collab` |
|
|
50
63
|
| `mcp/dsh-mcp.mjs` | MCP 服务器(零依赖 Node) | stdio 传输(Codex `[mcp_servers]` 直连)+ Streamable HTTP 传输(`--http`,供 Secure MCP Tunnel / cloudflared 桥接 ChatGPT);6 个工具:`dsh_task` / `dsh_sessions` / `dsh_task_status` / `dsh_task_cancel` / `dsh_review` / `dsh_read_file` |
|
|
51
|
-
| `harness/dsh-bridge.mjs` | DeepSeek Harness 宿主组合网关插件 | 提供 HTTP 网关(`/api/dsh-bridge/*`)、WS
|
|
64
|
+
| `harness/dsh-bridge.mjs` | DeepSeek Harness 宿主组合网关插件 | 提供 HTTP 网关(`/api/dsh-bridge/*`)、WS 推送、原生工具;管理编码/评审子代理,并把任务投递进已有会话。**必须装进 harness 宿主组合**(见下文) |
|
|
65
|
+
| `harness/session-target.mjs` | 会话目标的纯决策层 | 目标判定、候选挑选(0/1/N 不猜)、RemoteError→稳定 reason、基线切分、汇报选文。无 DSH import,可单测;`dsh-bridge.mjs` 依赖它,**两个文件必须一起部署** |
|
|
66
|
+
| `test/` | 25 项 Node 测试 | `session-target` 纯函数单测 + `dsh-bridge` 路由级回归测试(假 DSH 运行时加载真实 bridge)。CI 每次都跑 |
|
|
67
|
+
| `templates/codex-mcp/` | 反向通道配置模板 | 把 Codex CLI 当 MCP 服务端挂进 DSH(得到 `mcp__codex__*` 工具),让 DSH 侧也能调用 Codex。见下文「反向通道」 |
|
|
52
68
|
| `cordis.patch.yml` | bundle patch | 把网关插件挂进 DSH 宿主组合;`dsh.bundle.patch` 指向它,所以 `dsh plugin add` 能自动 reconcile |
|
|
53
69
|
|
|
54
70
|
## 功能
|
|
@@ -69,7 +85,8 @@ flowchart LR
|
|
|
69
85
|
|
|
70
86
|
```sh
|
|
71
87
|
# 1) 网关插件装进 harness profile(自动 reconcile bundles,无需手工复制文件)
|
|
72
|
-
dsh plugin --profile
|
|
88
|
+
dsh plugin --profile desktop add @whaletalk/dsh-codex-collab
|
|
89
|
+
# ↑ DSH 客户端用的就是 desktop profile;纯 Web 部署换成 --profile web
|
|
73
90
|
|
|
74
91
|
# 2) MCP 服务器交给 Codex
|
|
75
92
|
npm install -g @whaletalk/dsh-codex-collab
|
|
@@ -81,9 +98,27 @@ npm install -g @whaletalk/dsh-codex-collab
|
|
|
81
98
|
[mcp_servers.dsh]
|
|
82
99
|
command = 'dsh-mcp'
|
|
83
100
|
startup_timeout_sec = 120
|
|
101
|
+
env = { DSH_BRIDGE_URL = "http://127.0.0.1:3080" } # 见下方"端口";不写则默认 3080
|
|
84
102
|
```
|
|
85
103
|
|
|
86
|
-
|
|
104
|
+
**端口**:网关挂在 profile 的 `webServer` 上,地址取决于 profile 怎么起的——独立 harness 通常 `3080`,而 DSH 客户端里就是 **GUI 自己的端口**(例如 `http://127.0.0.1:19387`)。MCP 与 CLI 都用 `DSH_BRIDGE_URL` 找网关,指错只会看到连接失败或 401。验证:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
curl http://127.0.0.1:3080/api/dsh-bridge/status
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
装完**必须重启 profile 进程**——`dsh plugin add` 改的是 bundles 列表与 node_modules,HMR 不监控这两处;**替换包内容也不会热重载**(ESM 缓存),只有重启进程才加载新版本。
|
|
111
|
+
|
|
112
|
+
### 升级到新版本(有坑,照做)
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
# 1) 带精确版本号安装:不带版本号时 lockfile 会把旧版按回来
|
|
116
|
+
dsh plugin --profile desktop add @whaletalk/dsh-codex-collab@<新版本>
|
|
117
|
+
# 2) 重启 profile 进程:不重启不会加载新的 JS 模块
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **不要用"卸载 → 重装"**:卸载会把这个包从 `dsh.profile.bundles` 移除;重装后若没被重新选中,插件根本不会挂载(表现:所有 `/api/dsh-bridge/*` 都落到网关鉴权层返回 **401**)。真遇到了就在客户端插件页把它的开关打开。
|
|
121
|
+
- **刚发布的版本约 24 小时内装不上**:profile 的 pnpm 带 `minimumReleaseAge` 供应链冷却(`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`)。管理器会把它写进 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 才放行;手工安装可临时加 `--config.minimumReleaseAge=0`。
|
|
87
122
|
|
|
88
123
|
> 本包只提供 bundle patch,不含 profile 之外的强依赖:`@deepseek-ai/dsh-tools` 是 optional peer,由 DSH 安装树在运行时提供。因此 `npm install` 不会去 registry 拉它,也不会因它缺失而报错。
|
|
89
124
|
|
|
@@ -92,21 +127,23 @@ startup_timeout_sec = 120
|
|
|
92
127
|
|
|
93
128
|
### 1. DeepSeek Harness 网关(必需,后端)
|
|
94
129
|
|
|
95
|
-
把
|
|
130
|
+
把 **`harness/` 整个目录**复制到你的 dsh profile 目录(例如 `$DSH_HOME/profiles/web/` 下的 `harness/`),并在该目录的 `cordis.patch.yml` 中追加:
|
|
96
131
|
|
|
97
132
|
```yaml
|
|
98
133
|
- insert:
|
|
99
134
|
- id: dsh-bridge
|
|
100
|
-
name: './dsh-bridge.mjs'
|
|
135
|
+
name: './harness/dsh-bridge.mjs'
|
|
101
136
|
```
|
|
102
137
|
|
|
138
|
+
> `dsh-bridge.mjs` 会 `import './session-target.mjs'`——**两个文件必须在一起**,只复制前者会直接加载失败。
|
|
139
|
+
|
|
103
140
|
重启 harness。默认共享工作目录为 `D:\Harness`,可用环境变量 `DSH_BRIDGE_WORKSPACE` 覆盖。验证:
|
|
104
141
|
|
|
105
142
|
```
|
|
106
143
|
GET http://127.0.0.1:3080/api/dsh-bridge/status
|
|
107
144
|
```
|
|
108
145
|
|
|
109
|
-
>
|
|
146
|
+
> 手工方式必须用相对路径(文件就在 profile 目录里);npm 方式则用包子路径 `@whaletalk/dsh-codex-collab/bridge`,两者不要混用。
|
|
110
147
|
|
|
111
148
|
### 2. Codex 侧(二选一或都用)
|
|
112
149
|
|
|
@@ -158,12 +195,27 @@ tunnel-client run --profile dsh
|
|
|
158
195
|
|
|
159
196
|
**Codex 工作区会话**(MCP 工具就绪后):
|
|
160
197
|
|
|
161
|
-
>
|
|
198
|
+
> **新建子代理**:用 dsh_task 让 DeepSeek 生成一个随机密码 CLI + 测试 + README,放在当前项目目录,完成后你用 dsh_read_file 验收,再 dsh_review 评审
|
|
199
|
+
>
|
|
200
|
+
> **接进原对话**:先用 dsh_sessions 按标题找到用户正在用的那条会话,再用 dsh_task 带 sessionId 把下一步指令投递进去(不要新建会话)
|
|
201
|
+
|
|
202
|
+
**MCP 工具**(Codex 侧首选,工具名带 `mcp__dsh__` 前缀):
|
|
203
|
+
|
|
204
|
+
```jsonc
|
|
205
|
+
// 1) 找到会话(只读)
|
|
206
|
+
dsh_sessions({ "query": "按文档启动OKX策略实验计划" })
|
|
207
|
+
// → { "items": [{ "sessionId": "session-8d481ad9-…", "snippet": "按文档启动OKX策略实验计划",
|
|
208
|
+
// "agentAvailable": true }], "matchedBy": "list" }
|
|
209
|
+
|
|
210
|
+
// 2) 投递进那条会话
|
|
211
|
+
dsh_task({ "instruction": "继续推进第 3 步…", "sessionId": "session-8d481ad9-…" })
|
|
212
|
+
// deliver 可选 "queue"(默认,排队)或 "steer"(插入当前回合)
|
|
213
|
+
```
|
|
162
214
|
|
|
163
215
|
**命令行**(npm 安装后直接用;源码方式用 `node <脚本路径>`):
|
|
164
216
|
|
|
165
217
|
```bash
|
|
166
|
-
#
|
|
218
|
+
# 新建子代理(默认行为)
|
|
167
219
|
dsh-task --in "<指令>" --cwd "<目录>" [--lane backend] [--model fast|pro] [--commit]
|
|
168
220
|
|
|
169
221
|
# 接进已有会话:先找,再派
|
|
@@ -180,19 +232,74 @@ dsh-task --list | --status <taskId> | --cancel <taskId> [--force]
|
|
|
180
232
|
```bash
|
|
181
233
|
# 1) 找到(只读,不创建任何东西)
|
|
182
234
|
dsh-task --sessions "按文档启动OKX策略实验计划"
|
|
183
|
-
# → {"items":[{"sessionId":"session-8d481ad9
|
|
235
|
+
# → {"items":[{"sessionId":"session-8d481ad9-…","snippet":"按文档启动OKX策略实验计划",
|
|
236
|
+
# "agentAvailable":true}],"total":1,"matchedBy":"list"}
|
|
184
237
|
|
|
185
|
-
# 2)
|
|
186
|
-
dsh-task --in "<继续推进的指令>" --session "session-8d481ad9
|
|
238
|
+
# 2) 接通(投递进那条会话;这一轮的助手回复作为汇报返回,只回报本轮新增文本)
|
|
239
|
+
dsh-task --in "<继续推进的指令>" --session "session-8d481ad9-…"
|
|
187
240
|
```
|
|
188
241
|
|
|
242
|
+
`matchedBy` 说明"找到"走了哪条路:`search` = 会话搜索索引;`list` = 索引被部署禁用时退回遍历列表 + 标题投影(都不激活 Agent)。
|
|
243
|
+
|
|
189
244
|
失败一律返回稳定 `reason` 且**不创建任何会话/工作区**:`session-query-empty`(没命中)、`session-ambiguous`(多条命中,附 `candidates`)、`session-not-found`、`session-busy`、`session-writer-held`(会话被别的写入方占用)、`session-archived`、`session-not-accepted`、`session-timeout`、`session-cancel-refused`(取消会话目标默认被拒,需 `--force`)。
|
|
190
245
|
|
|
246
|
+
## 反向通道:让 DSH 调用 Codex
|
|
247
|
+
|
|
248
|
+
上面的链路都是 **Codex → DSH**。反过来的 **DSH → Codex** 也能做,但要分清两种含义:
|
|
249
|
+
|
|
250
|
+
| 含义 | 可行性 |
|
|
251
|
+
|---|---|
|
|
252
|
+
| 让 DSH 调用 Codex(把 Codex 当执行器/评审员) | ✅ 两端原生支持,见下 |
|
|
253
|
+
| 让 DSH 往你**正开着的某条 ChatGPT/Codex 对话**里插一条消息、就地开一轮 | ❌ 做不到 |
|
|
254
|
+
|
|
255
|
+
第二种做不到的原因:现有链路是 Codex 主动(它启动 `dsh-mcp` 子进程调工具),而 MCP 是
|
|
256
|
+
**客户端发起**的协议——服务端无法命令客户端开一轮。服务端→客户端只有 `notifications/*`
|
|
257
|
+
(纯通知,不触发回合)、`sampling/createMessage`、`elicitation/create`(取决于客户端是否
|
|
258
|
+
实现,且不等于"在对话里跑工具");ChatGPT 侧同样没有公开 API 能向用户会话投消息。
|
|
259
|
+
|
|
260
|
+
### 方案 A:反向通道已内置在本包里(推荐)
|
|
261
|
+
|
|
262
|
+
**装本包就同时得到两个方向**——`cordis.patch.yml` 里插了两行:`dsh-bridge`(Codex → DSH 派活)与 `codex-mcp`(DSH → Codex 反向调用)。原理是两端各有一个原生件:
|
|
263
|
+
|
|
264
|
+
- DSH 侧 `@deepseek-ai/dsh-mcp-client`(harness 自带):连外部 MCP 服务器,并把工具以 `mcp__<serverName>__<tool>` 注册进 `ctx.tools`;
|
|
265
|
+
- Codex 侧 `codex mcp-server`(stdio):把 Codex 暴露成 MCP 服务端。
|
|
266
|
+
|
|
267
|
+
内置的那一行等价于:
|
|
268
|
+
|
|
269
|
+
```yaml
|
|
270
|
+
- insert:
|
|
271
|
+
- id: codex-mcp
|
|
272
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
273
|
+
config:
|
|
274
|
+
serverName: codex
|
|
275
|
+
transport: stdio
|
|
276
|
+
command: codex # 跨平台:SDK 用 cross-spawn,Windows 的 .cmd 垫片也能解析
|
|
277
|
+
args: ['mcp-server']
|
|
278
|
+
failOnStartupError: false # 没装/没登录 Codex 只是没这批工具,不会拖垮 harness
|
|
279
|
+
toolCallTimeoutMs: 600000 # 默认只有 60s,Codex 跑一轮常常不够
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- 前提:本机装了 Codex CLI 并已登录(`codex --version` / `codex mcp-server --help` 能跑)。
|
|
283
|
+
- **不需要反向通道**?在 profile 的 `cordis.patch.yml` 里覆盖一行即可关掉:`- id: codex-mcp` + `disabled: true`。
|
|
284
|
+
- 想单独用反向通道(不装本插件主体)可以用独立模板 [`templates/codex-mcp/`](templates/codex-mcp/README.md)。
|
|
285
|
+
- 装完需**重启 profile 进程**;连接失败只是这批工具不出现(日志里有原因)。
|
|
286
|
+
- 排查:若 `codex` 不在 DSH 进程的 PATH 上,把 `command` 换成 `node` + `args: ['<...>/@openai/codex/bin/codex.js', 'mcp-server']`,或直接指向平台 `codex.exe`。
|
|
287
|
+
|
|
288
|
+
### 方案 B:worker 里直接跑 `codex exec`
|
|
289
|
+
|
|
290
|
+
```powershell
|
|
291
|
+
codex exec "把刚才的 diff 审一遍,只回结论" --cd E:\proj
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
一次性、零协议改动;代价是每次都是新会话(无跨轮记忆),且需要执行 shell 的权限。
|
|
295
|
+
|
|
296
|
+
> 注意:插件自带的 `dsh_collab_send` / `dsh_collab_review` 是 **DSH 侧入口**,只回到同一个网关,**不经过 Codex**。要"叫 Codex 干活"用上面 A/B;要"让 Codex 那条对话继续",只能等 Codex 自己下一轮(`dsh_task --wait` 或 `dsh_task_status`)。
|
|
297
|
+
|
|
191
298
|
## 环境变量
|
|
192
299
|
|
|
193
300
|
| 变量 | 默认 | 说明 |
|
|
194
301
|
|---|---|---|
|
|
195
|
-
| `DSH_BRIDGE_URL` | `http://127.0.0.1:3080` |
|
|
302
|
+
| `DSH_BRIDGE_URL` | `http://127.0.0.1:3080` | 网关地址。**必须指向插件实际挂载的那个 profile 的端口**(DSH 客户端里就是 GUI 端口,例如 19387) |
|
|
196
303
|
| `DSH_BRIDGE_WORKSPACE` | `D:\Harness` | 网关默认共享工作目录 |
|
|
197
304
|
| `DSH_BRIDGE_ROOT` | `D:\Harness` | `dsh_read_file` 相对路径基准 |
|
|
198
305
|
| `DSH_BRIDGE_HISTORY` | 多级降级 | 协作历史文件位置(默认 D 盘 → 用户目录 → 临时目录逐级降级) |
|
|
@@ -211,12 +318,26 @@ dsh-task --in "<继续推进的指令>" --session "session-8d481ad9-..."
|
|
|
211
318
|
- **评审不支持会话目标**:评审员必须与编码会话隔离,"独立评审"才成立
|
|
212
319
|
- session 目标不使用 `cwd`/`lane`/`model`/`--commit`(会话自带这些语义),传了会被判 `invalid-target`
|
|
213
320
|
- 网关重启会丢失内存中的任务表(含 session 观察器),这是既有设计
|
|
321
|
+
- **替换包内容不会热重载**:升级后必须重启 profile 进程,否则跑的还是旧 JS 模块(HMR 只重挂组合,不复用新代码)
|
|
322
|
+
- **组合包被取消选中 = 插件完全不挂载**:此时所有 `/api/dsh-bridge/*` 都落到网关鉴权层返回 401(不是 404,容易误判成没装)
|
|
214
323
|
- **DSH profile 的 pnpm 供应链冷却**:新发布的版本约 24 小时内会被 `minimumReleaseAge` 拒绝安装(`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`);管理器会把它写进 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 才放行。刚发版就装不上属正常,不是包的问题
|
|
215
|
-
-
|
|
324
|
+
- 升级已装组合包:客户端插件页没有升级入口,官方姿势是**卸载 → 重装**;但卸载会把该包从 `dsh.profile.bundles` 移除,重装后**要确认它被重新选中**,否则插件不会挂载(表现为所有 `/api/dsh-bridge/*` 都落到网关鉴权层返回 401)。更省事的做法是直接 `dsh plugin add <包>@<版本>`,见上文「升级到新版本」
|
|
216
325
|
- Codex Desktop Windows 本地市场技能注入 bug(见上,MCP 通道不受影响)
|
|
217
326
|
- 动态插件环境无 `AbortSignal`,冷恢复失败时自动降级为一次性执行;宿主组合持久化版无此问题
|
|
218
327
|
- ChatGPT Plus 写入型 MCP 的开放度取决于 OpenAI 产品策略
|
|
219
328
|
|
|
329
|
+
## 开发与测试
|
|
330
|
+
|
|
331
|
+
```sh
|
|
332
|
+
node test/session-target.test.mjs # 纯函数:目标判定 / 候选挑选 / 错误映射 / 基线切分 / 汇报选文
|
|
333
|
+
node test/dsh-bridge.test.mjs # 路由级:假 DSH 运行时 + 桩 peer,加载真实 bridge 跑真实路由
|
|
334
|
+
node --test test/*.test.mjs # 两个一起跑(CI 用的是这条)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
- 路由级测试会断言 **worker 分支确实调用了 `startContinuable`**、**session 分支只调用 `prompt` 且不建 owner/子代理**——这类"参数遮蔽/串线"缺陷只有在这一层才抓得到(v0.1.2~0.1.4 的 `deliver` 遮蔽 bug 就是它抓回来的)。
|
|
338
|
+
- CI(`.github/workflows/publish.yml`)每次 tag 都跑:语法检查 → 单测 → 包契约 → 版本号三处一致 → tarball 清单 → 干净目录安装 + CLI/MCP 冒烟 → 6 工具握手断言 → bridge 导出契约。**任何一步失败都不会发布**。
|
|
339
|
+
- 发布:`package.json` / `.codex-plugin/plugin.json` / `mcp/dsh-mcp.mjs` 的 `SERVER_VERSION` 三处同步改版本 → commit → 打 tag `vX.Y.Z` → 推 tag,CI 自动带 provenance 发布。
|
|
340
|
+
|
|
220
341
|
## License
|
|
221
342
|
|
|
222
343
|
MIT
|
package/cordis.patch.yml
CHANGED
|
@@ -1,20 +1,45 @@
|
|
|
1
|
-
# DSH Collab bundle layer
|
|
1
|
+
# DSH Collab bundle layer —— 双向协作:Codex → DSH 派活 + DSH → Codex 反向调用。
|
|
2
2
|
#
|
|
3
3
|
# 安装(npm):
|
|
4
|
-
# dsh plugin --profile
|
|
5
|
-
# `dsh plugin add` 会把本包加入 profile 的 dsh.profile.bundles,并自动套用本
|
|
6
|
-
# patch,无需手工复制 dsh-bridge.mjs。
|
|
4
|
+
# dsh plugin --profile desktop add @whaletalk/dsh-codex-collab
|
|
5
|
+
# `dsh plugin add` 会把本包加入 profile 的 dsh.profile.bundles,并自动套用本 patch。
|
|
7
6
|
#
|
|
8
|
-
#
|
|
9
|
-
# - id dsh-bridge —— 行 id。用户的 patch 层可用同 id 覆盖整段 config。
|
|
10
|
-
# - name用包子路径而非相对路径(对比 './dsh-bridge.mjs'):只有这样从 npm 安装
|
|
11
|
-
# 后解析器才能定位到包内文件,同时保持 profile 目录干净。
|
|
7
|
+
# 本 bundle 插两行:
|
|
12
8
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# profile
|
|
17
|
-
#
|
|
9
|
+
# 1) dsh-bridge —— 宿主网关插件(Codex → DSH 的派活通道)
|
|
10
|
+
# - id 行 id;用户的 patch 层可用同 id 覆盖整段 config。
|
|
11
|
+
# - name 用包子路径而非相对路径(对比 './dsh-bridge.mjs'):只有这样从 npm 安装
|
|
12
|
+
# 后解析器才能定位到包内文件,同时保持 profile 目录干净。
|
|
13
|
+
# 依赖的服务:webServer / agents / subagents / tools 四个注入,加上运行时取用的
|
|
14
|
+
# timer、sessionQuery、agentPresets、sessionPersistence、workspaceRegistry、llm。
|
|
15
|
+
# 其中 webServer 由 dsh-web-app 提供 —— 因此本 bundle 只能挂 web / desktop
|
|
16
|
+
# profile,headless profile 没有该服务会一直 pending。
|
|
17
|
+
# 本插件不使用 workflowEngine,所以 web GUI 默认禁用 workflow 引擎不影响它。
|
|
18
|
+
#
|
|
19
|
+
# 2) codex-mcp —— 反向通道(DSH → Codex)
|
|
20
|
+
# 把 Codex CLI 当 MCP 服务端挂进来:DSH 侧 agent 会拿到 mcp__codex__* 工具,
|
|
21
|
+
# 可以把活交回 Codex。两端都是现成的(DSH 自带 @deepseek-ai/dsh-mcp-client,
|
|
22
|
+
# Codex 自带 `codex mcp-server`),这里只是配置。
|
|
23
|
+
#
|
|
24
|
+
# - command 写裸名 `codex` 即可跨平台:MCP SDK 的 stdio 用 cross-spawn
|
|
25
|
+
# (shell:false)spawn,它在 Windows 上会正确解析 npm 的 .cmd 垫片。
|
|
26
|
+
# 若你的 codex 不在 PATH 上,改成 `node` + args 指向
|
|
27
|
+
# `...\@openai\codex\bin\codex.js`,或直接指向平台 codex.exe。
|
|
28
|
+
# - toolCallTimeoutMs 默认只有 60s,Codex 跑一轮读仓库常常不够。
|
|
29
|
+
# - failOnStartupError:false:没装/没登录 Codex 时只是这批工具不出现并记一条
|
|
30
|
+
# 日志,不会拖垮 harness。
|
|
31
|
+
# - 不需要反向通道?在 profile 的 cordis.patch.yml 里加一行覆盖即可关掉:
|
|
32
|
+
# - id: codex-mcp
|
|
33
|
+
# disabled: true
|
|
18
34
|
- insert:
|
|
19
35
|
- id: dsh-bridge
|
|
20
|
-
name: '@whaletalk/dsh-codex-collab/bridge'
|
|
36
|
+
name: '@whaletalk/dsh-codex-collab/bridge'
|
|
37
|
+
- id: codex-mcp
|
|
38
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
39
|
+
config:
|
|
40
|
+
serverName: codex
|
|
41
|
+
transport: stdio
|
|
42
|
+
command: codex
|
|
43
|
+
args: ['mcp-server']
|
|
44
|
+
failOnStartupError: false
|
|
45
|
+
toolCallTimeoutMs: 600000
|
package/harness/dsh-bridge.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
assistantTextOf,
|
|
27
27
|
baselineOf,
|
|
28
28
|
extractNewAssistantText,
|
|
29
|
+
pickSessionResult,
|
|
29
30
|
classifyTarget,
|
|
30
31
|
selectSession,
|
|
31
32
|
mapControllerError,
|
|
@@ -505,19 +506,20 @@ export async function apply(ctx) {
|
|
|
505
506
|
const finishSessionTask = (record, stopReason) => {
|
|
506
507
|
if (record.settled) return
|
|
507
508
|
void (async () => {
|
|
508
|
-
let
|
|
509
|
+
let extractedText = ''
|
|
509
510
|
if (sessionQuery !== undefined) {
|
|
510
511
|
try {
|
|
511
512
|
const snap = await sessionQuery.readSession(record.sessionId)
|
|
512
513
|
const events = snap !== undefined && snap !== null && Array.isArray(snap.events) ? snap.events : []
|
|
513
514
|
const extracted = extractNewAssistantText(events, record.baseline)
|
|
514
515
|
record.baseline = extracted.baseline
|
|
515
|
-
|
|
516
|
+
extractedText = extracted.text
|
|
516
517
|
} catch (e) {
|
|
517
518
|
console.error('[dsh-bridge] session result read failed:', errText(e).slice(0, 200))
|
|
518
519
|
}
|
|
519
520
|
}
|
|
520
|
-
|
|
521
|
+
// 重读的整轮文本优先;流式累计只在重读失败时兜底——两者相加会重复两遍。
|
|
522
|
+
settleSessionTask(record, 'done', { result: pickSessionResult(record.total, extractedText), stopReason })
|
|
521
523
|
})()
|
|
522
524
|
}
|
|
523
525
|
|
|
@@ -637,13 +639,13 @@ export async function apply(ctx) {
|
|
|
637
639
|
return { task, failed: null }
|
|
638
640
|
}
|
|
639
641
|
|
|
640
|
-
const enqueue = async (instruction, incomingHistory, target, modelAlias, kind,
|
|
642
|
+
const enqueue = async (instruction, incomingHistory, target, modelAlias, kind, deliveryMode, notes) => {
|
|
641
643
|
if (incomingHistory !== undefined) syncHistory(incomingHistory)
|
|
642
644
|
if (target !== undefined && target.kind === 'session') {
|
|
643
645
|
// session 目标:不注册工作区、不建 owner、不建子代理,失败也不降级为新建。
|
|
644
646
|
const dispatched = await dispatchToSession(
|
|
645
647
|
instruction, target,
|
|
646
|
-
|
|
648
|
+
deliveryMode === undefined ? 'queue' : deliveryMode,
|
|
647
649
|
Array.isArray(notes) ? notes : [],
|
|
648
650
|
incomingHistory,
|
|
649
651
|
)
|
|
@@ -77,6 +77,20 @@ export const extractNewAssistantText = (events, baseline) => {
|
|
|
77
77
|
return { text: fresh, total: text, baseline: { count: list.length, textLen: text.length } }
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* 收尾时选哪一份文本作为汇报。
|
|
82
|
+
*
|
|
83
|
+
* 以"派发后重读持久日志"得到的整轮文本为准——它是流式累计的超集;只有重读失败
|
|
84
|
+
* 或结果为空时,才退回 follow 累计的那份。**绝不能把两者相加**:那会让汇报重复
|
|
85
|
+
* 两遍(v0.1.4 的真实缺陷,在真实会话上实测出现)。
|
|
86
|
+
*/
|
|
87
|
+
export const pickSessionResult = (streamed, extracted) => {
|
|
88
|
+
const fromLog = typeof extracted === 'string' ? extracted.trim() : ''
|
|
89
|
+
if (fromLog !== '') return fromLog
|
|
90
|
+
const fromStream = typeof streamed === 'string' ? streamed.trim() : ''
|
|
91
|
+
return fromStream !== '' ? fromStream : '(无输出)'
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
/**
|
|
81
95
|
* 判定一次派发的目标。
|
|
82
96
|
*
|
package/mcp/dsh-mcp.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 客户端(Secure MCP Tunnel / cloudflared)桥接给 ChatGPT 等远程产品。
|
|
7
7
|
const GATEWAY = process.env.DSH_BRIDGE_URL || 'http://127.0.0.1:3080'
|
|
8
8
|
const SERVER_NAME = 'dsh-bridge'
|
|
9
|
-
const SERVER_VERSION = '0.1.
|
|
9
|
+
const SERVER_VERSION = '0.1.6'
|
|
10
10
|
const PROTOCOL_VERSION = '2024-11-05'
|
|
11
11
|
|
|
12
12
|
const log = (...args) => console.error('[dsh-mcp]', ...args)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whaletalk/dsh-codex-collab",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Codex × DeepSeek Harness 双向编码协作:Codex 主导派活与验收,DeepSeek Harness 编码子代理在共享工作目录执行并回传,独立评审子代理实际跑通构建/测试后出具报告。提供 DSH 宿主网关插件、零依赖 MCP 服务器与 Codex 技能三件套。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"harness/",
|
|
24
24
|
"mcp/",
|
|
25
25
|
"codex-plugin/",
|
|
26
|
+
"templates/",
|
|
26
27
|
"cordis.patch.yml",
|
|
27
28
|
"README.md",
|
|
28
29
|
"LICENSE",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# templates/codex-mcp —— 反向通道:让 DSH 调用 Codex
|
|
2
|
+
|
|
3
|
+
> **注意:主包已内置这一行。** 装 `@whaletalk/dsh-codex-collab` 时会同时挂上
|
|
4
|
+
> `dsh-bridge`(Codex → DSH 派活)和 `codex-mcp`(DSH → Codex),不需要再单独装。
|
|
5
|
+
> 本目录是**独立版本**:只想用反向通道、或想单独调参时用。关掉内置的那一行只需在
|
|
6
|
+
> profile 的 `cordis.patch.yml` 里覆盖 `- id: codex-mcp` + `disabled: true`。
|
|
7
|
+
|
|
8
|
+
把 **Codex CLI 当作 MCP 服务端**挂进 DeepSeek Harness,于是 DSH 侧的 agent(包括
|
|
9
|
+
本插件派出去的 worker)会拿到 `mcp__codex__*` 工具,可以把活**交回 Codex**。
|
|
10
|
+
|
|
11
|
+
- DSH 侧负责连接的是 harness 自带的 `@deepseek-ai/dsh-mcp-client`(把外部 MCP
|
|
12
|
+
服务器的工具注册进 `ctx.tools`,支持 `stdio` 与 `streamable-http`,带断线重连)。
|
|
13
|
+
- Codex 侧负责暴露的是 `codex mcp-server`(stdio)。
|
|
14
|
+
|
|
15
|
+
## 装法
|
|
16
|
+
|
|
17
|
+
**A. 并进你现有的 profile patch**(推荐,最少文件):把 `cordis.patch.yml` 里那行
|
|
18
|
+
`insert` 追加到 `~/.dsh/profiles/<profile>/cordis.patch.yml` 数组末尾。
|
|
19
|
+
|
|
20
|
+
**B. 当作独立 bundle 安装**:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
dsh plugin --profile desktop add "<本目录绝对路径>"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
两种方式都一样:**装完必须重启 profile 进程**(新增组合包不会热挂载),
|
|
27
|
+
重启后工具才会出现在列表里。
|
|
28
|
+
|
|
29
|
+
## 配置要点
|
|
30
|
+
|
|
31
|
+
| 字段 | 说明 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `serverName` | 工具命名空间,工具名形如 `mcp__codex__<tool>`;同时挂多个服务器时各自独立 |
|
|
34
|
+
| `transport` | `stdio`(本地进程)或 `streamable-http`(远端) |
|
|
35
|
+
| `command` / `args` / `env` / `cwd` | 仅 stdio:可执行文件、参数、追加环境变量、工作目录 |
|
|
36
|
+
| `failOnStartupError` | `false` = 连不上也照常启动 harness,只是没有这批工具 |
|
|
37
|
+
| `toolCallTimeoutMs` | 每次 `tools/call` 超时;**默认只有 60s**,Codex 跑一轮常常不够,建议 600000 |
|
|
38
|
+
|
|
39
|
+
### `command` 怎么写(Windows 也能用裸名)
|
|
40
|
+
|
|
41
|
+
**默认写裸名 `codex` 即可,跨平台。** DSH 的 MCP 客户端走官方 SDK 的
|
|
42
|
+
`StdioClientTransport`,它内部用 **cross-spawn**(`shell: false`)spawn——cross-spawn
|
|
43
|
+
在 Windows 上会正确解析 npm 的 `.cmd` 垫片。实测:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
cross-spawn('codex', ['--version']) → exit 0, "codex-cli 0.147.0"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
只有当 `codex` 不在 DSH 进程的 PATH 上(或你想完全绕开 npm 垫片)时才需要换写法:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
# 备选 1:node 跑垫片背后的 JS
|
|
53
|
+
command: node
|
|
54
|
+
args: ['C:\Users\<you>\AppData\Roaming\npm\node_modules\@openai\codex\bin\codex.js', 'mcp-server']
|
|
55
|
+
|
|
56
|
+
# 备选 2:直接指向平台二进制(路径随版本/架构略有不同)
|
|
57
|
+
command: 'C:\Users\<you>\AppData\Roaming\npm\node_modules\@openai\codex\node_modules\@openai\codex-win32-x64\vendor\x86_64-pc-windows-msvc\bin\codex.exe'
|
|
58
|
+
args: ['mcp-server']
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 验证
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
codex --version # 需要 CLI 已登录可用
|
|
65
|
+
codex mcp-server --help # 应打印 "Start Codex as an MCP server (stdio)"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
接好后在 DSH 侧让 agent 调一次 `mcp__codex__*`;工具没出现就去看 harness 日志
|
|
69
|
+
(连接失败会记录原因,且不会中止启动)。
|
|
70
|
+
|
|
71
|
+
## 能做什么、不能做什么
|
|
72
|
+
|
|
73
|
+
| | 可行性 |
|
|
74
|
+
|---|---|
|
|
75
|
+
| 让 DSH 调用 Codex(当执行器/评审员) | ✅ 本目录的做法 |
|
|
76
|
+
| 让 DSH 往你**正开着的某条 ChatGPT/Codex 对话**里插消息、就地开一轮 | ❌ 做不到 |
|
|
77
|
+
|
|
78
|
+
原因:现有链路是 Codex 主动(它启动 `dsh-mcp` 子进程调工具),MCP 是**客户端发起**
|
|
79
|
+
的协议,服务端无法命令客户端开一轮;服务端→客户端只有 `notifications/*`(不触发
|
|
80
|
+
回合)、`sampling/createMessage`、`elicitation/create`(取决于客户端实现,且不等于
|
|
81
|
+
"在对话里跑工具")。ChatGPT 侧同样没有公开 API 能向用户会话投消息。
|
|
82
|
+
|
|
83
|
+
> 另注:本插件自带的 `dsh_collab_send` / `dsh_collab_review` 是 **DSH 侧入口**,
|
|
84
|
+
> 它们只回到同一个网关,**不经过 Codex**。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 反向通道:把 Codex 挂成 DSH 的一个 MCP 服务端。
|
|
2
|
+
#
|
|
3
|
+
# 谁提供什么:
|
|
4
|
+
# - DSH 侧:@deepseek-ai/dsh-mcp-client(harness 自带)——连外部 MCP 服务器,
|
|
5
|
+
# 并把它们的工具以 mcp__<serverName>__<tool> 注册进 ctx.tools。
|
|
6
|
+
# - Codex 侧:codex mcp-server(stdio)——把 Codex 暴露成 MCP 服务端。
|
|
7
|
+
#
|
|
8
|
+
# 用法(二选一):
|
|
9
|
+
# A) 把下面这行 insert 追加到你的 profile cordis.patch.yml 数组末尾;
|
|
10
|
+
# B) 把本目录当作一个 bundle 安装(路径写绝对路径):
|
|
11
|
+
# dsh plugin --profile <profile> add <本目录绝对路径>
|
|
12
|
+
#
|
|
13
|
+
# 注意:主包 @whaletalk/dsh-codex-collab 已内置等价的一行;本文件是**独立版本**,
|
|
14
|
+
# 用于只想单独挂反向通道、或想单独调参的场景。
|
|
15
|
+
#
|
|
16
|
+
# 装完必须**重启 profile 进程**:新增组合包不会热挂载,只有重启后工具才会出现。
|
|
17
|
+
# 之后在 DSH 侧的工具列表里找 mcp__codex__*(例如 mcp__codex__codex)。
|
|
18
|
+
- insert:
|
|
19
|
+
- id: codex-mcp
|
|
20
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
21
|
+
config:
|
|
22
|
+
serverName: codex
|
|
23
|
+
transport: stdio
|
|
24
|
+
|
|
25
|
+
# 裸名 `codex` 跨平台可用:DSH 的 MCP 客户端用官方 SDK 的
|
|
26
|
+
# StdioClientTransport,内部是 cross-spawn(shell:false),在 Windows 上
|
|
27
|
+
# 也能正确解析 npm 的 .cmd 垫片(本机实测 exit 0)。
|
|
28
|
+
command: codex
|
|
29
|
+
args: ['mcp-server']
|
|
30
|
+
|
|
31
|
+
# 若 codex 不在 DSH 进程的 PATH 上,改成下面任一种:
|
|
32
|
+
# command: node
|
|
33
|
+
# args: ['C:\Users\<you>\AppData\Roaming\npm\node_modules\@openai\codex\bin\codex.js', 'mcp-server']
|
|
34
|
+
# 或直接指向平台二进制:
|
|
35
|
+
# command: 'C:\Users\<you>\AppData\Roaming\npm\node_modules\@openai\codex\node_modules\@openai\codex-win32-x64\vendor\x86_64-pc-windows-msvc\bin\codex.exe'
|
|
36
|
+
# args: ['mcp-server']
|
|
37
|
+
|
|
38
|
+
# 远端 Codex(有人把 mcp-server 包成 HTTP 时)用:
|
|
39
|
+
# transport: streamable-http
|
|
40
|
+
# url: http://127.0.0.1:3000/mcp
|
|
41
|
+
# headers: { Authorization: 'Bearer ...' }
|
|
42
|
+
|
|
43
|
+
# 启动失败不拖垮 harness:插件仍激活,只是不注册这批工具,日志里有原因。
|
|
44
|
+
failOnStartupError: false
|
|
45
|
+
# Codex 一轮可能跑很久(要读完仓库再答),给足超时;默认只有 60s。
|
|
46
|
+
toolCallTimeoutMs: 600000
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-codex-mcp-bridge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "把 Codex CLI 当作 MCP 服务端挂进 DeepSeek Harness:DSH 侧 agent 获得 mcp__codex__* 工具,可反向调用 Codex。",
|
|
7
|
+
"dsh": {
|
|
8
|
+
"bundle": {
|
|
9
|
+
"patch": "./cordis.patch.yml"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|