dsh-plugin-subagent-bridge 0.0.0-stage → 0.5.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dogtiti
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.
package/README.md CHANGED
@@ -1,3 +1,209 @@
1
- # Temporary Holding Version
1
+ # dsh-plugin-subagent-bridge
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
4
+ [![dsh](https://img.shields.io/badge/DSH-%5E0.2.1--0-2f6fed)](https://github.com/deepseek-ai/deepseek-harness)
5
+
6
+ 在 DeepSeek Harness 宿主内部提供**本机回环 MCP 端点**,让另一个智能体(如 Codex)把任务派给 DSH 会话并取回结果。
7
+
8
+ 插件运行在宿主进程内,直接使用公开的 `sessionController` 服务 —— 不需要读取 `~/.dsh/.credentials.yaml`,也不需要伪造 cookie。
9
+
10
+ ---
11
+
12
+ ## 工具
13
+
14
+ | 工具 | 作用 |
15
+ | --- | --- |
16
+ | `dsh_task` | **同步**派活:开会话、投递、阻塞到这一轮结束,只返回**最终回答**(最后一条 assistant 消息) |
17
+ | `dsh_spawn` | **异步**派活:立刻返回 `task_name` + `sessionId`,可同时开多个 worker 并行跑 |
18
+ | `dsh_followup` | 给已有 worker 再派一轮(复用它的上下文) |
19
+ | `dsh_close` | 关闭 worker,释放并发名额(不删除会话) |
20
+ | `dsh_list_workers` | 列出本桥开的 worker 及其实时状态 |
21
+ | `dsh_wait` | 一次等多批 worker:`targets` + `timeoutMs`,任一到达终态即返回 `{completed}` / `{errored}` / `"running"` / `"not_found"` |
22
+ | `dsh_dispatch` | 向指定会话投递一条用户消息并开始一轮;返回 `requestId` 与 `seqAtDispatch` |
23
+ | `dsh_read_result` | **长轮询**读进度:返回该会话在 `seqAtDispatch` 之后的**全部**助手文本(用于看过程;要结果用 `dsh_wait`) |
24
+ | `dsh_list_sessions` | 列出会话**元数据**(id、标题、工作目录、是否运行、最后活动时间) |
25
+
26
+ ## 反向:DSH → Codex
27
+
28
+ 插件同时给本部署的 Agent 注册了一个工具:
29
+
30
+ | 工具 | 参数 | 返回 |
31
+ | --- | --- | --- |
32
+ | `codex_dispatch` | `prompt`(必填)、`cwd`、`resume`(线程 id)、`sandbox` | `{ ok, text, threadId, exitCode, durationMs, error? }` |
33
+
34
+ 它执行 `codex exec [resume <id>] --json -o <临时文件>`,从事件流取 `thread.started`、从 `-o` 文件取最终消息;工具结果里会带 `[codex thread <id> · 12s]`,把那个 id 传给 `resume` 就能继续同一个 Codex 线程(实测能记住上一轮的回复)。
35
+
36
+ 注意:**`resume` 时 `sandbox` 与 `cwd` 不生效** —— `codex exec resume` 子命令本身不接受 `-s` / `-C`,线程沿用它原来的设置。
37
+
38
+ 边界(v0 硬约束):
39
+
40
+ - `dsh_list_sessions` **只返回元数据**,不含任何消息内容。
41
+ - `dsh_read_result` 只对**本桥派发过**的会话生效;从未派发过的会话一律拒绝。
42
+
43
+ ---
44
+
45
+ ## 安装
46
+
47
+ ```bash
48
+ # 本地目录安装
49
+ git clone https://github.com/Dogtiti/dsh-plugin-subagent-bridge
50
+ ```
51
+
52
+ Plugins → 添加 → 选该目录。
53
+
54
+ 启动后插件在 `127.0.0.1:47821` 监听,并写入端点文件(权限 0700/0600):
55
+
56
+ ```json
57
+ {
58
+ "url": "http://127.0.0.1:47821/mcp/<token>",
59
+ "port": 47821,
60
+ "token": "<token>",
61
+ "pid": 12345,
62
+ "startedAt": "2026-10-22T12:30:00.000Z"
63
+ }
64
+ ```
65
+
66
+ 路径:`~/.dsh/bridge/endpoint.json`
67
+
68
+ ## 接到 Codex
69
+
70
+ ```bash
71
+ codex mcp add dsh-bridge --url "$(node -p "require(process.env.HOME+'/.dsh/bridge/endpoint.json').url")"
72
+ ```
73
+
74
+ 验证:
75
+
76
+ ```bash
77
+ codex mcp list
78
+ codex mcp get dsh-bridge
79
+ ```
80
+
81
+ Codex 的桌面 App 与 CLI 共用 `~/.codex/config.toml`,所以两端都会看到这条。
82
+
83
+ > token 存在 URL 路径里(能力 URL),因此不需要在 Codex 侧配置环境变量 —— 桌面 App 从 Finder 启动时拿不到自定义环境变量。若你更想用 bearer,也可以 `--bearer-token-env-var` 指向环境变量,插件同时支持 `Authorization: Bearer <token>`。
84
+
85
+ 移除:
86
+
87
+ ```bash
88
+ codex mcp remove dsh-bridge
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 配置
94
+
95
+ ```yaml
96
+ - insert:
97
+ - id: subagent-bridge
98
+ name: 'dsh-plugin-subagent-bridge'
99
+ config:
100
+ port: 47821
101
+ ```
102
+
103
+ | 键 | 默认 | 说明 |
104
+ | --- | --- | --- |
105
+ | `enabled` | `true` | 总开关;关闭后不监听、不写端点文件 |
106
+ | `port` | `47821` | 回环监听端口;固定端口是为了让 Codex 侧那条 URL 在重启后依然有效 |
107
+ | `endpointFile` | `~/.dsh/bridge/endpoint.json` | 端点与令牌的落盘位置 |
108
+ | `maxResultChars` | `20000` | 单次 `dsh_read_result` 返回的最大字符数(超出保留尾部) |
109
+ | `taskTimeoutSeconds` | `300` | `dsh_task` 等待一轮结束的默认上限(客户端可用 `timeoutSeconds` 覆盖,最大 600) |
110
+ | `workersFile` | `~/.dsh/bridge/workers.json` | worker 注册表与重基线的落盘位置(0600) |
111
+ | `maxWorkers` | `4` | 同时打开的 worker 上限;**已完成但未 `dsh_close` 的也占名额**(与 Codex 一致) |
112
+ | `waitTimeoutMs` | `30000` | `dsh_wait` 的默认超时(1s ~ 1h,客户端可用 `timeoutMs` 覆盖) |
113
+ | `readWaitSeconds` | `25` | `dsh_read_result` 的默认长轮询时长(上限 55,客户端可用 `waitSeconds` 覆盖) |
114
+ | `codexBin` | `codex` | Codex 可执行文件;含 `/` 时按路径严格解析,找不到就报错而不回退 |
115
+ | `codexSandbox` | `workspace-write` | `codex_dispatch` 的默认 sandbox(`resume` 时无效) |
116
+ | `codexTimeoutMs` | `300000` | 单次 `codex_dispatch` 超时,超时先 SIGTERM 再 SIGKILL |
117
+
118
+ 端口被占用时插件不会静默换端口:端到端文件里会写入 `error` 字段,并在宿主日志打印 `subagent-bridge: could not start on port …`。
119
+
120
+ ---
121
+
122
+ ## 跨重启
123
+
124
+ worker 注册表(`task_name` / `sessionId` / 终态结果)和每轮的时间基线都落在 `~/.dsh/bridge/workers.json`:
125
+
126
+ - 派发、完成、关闭都会更新它;
127
+ - **重启后 `dsh_list_workers` 仍能看到之前的 worker**;
128
+ - 已经结束的 worker,`dsh_wait` 直接返回缓存里的最终回答(不必重新打开会话);
129
+ - 重启时还在跑的 worker,会话未打开时状态是 `offline`(结果未知,去 DSH 侧边栏打开那个会话即可查看);
130
+ - `dsh_followup` 会给 worker 开新一轮,同时**清掉**缓存的旧回答;
131
+ - 文件最多保留 200 条,超出时先淘汰最旧的 **已关闭** worker。
132
+
133
+ ## 安全模型
134
+
135
+ - 只监听 `127.0.0.1`,不对外暴露。
136
+ - 请求必须携带令牌:路径 `/mcp/<token>`,或 `Authorization: Bearer <token>`;比较使用定长时间比较。
137
+ - 令牌首次启动时生成并**持久化**(重启后复用),因此能力 URL 稳定。
138
+ - 能力等价于「向指定会话投递消息」:本机任意进程只要能读到端点文件就能派活。文件权限 0600、目录 0700;不要把它复制到共享位置。
139
+ - DSH 侧的权限策略不受影响:派发进去的消息仍按会话自身的 sandbox / 审批策略执行。
140
+
141
+ ---
142
+
143
+ ## 实现
144
+
145
+ ```
146
+ Codex / 任意 MCP 客户端
147
+ │ POST /mcp/<token> JSON-RPC(stateless,单次 JSON 响应)
148
+ ▼
149
+ dsh-plugin-subagent-bridge(宿主进程内)
150
+ · node:http 监听 127.0.0.1:47821
151
+ · initialize / tools/list / tools/call / ping
152
+ · sessionController.prompt({sessionId, requestId, mode, content})
153
+ · sessions.get(id).snapshotEvents() 读回助手文本
154
+ ```
155
+
156
+ 协议实现为 **streamable HTTP 的无状态子集**:每个 POST 返回一段 `application/json`,不做服务端推送,因此取结果用**长轮询**——`dsh_read_result` 在服务端等,客户端一次调用即可拿到答案;客户端断开时服务端立即停止等待。
157
+
158
+ ---
159
+
160
+ ## 配套 skill
161
+
162
+ `skills/` 下有两份可直接安装的 skill,把"什么时候该派活、怎么派、怎么收尾"写成模型能读的规则:
163
+
164
+ | Skill | 装给谁 | 作用 |
165
+ | --- | --- | --- |
166
+ | `deepseek-worker` | Codex | 让 Codex 把任务当 subagent 派给 DSH(`dsh_task`),一次调用等结果 |
167
+ | `delegate-to-codex` | DSH | 让 DSH 的 agent 在需要独立视角/复核时派给 Codex(`codex_dispatch`) |
168
+
169
+ ```bash
170
+ # Codex 侧
171
+ mkdir -p ~/.codex/skills && cp -R skills/deepseek-worker ~/.codex/skills/
172
+
173
+ # DSH 侧
174
+ mkdir -p ~/.dsh/skills && cp -R skills/delegate-to-codex ~/.dsh/skills/
175
+ ```
176
+
177
+ 两份都是标准 `SKILL.md`:YAML frontmatter(`name` + `description`)+ 正文。**触发靠 description**——模型平时只看到名字和描述,判断相关了才读正文,所以描述里写满了中英触发语和"不要用"的边界。改完立刻生效,不用重启:Codex 下一次会话、DSH 下一轮就能看到。
178
+
179
+ ---
180
+
181
+ ## 兼容性
182
+
183
+ - DSH `^0.2.1-0`(`engines.dsh`)。
184
+ - MCP 协议版本:`2025-06-18`、`2025-03-26`、`2024-11-05`(按客户端请求回显,未知版本回落到最新)。
185
+ - 依赖宿主公开服务 `sessionController`(`list` / `prompt`)与 `sessions.get()`;两者不可用时工具会返回明确错误。
186
+
187
+ ---
188
+
189
+ ## 开发
190
+
191
+ ```bash
192
+ node --test "test/*.test.mjs"
193
+ ```
194
+
195
+ 45 项:握手与版本协商、通知处理、工具清单、未知方法与未知工具、参数校验、元数据列表不含消息内容、派发调用形状、拒绝与错误透出、结果读取的时间基线、未派发会话的拒绝、长轮询(文本晚到即返回 / 超时 / 调用方取消)、真实 HTTP 往返(含 404/405/202 与 bearer 分支)、`codex exec` 事件解析、二进制解析、成功/失败/超时三种运行结果、resume 的参数分叉、工具结果的 lossless JSON 约束、**只回最终回答**(多步叙述被丢弃)、`dsh_wait` 的任一完成 / 超时 / 错误 / 未派发四种状态、`dsh_spawn` 立即返回与注册、`fork_turns=all` 走 fork、并发上限与 `dsh_close` 释放名额、`dsh_followup` 的重基线与最终回答切换、注册表落盘与重启后复原、冷会话下的缓存回答与 `offline` 状态、注册表 200 条上限的淘汰顺序。
196
+ `test/persistence.test.mjs` 单独覆盖持久化:文件权限 0600、缺失/损坏/畸形记录、spawn 返回时文件已落盘、完成即缓存、followup 清缓存、close 后重启仍为 closed、冷会话走缓存或 `offline`、并发写不串。
197
+
198
+ ---
199
+
200
+ ## 相关插件
201
+
202
+ | 插件 | 说明 |
203
+ | --- | --- |
204
+ | [dsh-plugin-composer-predict](https://github.com/Dogtiti/dsh-plugin-composer-predict) | 预测下一条指令并显示为占位文字,`Tab` 采纳 |
205
+ | [dsh-plugin-composer-history](https://github.com/Dogtiti/dsh-plugin-composer-history) | 输入框中按 `↑` / `↓` 回填历史消息 |
206
+
207
+ ## 许可
208
+
209
+ MIT © [Dogtiti](https://github.com/Dogtiti)
@@ -0,0 +1,5 @@
1
+ - insert:
2
+ - id: subagent-bridge
3
+ name: 'dsh-plugin-subagent-bridge'
4
+ config:
5
+ port: 47821
package/icon.svg ADDED
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" role="img" aria-label="Agent bridge">
2
+ <rect x="1" y="1" width="22" height="22" rx="6" fill="#2f6fed"/>
3
+ <path d="M5.4 12h4.2M14.4 12h4.2" fill="none" stroke="#ffffff" stroke-width="1.9" stroke-linecap="round"/>
4
+ <circle cx="12" cy="12" r="2.6" fill="none" stroke="#ffffff" stroke-width="1.9"/>
5
+ <circle cx="5.4" cy="12" r="1.5" fill="#ffffff"/>
6
+ <circle cx="18.6" cy="12" r="1.5" fill="#ffffff"/>
7
+ </svg>