dsh-plugin-subagent-bridge 0.6.1 → 0.6.3
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 +2 -2
- package/index.js +19 -4
- package/package.json +1 -1
- package/skills/deepseek-worker/SKILL.md +59 -4
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ codex mcp remove dsh-bridge
|
|
|
111
111
|
| `maxHops` | `1` | 跨工具委派层数上限(1 = 只允许一层,和 Codex / DSH 的默认 `maxDepth: 1` 一致) |
|
|
112
112
|
| `dispatchPerMinute` | `10` | 滚动窗口内允许的派发次数(spawn / task / dispatch / followup / codex_dispatch 合计) |
|
|
113
113
|
| `dispatchWindowMs` | `60000` | 上述窗口长度 |
|
|
114
|
-
| `maxWorkers` | `4` |
|
|
114
|
+
| `maxWorkers` | `4` | **整个 App 共享**的 worker 上限;已完成但未 `dsh_close` 的也占名额(与 Codex 一致),不按会话隔离 |
|
|
115
115
|
| `waitTimeoutMs` | `30000` | `dsh_wait` 的默认超时(1s ~ 1h,客户端可用 `timeoutMs` 覆盖) |
|
|
116
116
|
| `readWaitSeconds` | `25` | `dsh_read_result` 的默认长轮询时长(上限 55,客户端可用 `waitSeconds` 覆盖) |
|
|
117
117
|
| `codexBin` | `codex` | Codex 可执行文件;含 `/` 时按路径严格解析,找不到就报错而不回退 |
|
|
@@ -219,7 +219,7 @@ mkdir -p ~/.dsh/skills && cp -R skills/delegate-to-codex ~/.dsh/skills/
|
|
|
219
219
|
node --test "test/*.test.mjs"
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
53 项:握手与版本协商、通知处理、工具清单、未知方法与未知工具、参数校验、元数据列表不含消息内容、派发调用形状、拒绝与错误透出、结果读取的时间基线、未派发会话的拒绝、长轮询(文本晚到即返回 / 超时 / 调用方取消)、真实 HTTP 往返(含 404/405/202 与 bearer 分支)、`codex exec` 事件解析、二进制解析、成功/失败/超时三种运行结果、resume 的参数分叉、工具结果的 lossless JSON 约束、**只回最终回答**(多步叙述被丢弃)、`dsh_wait` 的任一完成 / 超时 / 错误 / 未派发四种状态、`dsh_spawn` 立即返回与注册、`fork_turns=all` 走 fork、并发上限与 `dsh_close` 释放名额、`dsh_followup` 的重基线与最终回答切换、注册表落盘与重启后复原、冷会话下的缓存回答与 `offline` 状态、注册表 200 条上限的淘汰顺序、`maxHops` 拒绝回派而放行首层、在飞链路阻塞嵌套派发、速率突发的拒绝与窗口过期、followup 计入同一预算、握手版本号取自 `package.json`(不再硬编码)、阻塞式 `dsh_task` 不占 spawn 名额(重启后也不占)。
|
|
223
223
|
`test/persistence.test.mjs` 单独覆盖持久化:文件权限 0600、缺失/损坏/畸形记录、spawn 返回时文件已落盘、完成即缓存、followup 清缓存、close 后重启仍为 closed、冷会话走缓存或 `offline`、并发写不串。
|
|
224
224
|
|
|
225
225
|
---
|
package/index.js
CHANGED
|
@@ -617,7 +617,7 @@ export function createApi(ctx, state, settings) {
|
|
|
617
617
|
)
|
|
618
618
|
state.dispatched.add(sessionId)
|
|
619
619
|
state.baseline.set(sessionId, before)
|
|
620
|
-
state.workers.set(taskName, { taskName, sessionId, status: 'open', createdAt: Date.now(), updatedAt: Date.now() })
|
|
620
|
+
state.workers.set(taskName, { taskName, sessionId, status: 'open', countsTowardLimit: true, createdAt: Date.now(), updatedAt: Date.now() })
|
|
621
621
|
await saveWorkers(settings, state)
|
|
622
622
|
return { task_name: taskName, sessionId, accepted: accepted?.accepted === true, fork_turns: forkTurns }
|
|
623
623
|
},
|
|
@@ -694,9 +694,18 @@ export function createApi(ctx, state, settings) {
|
|
|
694
694
|
)
|
|
695
695
|
state.dispatched.add(targetId)
|
|
696
696
|
state.baseline.set(targetId, before)
|
|
697
|
-
|
|
697
|
+
/* A blocking task is not a spawned worker: it is recorded for later lookup but
|
|
698
|
+
* must not consume one of the concurrency slots. */
|
|
699
|
+
const implicitName = `task_${targetId.slice('session-'.length, 'session-'.length + 8)}`
|
|
698
700
|
if (!state.workers.has(implicitName)) {
|
|
699
|
-
state.workers.set(implicitName, {
|
|
701
|
+
state.workers.set(implicitName, {
|
|
702
|
+
taskName: implicitName,
|
|
703
|
+
sessionId: targetId,
|
|
704
|
+
status: 'open',
|
|
705
|
+
countsTowardLimit: false,
|
|
706
|
+
createdAt: Date.now(),
|
|
707
|
+
updatedAt: Date.now(),
|
|
708
|
+
})
|
|
700
709
|
await saveWorkers(settings, state)
|
|
701
710
|
}
|
|
702
711
|
|
|
@@ -979,6 +988,8 @@ export async function loadWorkers(settings, state) {
|
|
|
979
988
|
taskName: entry.taskName,
|
|
980
989
|
sessionId: entry.sessionId,
|
|
981
990
|
status: entry.status === 'closed' ? 'closed' : 'open',
|
|
991
|
+
/* Blocking tasks resume as non-counting records, exactly as they were written. */
|
|
992
|
+
countsTowardLimit: entry.countsTowardLimit !== false,
|
|
982
993
|
createdAt: Number(entry.createdAt ?? Date.now()),
|
|
983
994
|
updatedAt: Number(entry.updatedAt ?? Date.now()),
|
|
984
995
|
...(entry.outcome === undefined ? {} : { outcome: entry.outcome }),
|
|
@@ -1064,7 +1075,11 @@ function resolveWorker(state, reference) {
|
|
|
1064
1075
|
*/
|
|
1065
1076
|
function assertCapacity(state, settings) {
|
|
1066
1077
|
let open = 0
|
|
1067
|
-
for (const record of state.workers.values())
|
|
1078
|
+
for (const record of state.workers.values()) {
|
|
1079
|
+
if (record.status === 'closed') continue
|
|
1080
|
+
if (record.countsTowardLimit === false) continue
|
|
1081
|
+
open += 1
|
|
1082
|
+
}
|
|
1068
1083
|
if (open >= settings.maxWorkers) {
|
|
1069
1084
|
throw new Error(
|
|
1070
1085
|
`worker limit reached (${settings.maxWorkers}); close one with dsh_close before spawning another`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subagent-bridge",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Subagent bridge: lets Codex delegate a task to DeepSeek Harness the way it would spawn a subagent (one call, waits for the answer), plus a codex_dispatch tool for the reverse direction.",
|
|
6
6
|
"keywords": [
|
|
@@ -9,7 +9,7 @@ DSH 是通过 `dsh-bridge` MCP 接上来的另一个编码代理。用法和"给
|
|
|
9
9
|
|
|
10
10
|
## 先确认工具在
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
工具列表里应当有:`dsh_task`、`dsh_spawn`、`dsh_wait`、`dsh_followup`、`dsh_close`、`dsh_list_workers`、`dsh_dispatch`、`dsh_read_result`、`dsh_list_sessions`。
|
|
13
13
|
|
|
14
14
|
- 没有 `dsh_task` → DSH 没启动或 MCP 未接入。直接告诉用户"需要先启动 DeepSeek Harness",**不要假装调用成功**。
|
|
15
15
|
- 调用报连接错误 → 报错误原文,然后自己把这件事做掉,不要反复重试。
|
|
@@ -38,6 +38,52 @@ dsh_task({
|
|
|
38
38
|
|
|
39
39
|
`dsh_task` **阻塞到 DSH 那一轮结束**再返回,不需要轮询。默认最多等 300 秒,更重的任务传 `timeoutSeconds`(上限 600)。
|
|
40
40
|
|
|
41
|
+
## 多件活:并行开 worker
|
|
42
|
+
|
|
43
|
+
互不依赖的子任务,**并行比串行快**,而且往往质量更好(每个 worker 有干净的上下文)。这是 Codex 自己多智能体策略的原话,照抄过来:
|
|
44
|
+
|
|
45
|
+
> 只要并行能省时间或提高质量,就应该把任务分出去(无论你是主 agent 还是子 agent)。
|
|
46
|
+
|
|
47
|
+
流程:
|
|
48
|
+
|
|
49
|
+
```jsonc
|
|
50
|
+
// 1. 一次开多个,每个都立刻返回,不等结果
|
|
51
|
+
dsh_spawn({ "task_name": "audit_api", "message": "审 src/api/**,只出问题清单" })
|
|
52
|
+
dsh_spawn({ "task_name": "audit_store", "message": "审 src/store/**,只出问题清单" })
|
|
53
|
+
dsh_spawn({ "task_name": "run_tests", "message": "跑全量测试,只报失败用例" })
|
|
54
|
+
|
|
55
|
+
// 2. 等任意一个先完成(谁先好先用,不必等全部)
|
|
56
|
+
dsh_wait({ "targets": ["audit_api", "audit_store", "run_tests"], "timeoutMs": 300000 })
|
|
57
|
+
// → { "status": { "audit_api": { "completed": "…" }, "audit_store": "running", … } }
|
|
58
|
+
|
|
59
|
+
// 3. 处理已完成的,继续等剩下的;完工的记得关掉
|
|
60
|
+
dsh_close({ "worker": "audit_api" })
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**规则(与 Codex 一致)**
|
|
64
|
+
|
|
65
|
+
- **并发上限 4 个 worker**(`maxWorkers`)。**已完成但没 `dsh_close` 的也占名额** —— 用完就关,别一直挂着。
|
|
66
|
+
- `dsh_task` 是阻塞的、一次一件;它**不占**那 4 个名额,适合"我就等着这一件"。
|
|
67
|
+
- `dsh_wait` 给**长一点的超时**(默认 30 秒偏短,重活用 2~5 分钟),不要用短超时反复轮询 —— Codex 的原话是 "prefer longer waits (minutes) to avoid busy polling"。
|
|
68
|
+
- `dsh_wait` 只要**任意一个**到终态就返回,所以它是"收割完成项"的循环,而不是"等全部"。
|
|
69
|
+
|
|
70
|
+
**不要并行的情况**
|
|
71
|
+
|
|
72
|
+
- 任务之间**有先后依赖**(B 要用 A 的结论)。
|
|
73
|
+
- 多个 worker 要改**同一批文件**:它们各自在自己的会话里,但落在同一个仓库,并发改同一文件会互相覆盖。要并行就按目录/模块切分,并在 prompt 里写死各自的边界。
|
|
74
|
+
- 你还没想清楚验收标准 —— 先想清楚再拆,否则只是把混乱放大 4 倍。
|
|
75
|
+
|
|
76
|
+
**需要上下文时**
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
dsh_spawn({ "task_name": "continue_here", "fork_turns": "all",
|
|
80
|
+
"from": "session-…", // 既有 DSH 会话
|
|
81
|
+
"message": "接着这个会话继续修那个失败用例" })
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`fork_turns: "all"` 会 fork 那个会话作为 worker 的起点(继承到最近一个已完成 turn),worker 的后续改动**不会**污染原会话。默认 `"none"` 是全新空会话。
|
|
85
|
+
|
|
86
|
+
|
|
41
87
|
## 怎么写 prompt
|
|
42
88
|
|
|
43
89
|
worker **看不到你这段对话**,它只有 `prompt` 和 `cwd`。写成可以独立开工的工单:
|
|
@@ -77,10 +123,18 @@ worker 收到的是**一条普通用户消息**,它看不到你这边有没有
|
|
|
77
123
|
## 同一个 worker 继续干
|
|
78
124
|
|
|
79
125
|
```jsonc
|
|
80
|
-
|
|
126
|
+
dsh_followup({ "worker": "audit_api", "message": "把第 3 条展开,给出补丁" })
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`dsh_followup` 复用该 worker 的会话,比新开一个便宜。也可以用 `dsh_task({ "sessionId": …, "prompt": … })` 做同样的事(阻塞版)。
|
|
130
|
+
|
|
131
|
+
## 查现在有哪些 worker
|
|
132
|
+
|
|
133
|
+
```jsonc
|
|
134
|
+
dsh_list_workers() // [{ task_name, sessionId, status }, …]
|
|
81
135
|
```
|
|
82
136
|
|
|
83
|
-
|
|
137
|
+
`status` 是 `"running"` / `"pending"` / `{completed}` / `{errored}` / `"closed"` / `"offline"`(重启后会话没打开且无缓存结果)。**重启 App 之后这些记录仍在**(落在 `~/.dsh/bridge/workers.json`)。
|
|
84
138
|
|
|
85
139
|
## 低阶工具(一般不用)
|
|
86
140
|
|
|
@@ -94,5 +148,6 @@ dsh_task({ "sessionId": "session-…", "prompt": "再把失败的那个用例补
|
|
|
94
148
|
|
|
95
149
|
- 不要把密钥、私有凭据写进 `prompt`——它会进入 DSH 的会话日志。
|
|
96
150
|
- 不要让 worker 操作你的本地环境(它有自己的权限与目录)。
|
|
97
|
-
-
|
|
151
|
+
- 不要把三件不相关的事塞进一次调用;拆开更可控(也可以直接并行)。
|
|
152
|
+
- 不要超过 4 个 worker,也不要让已完成的 worker 一直挂着不关。
|
|
98
153
|
- 不要在 DSH 未启动时假装委派成功。
|