dsh-plugin-subagent-bridge 0.10.1 → 0.11.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.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/index.js +35 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -56,6 +56,26 @@ dsh_task({ prompt: "…", cwd: "/repo/clawer" })
56
56
  - 返回结果里带上 `workspace: { id, title, path }`,方便确认落在哪个项目;
57
57
  - 项目操作失败不影响派发本身(分组是外观,不因此让任务失败)。
58
58
 
59
+ ## 排队还是立刻打断(`mode`)
60
+
61
+ DSH 的 prompt 有两种投递模式,桥把它们交给调用方选:
62
+
63
+ | `mode` | 行为 |
64
+ | --- | --- |
65
+ | `queue`(默认) | 等当前回合跑完再处理这条消息 |
66
+ | `steer` | **立刻打断**当前回合,把这条消息插进去 |
67
+
68
+ `dsh_task`、`dsh_spawn`、`dsh_followup` 都支持:
69
+
70
+ ```js
71
+ dsh_followup({ worker: "w1", message: "别改了,先把现在的结果汇报一下", mode: "steer" })
72
+ dsh_task({ sessionId: "session-…", prompt: "停一下,改成只跑单测", mode: "steer" })
73
+ ```
74
+
75
+ 和 `dsh_dispatch` 的关系:`dsh_dispatch` 一直有 `mode`,但它只投递、不等待;`dsh_task` 会阻塞到回合结束,所以以前对正在运行的会话直接拒绝。现在传 `mode: "steer"` 就允许插进运行中的会话——**它会先打断当前回合**,然后这个新回合跑完才返回。
76
+
77
+ 返回里带 `mode` 字段,方便确认这次是排队还是打断。
78
+
59
79
  ## 指定模型
60
80
 
61
81
  `dsh_task`、`dsh_spawn`、`dsh_followup` 都可以带 `model`,让 worker 跑在指定模型上:
package/index.js CHANGED
@@ -115,6 +115,13 @@ const TOOLS = [
115
115
  description:
116
116
  'Grouping for the new session. true creates a DSH project (workspace) for `cwd`, titled after its last path segment, when none owns that directory yet; a string uses that title instead. Omit (or false) to only reuse an existing project, leaving the session ungrouped when the directory has none.',
117
117
  },
118
+ mode: {
119
+ type: 'string',
120
+ enum: ['queue', 'steer'],
121
+ description:
122
+ 'delivery: "queue" (default) waits for the running turn to finish, "steer" interrupts it and inserts this message now.',
123
+ },
124
+
118
125
  reasoningEffort: {
119
126
  type: 'string',
120
127
  description: 'Reasoning level for `model`, if that model offers levels (low, medium, high, max...).',
@@ -151,6 +158,13 @@ const TOOLS = [
151
158
  description:
152
159
  'Grouping for the new worker. true creates a DSH project (workspace) for `cwd` when none owns it; a string uses that title. Omit to only reuse an existing project.',
153
160
  },
161
+ mode: {
162
+ type: 'string',
163
+ enum: ['queue', 'steer'],
164
+ description:
165
+ 'delivery: "queue" (default) waits for the running turn to finish, "steer" interrupts it and inserts this message now.',
166
+ },
167
+
154
168
  reasoningEffort: { type: 'string', description: 'Reasoning level for `model`, when that model offers levels.' },
155
169
  },
156
170
  required: ['task_name', 'message'],
@@ -171,6 +185,12 @@ const TOOLS = [
171
185
  description: 'Switch this worker to another model before the turn, as "provider/model". Omit to keep its current one.',
172
186
  },
173
187
  reasoningEffort: { type: 'string', description: 'Reasoning level for `model`, when that model offers levels.' },
188
+ mode: {
189
+ type: 'string',
190
+ enum: ['queue', 'steer'],
191
+ description:
192
+ 'delivery: "queue" (default) waits for the running turn to finish, "steer" interrupts it and inserts this message now.',
193
+ },
174
194
  },
175
195
  required: ['worker', 'message'],
176
196
  additionalProperties: false,
