dsh-plugin-subagent-bridge 0.11.0 → 0.12.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/README.md +10 -2
- package/index.js +30 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,7 +63,8 @@ DSH 的 prompt 有两种投递模式,桥把它们交给调用方选:
|
|
|
63
63
|
| `mode` | 行为 |
|
|
64
64
|
| --- | --- |
|
|
65
65
|
| `queue`(默认) | 等当前回合跑完再处理这条消息 |
|
|
66
|
-
| `steer` |
|
|
66
|
+
| `steer` | 把消息**插进当前回合**(当前那一步仍会跑完) |
|
|
67
|
+
| `interrupt` | **先取消当前回合**,再发送,真正立刻停下 |
|
|
67
68
|
|
|
68
69
|
`dsh_task`、`dsh_spawn`、`dsh_followup` 都支持:
|
|
69
70
|
|
|
@@ -72,7 +73,14 @@ dsh_followup({ worker: "w1", message: "别改了,先把现在的结果汇报
|
|
|
72
73
|
dsh_task({ sessionId: "session-…", prompt: "停一下,改成只跑单测", mode: "steer" })
|
|
73
74
|
```
|
|
74
75
|
|
|
75
|
-
和 `dsh_dispatch` 的关系:`dsh_dispatch` 一直有 `mode`,但它只投递、不等待;`dsh_task` 会阻塞到回合结束,所以以前对正在运行的会话直接拒绝。现在传 `mode: "steer"`
|
|
76
|
+
和 `dsh_dispatch` 的关系:`dsh_dispatch` 一直有 `mode`,但它只投递、不等待;`dsh_task` 会阻塞到回合结束,所以以前对正在运行的会话直接拒绝。现在传 `mode: "steer"` 就允许插进运行中的会话——**它会先打断当前回合**,然后等这一轮结束才返回。
|
|
77
|
+
|
|
78
|
+
两个实测出来的细节:
|
|
79
|
+
|
|
80
|
+
1. **`steer` 不会打断正在执行的工具**。实测:worker 正在跑 `sleep 90`,`steer` 进去后消息被**并入当前回合**,但要等那一步工具调用结束才会被模型看到——整整等了 70 秒。原因是 `steer` 只是投递,`cancel` 才是中止。
|
|
81
|
+
2. **`steer` 不新开回合**,所以等待条件不能是"下一个回合结束"。早期实现那样写,答案已经回来了、调用还在等,最后 120 秒超时。现在 steer 等的是「当前回合结束」。
|
|
82
|
+
|
|
83
|
+
所以真要"按下就停",用 `interrupt`:它先 `cancel` 掉当前回合(最多等 15 秒确认),再以普通排队模式发消息,开启新回合。
|
|
76
84
|
|
|
77
85
|
返回里带 `mode` 字段,方便确认这次是排队还是打断。
|
|
78
86
|
|
package/index.js
CHANGED
|
@@ -117,9 +117,9 @@ const TOOLS = [
|
|
|
117
117
|
},
|
|
118
118
|
mode: {
|
|
119
119
|
type: 'string',
|
|
120
|
-
enum: ['queue', 'steer'],
|
|
120
|
+
enum: ['queue', 'steer', 'interrupt'],
|
|
121
121
|
description:
|
|
122
|
-
'delivery: "queue" (default) waits for the running turn to finish
|
|
122
|
+
'delivery: "queue" (default) waits for the running turn to finish; "steer" inserts the message into that turn (its current step still completes); "interrupt" cancels the running turn first, then sends.',
|
|
123
123
|
},
|
|
124
124
|
|
|
125
125
|
reasoningEffort: {
|
|
@@ -160,9 +160,9 @@ const TOOLS = [
|
|
|
160
160
|
},
|
|
161
161
|
mode: {
|
|
162
162
|
type: 'string',
|
|
163
|
-
enum: ['queue', 'steer'],
|
|
163
|
+
enum: ['queue', 'steer', 'interrupt'],
|
|
164
164
|
description:
|
|
165
|
-
'delivery: "queue" (default) waits for the running turn to finish
|
|
165
|
+
'delivery: "queue" (default) waits for the running turn to finish; "steer" inserts the message into that turn (its current step still completes); "interrupt" cancels the running turn first, then sends.',
|
|
166
166
|
},
|
|
167
167
|
|
|
168
168
|
reasoningEffort: { type: 'string', description: 'Reasoning level for `model`, when that model offers levels.' },
|
|
@@ -187,9 +187,9 @@ const TOOLS = [
|
|
|
187
187
|
reasoningEffort: { type: 'string', description: 'Reasoning level for `model`, when that model offers levels.' },
|
|
188
188
|
mode: {
|
|
189
189
|
type: 'string',
|
|
190
|
-
enum: ['queue', 'steer'],
|
|
190
|
+
enum: ['queue', 'steer', 'interrupt'],
|
|
191
191
|
description:
|
|
192
|
-
'delivery: "queue" (default) waits for the running turn to finish
|
|
192
|
+
'delivery: "queue" (default) waits for the running turn to finish; "steer" inserts the message into that turn (its current step still completes); "interrupt" cancels the running turn first, then sends.',
|
|
193
193
|
},
|
|
194
194
|
},
|
|
195
195
|
required: ['worker', 'message'],
|
|
@@ -610,7 +610,7 @@ async function callTool(id, params, api, signal) {
|
|
|
610
610
|
from: args.from,
|
|
611
611
|
cwd: args.cwd,
|
|
612
612
|
workspace: args.workspace === true || (typeof args.workspace === 'string' && args.workspace !== '') ? args.workspace : undefined,
|
|
613
|
-
mode: args.mode === 'steer'
|
|
613
|
+
mode: args.mode === 'steer' || args.mode === 'interrupt' ? args.mode : undefined,
|
|
614
614
|
model: typeof args.model === 'string' && args.model !== '' ? args.model : undefined,
|
|
615
615
|
reasoningEffort:
|
|
616
616
|
typeof args.reasoningEffort === 'string' && args.reasoningEffort !== '' ? args.reasoningEffort : undefined,
|
|
@@ -627,7 +627,7 @@ async function callTool(id, params, api, signal) {
|
|
|
627
627
|
await api.followup({
|
|
628
628
|
worker,
|
|
629
629
|
message,
|
|
630
|
-
mode: args.mode === 'steer'
|
|
630
|
+
mode: args.mode === 'steer' || args.mode === 'interrupt' ? args.mode : undefined,
|
|
631
631
|
model: typeof args.model === 'string' && args.model !== '' ? args.model : undefined,
|
|
632
632
|
reasoningEffort:
|
|
633
633
|
typeof args.reasoningEffort === 'string' && args.reasoningEffort !== '' ? args.reasoningEffort : undefined,
|
|
@@ -666,7 +666,7 @@ async function callTool(id, params, api, signal) {
|
|
|
666
666
|
sessionId,
|
|
667
667
|
agentPreset: typeof args.agentPreset === 'string' && args.agentPreset !== '' ? args.agentPreset : undefined,
|
|
668
668
|
workspace: args.workspace === true || (typeof args.workspace === 'string' && args.workspace !== '') ? args.workspace : undefined,
|
|
669
|
-
mode: args.mode === 'steer'
|
|
669
|
+
mode: args.mode === 'steer' || args.mode === 'interrupt' ? args.mode : undefined,
|
|
670
670
|
model: typeof args.model === 'string' && args.model !== '' ? args.model : undefined,
|
|
671
671
|
reasoningEffort:
|
|
672
672
|
typeof args.reasoningEffort === 'string' && args.reasoningEffort !== '' ? args.reasoningEffort : undefined,
|
|
@@ -869,20 +869,36 @@ export function createApi(ctx, state, settings) {
|
|
|
869
869
|
}
|
|
870
870
|
if (typeof targetId !== 'string' || targetId === '') throw new Error('session controller did not return a session id')
|
|
871
871
|
created = true
|
|
872
|
-
} else if (mode !== 'steer' && ctx.get('agents')?.get?.(targetId)?.status === 'running') {
|
|
872
|
+
} else if (mode !== 'steer' && mode !== 'interrupt' && ctx.get('agents')?.get?.(targetId)?.status === 'running') {
|
|
873
873
|
/* Steering exists precisely to reach a running session; queueing into one would
|
|
874
874
|
* silently wait for a turn this call is not tracking. */
|
|
875
|
-
throw new Error('that session is running; pass mode:"steer"
|
|
875
|
+
throw new Error('that session is running; pass mode:"steer" (insert into the turn) or mode:"interrupt" (cancel it first), or use dsh_dispatch plus dsh_read_result')
|
|
876
876
|
}
|
|
877
877
|
|
|
878
878
|
const appliedModel = await applyModelSpec(ctx, targetId, model, reasoningEffort)
|
|
879
|
-
const deliveryMode = mode === 'steer' ? 'steer' : 'queue'
|
|
879
|
+
const deliveryMode = mode === 'steer' ? 'steer' : mode === 'interrupt' ? 'interrupt' : 'queue'
|
|
880
880
|
const turnBefore = lastTurn(ctx, targetId)
|
|
881
|
+
|
|
882
|
+
/* `steer` inserts the message into the running turn, but that turn still finishes its
|
|
883
|
+
* current step first — an in-flight tool call keeps running. `interrupt` is the hard
|
|
884
|
+
* stop: cancel the turn, then send, so the worker drops what it was doing. */
|
|
885
|
+
if (deliveryMode === 'interrupt' && ctx.get('agents')?.get?.(targetId)?.status === 'running') {
|
|
886
|
+
try {
|
|
887
|
+
await service.cancel({ sessionId: targetId })
|
|
888
|
+
} catch {
|
|
889
|
+
/* Cancelling is best effort: the send below still delivers the message. */
|
|
890
|
+
}
|
|
891
|
+
await waitForTurnEnd(ctx, targetId, turnBefore, 15000)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/* A steered message is absorbed by the turn that is already running, so waiting for
|
|
895
|
+
* a strictly newer turn would hang until the timeout even after the answer landed. */
|
|
896
|
+
const waitAfter = deliveryMode === 'steer' ? Math.max(0, turnBefore - 1) : turnBefore
|
|
881
897
|
const before = Number(sessions()?.get?.(targetId)?.seq ?? 0)
|
|
882
|
-
const finished = waitForTurnEnd(ctx, targetId,
|
|
898
|
+
const finished = waitForTurnEnd(ctx, targetId, waitAfter, seconds * 1000)
|
|
883
899
|
const requestId = randomUUID()
|
|
884
900
|
const accepted = await service.prompt(
|
|
885
|
-
{ sessionId: targetId, requestId, mode: deliveryMode, content: [{ type: 'text', text: prompt }] },
|
|
901
|
+
{ sessionId: targetId, requestId, mode: deliveryMode === 'steer' ? 'steer' : 'queue', content: [{ type: 'text', text: prompt }] },
|
|
886
902
|
AbortSignal.timeout(30000),
|
|
887
903
|
)
|
|
888
904
|
state.dispatched.add(targetId)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subagent-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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": [
|