@@ -590,6 +610,7 @@ async function callTool(id, params, api, signal) {
590
610
  from: args.from,
591
611
  cwd: args.cwd,
592
612
  workspace: args.workspace === true || (typeof args.workspace === 'string' && args.workspace !== '') ? args.workspace : undefined,
613
+ mode: args.mode === 'steer' ? 'steer' : undefined,
593
614
  model: typeof args.model === 'string' && args.model !== '' ? args.model : undefined,
594
615
  reasoningEffort:
595
616
  typeof args.reasoningEffort === 'string' && args.reasoningEffort !== '' ? args.reasoningEffort : undefined,
@@ -606,6 +627,7 @@ async function callTool(id, params, api, signal) {
606
627
  await api.followup({
607
628
  worker,
608
629
  message,
630
+ mode: args.mode === 'steer' ? 'steer' : undefined,
609
631
  model: typeof args.model === 'string' && args.model !== '' ? args.model : undefined,
610
632
  reasoningEffort:
611
633
  typeof args.reasoningEffort === 'string' && args.reasoningEffort !== '' ? args.reasoningEffort : undefined,
@@ -644,6 +666,7 @@ async function callTool(id, params, api, signal) {
644
666
  sessionId,
645
667
  agentPreset: typeof args.agentPreset === 'string' && args.agentPreset !== '' ? args.agentPreset : undefined,
646
668
  workspace: args.workspace === true || (typeof args.workspace === 'string' && args.workspace !== '') ? args.workspace : undefined,
669
+ mode: args.mode === 'steer' ? 'steer' : undefined,
647
670
  model: typeof args.model === 'string' && args.model !== '' ? args.model : undefined,
648
671
  reasoningEffort:
649
672
  typeof args.reasoningEffort === 'string' && args.reasoningEffort !== '' ? args.reasoningEffort : undefined,
@@ -718,7 +741,7 @@ export function createApi(ctx, state, settings) {
718
741
  })
719
742
  },
720
743
 
721
- async spawn({ taskName, message, forkTurns, from, cwd, model, reasoningEffort, workspace }) {
744
+ async spawn({ taskName, message, forkTurns, from, cwd, model, reasoningEffort, workspace, mode }) {
722
745
  const service = controller()
723
746
  if (service === undefined || service === null) throw new Error('session controller is unavailable')
724
747
  if (state.workers.has(taskName)) throw new Error(`task_name ${JSON.stringify(taskName)} is already in use`)
@@ -750,7 +773,7 @@ export function createApi(ctx, state, settings) {
750
773
  const before = Number(sessions()?.get?.(sessionId)?.seq ?? 0)
751
774
  const requestId = randomUUID()
752
775
  const accepted = await service.prompt(
753
- { sessionId, requestId, mode: 'queue', content: [{ type: 'text', text: message }] },
776
+ { sessionId, requestId, mode: mode === 'steer' ? 'steer' : 'queue', content: [{ type: 'text', text: message }] },
754
777
  AbortSignal.timeout(30000),
755
778
  )
756
779
  state.dispatched.add(sessionId)
@@ -775,7 +798,7 @@ export function createApi(ctx, state, settings) {
775
798
  }
776
799
  },
777
800
 
778
- async followup({ worker, message, model, reasoningEffort }) {
801
+ async followup({ worker, message, model, reasoningEffort, mode }) {
779
802
  const target = resolveWorker(state, worker)
780
803
  if (target === undefined) throw new Error(`unknown worker ${JSON.stringify(worker)}`)
781
804
  const service = controller()
@@ -785,7 +808,7 @@ export function createApi(ctx, state, settings) {
785
808
  const before = Number(sessions()?.get?.(target.sessionId)?.seq ?? 0)
786
809
  const requestId = randomUUID()
787
810
  const accepted = await service.prompt(
788
- { sessionId: target.sessionId, requestId, mode: 'queue', content: [{ type: 'text', text: message }] },
811
+ { sessionId: target.sessionId, requestId, mode: mode === 'steer' ? 'steer' : 'queue', content: [{ type: 'text', text: message }] },
789
812
  AbortSignal.timeout(30000),
790
813
  )
791
814
  state.baseline.set(target.sessionId, before)
@@ -824,7 +847,7 @@ export function createApi(ctx, state, settings) {
824
847
  return rows
825
848
  },
826
849
 
827
- async task({ prompt, cwd, sessionId, agentPreset, timeoutSeconds, model, reasoningEffort, workspace }) {
850
+ async task({ prompt, cwd, sessionId, agentPreset, timeoutSeconds, model, reasoningEffort, workspace, mode }) {
828
851
  const service = controller()
829
852
  if (service === undefined || service === null) throw new Error('session controller is unavailable')
830
853
  const seconds = Math.min(
@@ -846,17 +869,20 @@ export function createApi(ctx, state, settings) {
846
869
  }
847
870
  if (typeof targetId !== 'string' || targetId === '') throw new Error('session controller did not return a session id')
848
871
  created = true
849
- } else if (ctx.get('agents')?.get?.(targetId)?.status === 'running') {
850
- throw new Error('that session is running; use dsh_dispatch plus dsh_read_result instead')
872
+ } else if (mode !== 'steer' && ctx.get('agents')?.get?.(targetId)?.status === 'running') {
873
+ /* Steering exists precisely to reach a running session; queueing into one would
874
+ * silently wait for a turn this call is not tracking. */
875
+ throw new Error('that session is running; pass mode:"steer" to interrupt it, or use dsh_dispatch plus dsh_read_result')
851
876
  }
852
877
 
853
878
  const appliedModel = await applyModelSpec(ctx, targetId, model, reasoningEffort)
879
+ const deliveryMode = mode === 'steer' ? 'steer' : 'queue'
854
880
  const turnBefore = lastTurn(ctx, targetId)
855
881
  const before = Number(sessions()?.get?.(targetId)?.seq ?? 0)
856
882
  const finished = waitForTurnEnd(ctx, targetId, turnBefore, seconds * 1000)
857
883
  const requestId = randomUUID()
858
884
  const accepted = await service.prompt(
859
- { sessionId: targetId, requestId, mode: 'queue', content: [{ type: 'text', text: prompt }] },
885
+ { sessionId: targetId, requestId, mode: deliveryMode, content: [{ type: 'text', text: prompt }] },
860
886
  AbortSignal.timeout(30000),
861
887
  )
862
888
  state.dispatched.add(targetId)
@@ -895,6 +921,7 @@ export function createApi(ctx, state, settings) {
895
921
  : {}),
896
922
  ...(appliedModel === undefined ? {} : { model: appliedModel }),
897
923
  ...(placed === undefined ? {} : { workspace: placed }),
924
+ mode: deliveryMode,
898
925
  text: collected.text,
899
926
  truncated: collected.truncated,
900
927
  turns: lastTurn(ctx, targetId),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-subagent-bridge",
3
- "version": "0.10.1",
3
+ "version": "0.11.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": [