@deepseek-ai/dsh-subagent-dsh-sdk 0.2.0-rc.2 → 0.2.1-alpha.2

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.i18n.yaml CHANGED
@@ -18,17 +18,17 @@
18
18
  en: d6eb16d8fc690671
19
19
  zh: 5865ee3bed0b36ca
20
20
  /deepseek-ai-dsh-subagent-dsh-sdk/use-this-package/when-to-choose-it:
21
- en: 617cbcee49ca46d6
22
- zh: fbe52df163a1e64f
21
+ en: 83a84f1d42a071da
22
+ zh: 0dce8e7ea861f8c2
23
23
  /deepseek-ai-dsh-subagent-dsh-sdk/use-this-package/configuration:
24
- en: a242a1a4d16e5241
25
- zh: 20a8e87d4fec4117
24
+ en: 67cf17bbc985cad7
25
+ zh: ea432ae524f4ade9
26
26
  /deepseek-ai-dsh-subagent-dsh-sdk/use-this-package/what-you-get:
27
27
  en: b9ccfa85b40b07eb
28
28
  zh: 4f84e7327b4ff188
29
29
  /deepseek-ai-dsh-subagent-dsh-sdk/use-this-package/failure-and-recovery:
30
- en: 21eb548e92fe20ce
31
- zh: d865a29acf60018e
30
+ en: 9e3838df9eba09bb
31
+ zh: fbd00b9d72e41adc
32
32
  /deepseek-ai-dsh-subagent-dsh-sdk/understand-the-implementation:
33
33
  en: 00d076edd7fee716
34
34
  zh: 7aea818c13fe3cc1
@@ -39,8 +39,8 @@
39
39
  en: 1bed6c1577997df6
40
40
  zh: 8c99f132377f6ca2
41
41
  /deepseek-ai-dsh-subagent-dsh-sdk/understand-the-implementation/run-flow:
42
- en: f268db0520abf6f7
43
- zh: 4517fc7ea6bd099c
42
+ en: 6c50e4e8dc5e0698
43
+ zh: 9d0011e28341bbde
44
44
  /deepseek-ai-dsh-subagent-dsh-sdk/understand-the-implementation/stop-reason-mapping:
45
45
  en: e040a57202916f91
46
46
  zh: 196bf38f0f0db310
@@ -69,17 +69,17 @@
69
69
  en: 75d061c2151624a7
70
70
  zh: 71e58e020b60ff90
71
71
  /deepseek-ai-dsh-subagent-dsh-sdk/model-experience/parent-tool-result-indirectly/what-the-model-sees:
72
- en: e299c74b31e3ed7d
73
- zh: 118adc28e0755bfb
72
+ en: 5e4e3efe7517f782
73
+ zh: 0465e164583f6bc3
74
74
  /deepseek-ai-dsh-subagent-dsh-sdk/model-experience/parent-tool-result-indirectly/token-effect:
75
- en: 600ac97cdd163650
76
- zh: d08f0a72321a9548
75
+ en: 4fae54e2ff0c4874
76
+ zh: 1574bfe874f66a1f
77
77
  /deepseek-ai-dsh-subagent-dsh-sdk/model-experience/parent-tool-result-indirectly/kv-cache-effect:
78
78
  en: f5d5f97a153f7c70
79
79
  zh: 3d6bc05b9eadfbc2
80
80
  /deepseek-ai-dsh-subagent-dsh-sdk/known-limitations-and-deferred-work:
81
- en: 01cbee2a855b1d6a
82
- zh: 701e7afc9a7fc862
81
+ en: 9158e90d1a521c05
82
+ zh: 246ed0c694ead7ac
83
83
  /deepseek-ai-dsh-subagent-dsh-sdk/known-limitations-and-deferred-work/dev-note:
84
- en: 854b5d4640bb8c07
85
- zh: acd7a2aa4a4f457c
84
+ en: 04a404ccea615ef3
85
+ zh: df2d9d901d75f024
package/README.md CHANGED
@@ -31,7 +31,7 @@ Mount this provider when a delegation should run as a complete Harness runtime i
31
31
 
32
32
  Choose this backend when the child must be a full harness peer — its own composition, session persistence, model route, and tools — rather than an agent that shares the parent's process. Choose an in-process backend when the child must share the parent's composition or honor parent-enforced non-route capabilities: this provider accepts agent route options but rejects structured output, depth caps, tool filters, and personas rather than silently omitting them.
33
33
 
34
- The provider advertises `agentOptions: true`, with `outputSchema`/`depthLimit`/`toolFilter`/`persona` false, and `inheritsParentContext: false`. Its immutable `agentRouteDefaults` publish the configured provider/model baseline to `dsh-tool-subagent` before model overrides and exact-route preflight; `start()` independently applies the same configuration defaults for direct callers and `maxTokens`. Agent route values cross the SDK wire as an explicit whitelist; the child remains a fresh runtime in another process, and the only value derived from the parent agent itself is the workspace cwd. `dsh-tool-subagent` deployments over this provider set `maxDepth: 'provider-managed'` — the child harness owns its own recursion budget.
34
+ The provider advertises `agentOptions: true`, with `outputSchema`/`depthLimit`/`toolFilter`/`persona` false, and `inheritsParentContext: false`. Its immutable `agentRouteDefaults` publish the configured provider/model baseline to `dsh-tool-subagent` before model overrides and exact-route preflight; the backend applies the same configuration defaults during activation startup, including `maxTokens`. Agent route values cross the SDK wire as an explicit whitelist; the child remains a fresh runtime in another process, and the child process uses the selected effective directory while its Session retains the parent origin directory. `dsh-tool-subagent` deployments over this provider set `maxDepth: 'provider-managed'` — the child harness owns its own recursion budget.
35
35
 
36
36
  ### Configuration
37
37
 
@@ -42,7 +42,6 @@ The provider advertises `agentOptions: true`, with `outputSchema`/`depthLimit`/`
42
42
  | `profile` | `sdk` | Named child profile |
43
43
  | `patches` | `[]` | Ordered per-launch profile patch files, resolved and checked at plugin load |
44
44
  | `dshHome` | required | Absolute isolated Harness home for every nested child process |
45
- | `cwd` | parent session cwd | Working-directory override for the child process and its SDK session |
46
45
  | `provider` | `deepseek-official` | Provider route sent in the child's `initialize` |
47
46
  | `model` | `deepseek-v4-flash` | Model sent in the child's `initialize` |
48
47
  | `maxTokens` | adapter/provider route default | Per-request output-token cap sent in the child's `initialize` |
@@ -77,7 +76,7 @@ A successful run returns the child's final assistant text (or accumulated partia
77
76
 
78
77
  ### Failure and recovery
79
78
 
80
- An already-aborted request fails before path resolution or spawn. A route, spawn, handshake, or pre-publication cancellation failure ordinarily rejects only after the subprocess is reaped. If initialization and cleanup both fail, the ordered safe facts preserve both failures without claiming quiescence. A child runtime that fails after publication settles through the run rather than rejecting it; partial output stays separate from the safe diagnostic. Diagnostics expose only the provider plus `initialize`, `session-run`, or `shutdown` stage and a fixed category. They never copy SDK messages, stderr, paths, task content, environment values, credentials, or protocol payloads.
79
+ The configured SDK runtime must implement `session/wait`; a runtime without it reports an execution failure. Update the provider and a separately configured `dshBin` runtime together. An already-aborted request fails before path resolution or spawn. A route, spawn, handshake, or pre-publication cancellation failure ordinarily rejects only after the subprocess is reaped. If initialization and cleanup both fail, the ordered safe facts preserve both failures without claiming quiescence. A child runtime that fails after publication settles through the run rather than rejecting it; partial output stays separate from the safe diagnostic. Diagnostics expose only the provider plus `initialize`, `session-run`, or `shutdown` stage and a fixed category. They never copy SDK messages, stderr, paths, task content, environment values, credentials, or protocol payloads.
81
80
 
82
81
  -----
83
82
 
@@ -104,7 +103,7 @@ This section explains how the backend drives a child Harness runtime and where t
104
103
 
105
104
  ### Run flow
106
105
 
107
- A start resolves the child's working directory and one process-wide SDK route before spawning. Each declared `request.agentOptions` field (`provider`, `model`, `reasoningEffort`, or `maxTokens`) overrides the matching provider-instance default; omission preserves the configured provider/model and optional cap, while reasoning effort remains absent unless the request supplies it. The provider spawns the runtime through the SDK client and completes the `initialize` handshake, including exact-model and effort validation, before it fulfills. A route, spawn, handshake, or pre-publication cancellation failure rejects only after the subprocess is reaped; a working-directory resolution failure rejects before spawning. After publication the provider owns one SDK activity and reads the child's answer from its session events: the last complete non-empty `assistant/message` (an empty-content message that records usage is skipped), or the accumulated `text-delta` stream when no such message exists. Disposal is idempotent: it settles the result locally as `aborted`, sends a bounded protocol `shutdown` request, then escalates through stdin EOF → SIGTERM → SIGKILL to actual exit.
106
+ A start resolves the child's working directory and one process-wide SDK route before spawning. Each declared `request.agentOptions` field (`provider`, `model`, `reasoningEffort`, or `maxTokens`) overrides the matching provider-instance default; omission preserves the configured provider/model and optional cap, while reasoning effort remains absent unless the request supplies it. The provider spawns the runtime through the SDK client and completes the `initialize` handshake, including exact-model and effort validation, before it fulfills. A route, spawn, handshake, or pre-publication cancellation failure rejects only after the subprocess is reaped; a working-directory resolution failure rejects before spawning. After publication the provider subscribes to the child Session, queues its prompt, and waits through `session/wait` for managed descendants and subsequent root turns. It reads the child's answer from the ordered session events: the last complete non-empty `assistant/message` (an empty-content message that records usage is skipped), or the accumulated `text-delta` stream when no such message exists. Disposal is idempotent: it settles the result locally as `aborted`, sends a bounded protocol `shutdown` request, then escalates through stdin EOF → SIGTERM → SIGKILL to actual exit. The activation manager owns that run after acceptance; the caller's startup signal no longer cancels the child. Each activation executes once, and its receipt cancels execution and awaits cleanup.
108
107
 
109
108
  ### Stop-reason mapping
110
109
 
@@ -152,11 +151,11 @@ Independent of the parent request cache. Each SDK child can reuse only prefixes
152
151
 
153
152
  #### What the model sees
154
153
 
155
- Through `dsh-tool-subagent`, the parent receives only the child's final assistant text (or accumulated partial text) or that consumer's exact stop-reason error, not intermediate messages or tool traffic. A diagnostic-bearing non-completed result presents the safe diagnostic before separately preserved partial assistant output; startup and shutdown errors expose the same fixed facts without raw SDK text.
154
+ Through `dsh-tool-subagent`, the parent model first receives a child id, then a completion notice containing final or partial assistant text, the stop reason, and safe diagnostic. The parent Session independently retains the external task identity and complete terminal result. Intermediate messages and tool traffic do not enter the parent Session.
156
155
 
157
156
  #### Token effect
158
157
 
159
- Parent input grows only by the final result or error, which is data-dependent and retained until compaction. This provider adds no parent schema itself.
158
+ Parent input grows by the start acknowledgement and completion notice, whose size depends on the result data and persists until compaction. This provider adds no parent schema by itself.
160
159
 
161
160
  #### KV Cache effect
162
161
 
@@ -171,7 +170,7 @@ These limits define when this backend is a poor fit or needs special operational
171
170
 
172
171
  - **A fresh runtime process per run** — no pooling; a harness runtime boots a full plugin tree, so per-run spawn cost is higher than the ACP backend's typical child.
173
172
  - **No non-route start-time capabilities** — the parent can select the child agent route but cannot enforce `outputSchema`, depth, tool filters, or persona inside the child process; configure the selected child profile and its ordered patches instead.
174
- - **The child's transcript stays in the child's own session root** — the parent log records only the delegation tool call and result; the streamed `session.event` channel is consumed for output extraction, not bridged into the parent log.
173
+ - **The child's transcript stays in the child's own session root** — the parent log retains the external task identity and full result, while the streamed `session.event` channel is consumed for output extraction rather than transcript replication.
175
174
  - **Local child processes only** — the resolved working directory is a local path; a remote runtime would need its own backend.
176
175
 
177
176
  <a id="dev-note"></a>
@@ -186,5 +185,3 @@ This Dev Note is working context for maintainers: open questions and undecided d
186
185
  - **Remote runtimes** — a remote runtime would need its own backend and workspace mapping.
187
186
 
188
187
  </details>
189
-
190
- **Runtime invariant:** No companion is published. Run lifecycle pairing is owned and checked by the subagent seam's invariant; this backend's own state lives in the child process beyond this context's event streams.
package/README.zh.md CHANGED
@@ -31,7 +31,7 @@ kind: "package-reference"
31
31
 
32
32
  当子进程必须是完整的 harness 对等体——拥有自己的组合、会话持久化、模型路由与工具——而不是共享父进程的 agent 时,选择此后端。当子进程必须共享父级组合或遵守父级强制的非路由能力时,请选择进程内后端:本提供方接受 agent 路由选项,但会拒绝结构化输出、深度上限、工具过滤或 persona,而不是静默省略。
33
33
 
34
- 提供方声明 `agentOptions: true`,同时保持 `outputSchema`/`depthLimit`/`toolFilter`/`persona` 为 false,并且 `inheritsParentContext: false`。不可变的 `agentRouteDefaults` 会在模型覆盖与确切路由预检前,把配置的 provider/model 基线公开给 `dsh-tool-subagent`;`start()` 则为直接调用方独立应用同一份配置默认值,包括 `maxTokens`。agent 路由值通过显式白名单跨越 SDK 协议;子进程仍是另一进程里的全新运行时,唯一从父 agent 本身派生的值是工作区 cwd。基于本提供方的 `dsh-tool-subagent` 部署应设置 `maxDepth: 'provider-managed'`——子 harness 拥有自己的递归预算。
34
+ 提供方声明 `agentOptions: true`,同时保持 `outputSchema`/`depthLimit`/`toolFilter`/`persona` 为 false,并且 `inheritsParentContext: false`。不可变的 `agentRouteDefaults` 会在模型覆盖与确切路由预检前,把配置的 provider/model 基线公开给 `dsh-tool-subagent`;后端在 activation 启动时应用同一份配置默认值,包括 `maxTokens`。agent 路由值通过显式白名单跨越 SDK 协议;子进程仍是另一进程里的全新运行时,子进程使用所选有效目录,而其 Session 保留父级起始目录。基于本提供方的 `dsh-tool-subagent` 部署应设置 `maxDepth: 'provider-managed'`——子 harness 拥有自己的递归预算。
35
35
 
36
36
  ### 配置
37
37
 
@@ -42,7 +42,6 @@ kind: "package-reference"
42
42
  | `profile` | `sdk` | 具名子 profile |
43
43
  | `patches` | `[]` | 每次启动的有序 profile patch 文件,在插件加载时解析并校验 |
44
44
  | `dshHome` | 必填 | 每个嵌套子进程的绝对隔离 Harness home |
45
- | `cwd` | 父会话 cwd | 子进程及其 SDK 会话的工作目录覆盖值 |
46
45
  | `provider` | `deepseek-official` | 写入子进程 `initialize` 的提供方路由 |
47
46
  | `model` | `deepseek-v4-flash` | 写入子进程 `initialize` 的模型 |
48
47
  | `maxTokens` | 适配器/提供方路由默认值 | 写入子进程 `initialize` 的单次请求输出 token 上限 |
@@ -77,7 +76,7 @@ kind: "package-reference"
77
76
 
78
77
  ### 失败与恢复
79
78
 
80
- 已取消的请求会在路径解析或 spawn 之前失败。路由、spawn、握手或发布前取消失败通常只在子进程被回收后拒绝;如果初始化与清理均失败,有序安全事实会保留两项失败,而不会宣称已完全停稳。子运行时在发布后失败时会通过运行本身结算,而不是拒绝;部分输出与安全诊断保持分离。诊断只公开提供方、`initialize`、`session-run` 或 `shutdown` 阶段,以及固定类别。SDK 消息、stderr、路径、任务内容、环境值、凭据和协议载荷绝不会复制到诊断中。
79
+ 配置的 SDK 运行时必须实现 `session/wait`;不支持该请求的运行时会报告执行失败。请一并更新提供方与单独配置的 `dshBin` 运行时。已取消的请求会在路径解析或 spawn 之前失败。路由、spawn、握手或发布前取消失败通常只在子进程被回收后拒绝;如果初始化与清理均失败,有序安全事实会保留两项失败,而不会宣称已完全停稳。子运行时在发布后失败时会通过运行本身结算,而不是拒绝;部分输出与安全诊断保持分离。诊断只公开提供方、`initialize`、`session-run` 或 `shutdown` 阶段,以及固定类别。SDK 消息、stderr、路径、任务内容、环境值、凭据和协议载荷绝不会复制到诊断中。
81
80
 
82
81
  -----
83
82
 
@@ -104,7 +103,7 @@ kind: "package-reference"
104
103
 
105
104
  ### 运行流程
106
105
 
107
- 一次启动会在 spawn 前解析子进程工作目录与一条进程级 SDK 路由。`request.agentOptions` 中每个已声明字段(`provider`、`model`、`reasoningEffort` 或 `maxTokens`)都会覆盖对应的提供方实例默认值;省略时保留已配置的提供方/模型与可选上限,而推理强度只有在请求提供时才会出现。随后,提供方通过 SDK 客户端 spawn 运行时,并在履行前完成 `initialize` 握手,其中包括确切模型与推理强度校验。路由、spawn、握手或发布前取消失败时,只会在子进程被回收后拒绝;工作目录解析失败则会在尚未 spawn 任何内容时拒绝。发布后,提供方拥有一段 SDK 活动,并从子会话事件中读取答案:最后一条完整且非空的 `assistant/message`(记录 usage 的空内容消息会被跳过);若没有这类消息,则取累积的 `text-delta` 流。dispose 是幂等的:先在本地把结果确定为 `aborted`,发出有界的协议 `shutdown` 请求,再经 stdin EOF → SIGTERM → SIGKILL 升级到实际退出。
106
+ 一次启动会在 spawn 前解析子进程工作目录与一条进程级 SDK 路由。`request.agentOptions` 中每个已声明字段(`provider`、`model`、`reasoningEffort` 或 `maxTokens`)都会覆盖对应的提供方实例默认值;省略时保留已配置的提供方/模型与可选上限,而推理强度只有在请求提供时才会出现。随后,提供方通过 SDK 客户端 spawn 运行时,并在履行前完成 `initialize` 握手,其中包括确切模型与推理强度校验。路由、spawn、握手或发布前取消失败时,只会在子进程被回收后拒绝;工作目录解析失败则会在尚未 spawn 任何内容时拒绝。发布后,提供方先订阅子会话,再排入提示词,并通过 `session/wait` 等待受管理后代与根 Agent 后续轮次。它从有序会话事件中读取答案:最后一条完整且非空的 `assistant/message`(记录 usage 的空内容消息会被跳过);若没有这类消息,则取累积的 `text-delta` 流。dispose 是幂等的:先在本地把结果确定为 `aborted`,发出有界的协议 `shutdown` 请求,再经 stdin EOF → SIGTERM → SIGKILL 升级到实际退出。任务接受后由 activation 管理器拥有该运行;此后调用方的启动信号不再取消子任务。每个 activation 只执行一次,其凭据用于取消执行并等待清理。
108
107
 
109
108
  ### 停止原因映射
110
109
 
@@ -152,11 +151,11 @@ kind: "package-reference"
152
151
 
153
152
  #### 模型看到什么
154
153
 
155
- 经由 `dsh-tool-subagent`,父级只会收到子运行时最终的 assistant 文本(或累积的部分文本),或该消费方给出的精确停止原因错误;不会收到中间消息或工具流量。带诊断的非完成结果会先呈现安全诊断,再单独呈现保留的部分 assistant 输出;启动与 shutdown 错误使用同一固定事实,不公开原始 SDK 文本。
154
+ 通过 `dsh-tool-subagent`,父模型先收到 child id,随后收到含最终或部分 assistant 文本、停止原因与安全诊断的完成通知。父会话独立保存外部任务身份和完整终态结果。中间消息与工具通信不进入父会话。
156
155
 
157
156
  #### Token 影响
158
157
 
159
- 父级输入只增加最终结果或错误,其大小取决于数据,并保留到压缩(compaction)为止。本提供方自身不会向父级添加任何 schema。
158
+ 父级输入增加启动确认与完成通知,内容大小取决于数据,并保留到压缩(compaction)为止。本提供方自身不会向父级添加任何 schema。
160
159
 
161
160
  #### KV Cache 影响
162
161
 
@@ -171,7 +170,7 @@ kind: "package-reference"
171
170
 
172
171
  - **每次运行都使用全新的运行时进程**——不使用进程池;harness 运行时需要启动完整的插件树,因此每次运行的 spawn 成本高于 ACP 后端通常使用的子进程。
173
172
  - **不支持路由之外的启动时能力**——父级可以选择子 agent 路由,但无法在子进程内强制执行 `outputSchema`、深度限制、工具过滤或 persona;应改为配置所选子 profile 及其有序 patch。
174
- - **子进程的 transcript(文本记录)保留在其自身的会话根目录中**——父级日志只记录委派工具调用与结果;流式 `session.event` 通道只用于提取输出,不会桥接到父级日志中。
173
+ - **子任务转录保留在子进程自己的会话根目录中**——父日志保留外部任务身份与完整结果;流式 `session.event` 通道用于提取输出,不用于复制子任务转录。
175
174
  - **仅支持本地子进程**——解析出的工作目录是本地路径;远程运行时需要独立的后端。
176
175
 
177
176
  <a id="dev-note"></a>
@@ -186,5 +185,3 @@ kind: "package-reference"
186
185
  - **远程运行时**——远程运行时需要独立的后端与工作区映射。
187
186
 
188
187
  </details>
189
-
190
- **运行时不变式:** 不发布伴生入口。run 生命周期配对由 subagent seam 的不变式检查;本后端自身的状态位于子进程中,不在当前上下文的事件流内。
package/lib/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { statSync } from "node:fs";
2
2
  import { isAbsolute, resolve } from "node:path";
3
3
  import z from "@deepseek-ai/schemastery";
4
- import { AssistantOutputFold, NO_START_CAPABILITIES, assertPositiveFinite, resolveChildCwd, settleRunResult, subprocessRunHandle, validateConfiguredCwd } from "@deepseek-ai/dsh-subagent";
4
+ import { AssistantOutputFold, NO_START_CAPABILITIES, assertPositiveFinite, assertUsableCwd, settleRunResult, subprocessRunHandle } from "@deepseek-ai/dsh-subagent";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { brandString } from "@deepseek-ai/dsh-brand";
7
- import { DeepSeekHarness, JsonRpcResponseError, SdkProtocolError, TransportClosedError } from "@deepseek-ai/dsh-sdk-client";
7
+ import { DeepSeekHarness, JsonRpcResponseError, SdkProtocolError, TransportClosedError, validatedSessionEvent } from "@deepseek-ai/dsh-sdk-client";
8
8
  import { scrubbedParentEnv } from "@deepseek-ai/dsh-subprocess";
9
9
  //#region lib/types/run.js
10
10
  /**
@@ -165,7 +165,7 @@ async function startSdkRun(request, spec) {
165
165
  shutdownTimeoutMs: spec.shutdownTimeoutMs,
166
166
  disposeEofGraceMs: spec.disposeEofGraceMs,
167
167
  disposeGraceMs: spec.disposeGraceMs,
168
- cwd: spec.cwd,
168
+ cwd: spec.originCwd ?? spec.cwd,
169
169
  provider: spec.provider,
170
170
  model: spec.model,
171
171
  ...spec.reasoningEffort === void 0 ? {} : { reasoningEffort: spec.reasoningEffort },
@@ -209,9 +209,15 @@ async function startSdkRun(request, spec) {
209
209
  }
210
210
  const childSessionId = `session-${randomUUID().replaceAll("-", "")}`;
211
211
  const fold = new AssistantOutputFold();
212
- const observe = (notification) => {
213
- if (notification.method !== "session.event" || notification.params.sessionId !== childSessionId) return;
214
- fold.push(notification.params.event);
212
+ const subscription = harness.client.subscribe((notification) => notification.method === "session.event" && notification.params.sessionId === childSessionId);
213
+ let lastEnd;
214
+ const drainNotifications = () => {
215
+ let notification;
216
+ while ((notification = subscription.tryNext()) !== void 0) {
217
+ const event = validatedSessionEvent(notification.params.event);
218
+ fold.push(event);
219
+ if (event.type === "turn/end") lastEnd = event.data.reason;
220
+ }
215
221
  };
216
222
  const collectOutput = () => fold.collect() ?? [];
217
223
  const teardown = async () => {
@@ -226,17 +232,28 @@ async function startSdkRun(request, spec) {
226
232
  }
227
233
  };
228
234
  let diagnostic;
235
+ let accepted = false;
229
236
  return subprocessRunHandle({
230
237
  id,
231
238
  result: settleRunResult({
232
239
  attempt: async () => {
233
240
  try {
234
- const turn = await Promise.race([harness.session(childSessionId).run(request.prompt, { onNotification: observe }), cancelSettled.then(() => "cancelled")]);
235
- if (turn === "cancelled") return {
241
+ try {
242
+ await Promise.race([(async () => {
243
+ await harness.session(childSessionId).setWorkingDirectory(spec.cwd);
244
+ if (flags.cancelled) return;
245
+ await harness.client.prompt(childSessionId, request.prompt);
246
+ accepted = true;
247
+ await harness.client.request("session/wait", { sessionId: childSessionId });
248
+ })(), cancelSettled]);
249
+ } finally {
250
+ if (accepted) drainNotifications();
251
+ }
252
+ if (flags.cancelled) return {
236
253
  output: collectOutput(),
237
254
  stopReason: "aborted"
238
255
  };
239
- const outcome = sdkChildOutcome(turn.events.findLast((event) => event.type === "turn/end")?.data.reason);
256
+ const outcome = sdkChildOutcome(lastEnd);
240
257
  diagnostic = outcome.diagnostic;
241
258
  return {
242
259
  output: collectOutput(),
@@ -245,6 +262,8 @@ async function startSdkRun(request, spec) {
245
262
  } catch (error) {
246
263
  diagnostic = failureDiagnostic(sdkFailure(error, "session-run").facts);
247
264
  throw error;
265
+ } finally {
266
+ subscription.close();
248
267
  }
249
268
  },
250
269
  collectOutput,
@@ -268,8 +287,8 @@ async function startSdkRun(request, spec) {
268
287
  * session, model route, and tools — driven over stdio JSON-RPC through the
269
288
  * TypeScript SDK client, so it shares no Cordis context. It accepts the
270
289
  * provider/model/reasoning/maxTokens subset of `agentOptions`; other start
271
- * features remain unsupported. The ONE thing it reads off `request.parent`
272
- * is the session's workspace cwd. This plugin uses named
290
+ * features remain unsupported. The child retains its parent's origin directory
291
+ * while starting in the directory selected by the subagent service. This plugin uses named
273
292
  * exports only; a default would hide its loader metadata (see
274
293
  * `docs/postmortem/0001-acp-default-export-drops-inject.md`).
275
294
  * @module @deepseek-ai/dsh-subagent-dsh-sdk
@@ -282,7 +301,6 @@ const Config = z.object({
282
301
  profile: z.string().default("sdk"),
283
302
  patches: z.array(z.string()).default([]),
284
303
  dshHome: z.string().required(),
285
- cwd: z.string(),
286
304
  provider: z.string().default("deepseek-official"),
287
305
  model: z.string().default("deepseek-v4-flash"),
288
306
  maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER),
@@ -339,7 +357,7 @@ var SdkSubagentProvider = class {
339
357
  if (request.signal.aborted) throw new Error("subagent request was aborted before the SDK child started");
340
358
  let cwd;
341
359
  try {
342
- cwd = resolveChildCwd("subagent-dsh-sdk", this.config.cwd, request.parent.session.header.cwd);
360
+ cwd = assertUsableCwd("subagent-dsh-sdk", "child cwd", request.cwd);
343
361
  } catch (error) {
344
362
  const failure = sdkConfigurationFailure(error);
345
363
  this.ctx.logger.warn(`subagent-dsh-sdk "${this.name}": child start failed: %o`, error);
@@ -352,6 +370,7 @@ var SdkSubagentProvider = class {
352
370
  patches: this.config.patches,
353
371
  dshHome: this.config.dshHome,
354
372
  cwd,
373
+ originCwd: request.parent.session.header.cwd,
355
374
  ...route,
356
375
  env: this.config.env,
357
376
  shutdownTimeoutMs: this.config.shutdownTimeoutMs,
@@ -375,12 +394,7 @@ function apply(ctx, config) {
375
394
  patches: resolved.patches.map((path, index) => resolveConfiguredFile(`patches[${String(index)}]`, path)),
376
395
  ...resolved.dshBin === void 0 ? {} : { dshBin: resolveConfiguredFile("dshBin", resolved.dshBin) }
377
396
  };
378
- const configuredCwd = validateConfiguredCwd("subagent-dsh-sdk", resolved.cwd);
379
- const validated = configuredCwd === void 0 ? launchPaths : {
380
- ...launchPaths,
381
- cwd: configuredCwd
382
- };
383
- ctx.subagents.registerProvider(new SdkSubagentProvider(validated.providerName, ctx, validated));
397
+ ctx.subagents.registerProvider(new SdkSubagentProvider(launchPaths.providerName, ctx, launchPaths));
384
398
  }
385
399
  //#endregion
386
400
  export { Config, apply, inject, name };
@@ -4,8 +4,8 @@
4
4
  * session, model route, and tools — driven over stdio JSON-RPC through the
5
5
  * TypeScript SDK client, so it shares no Cordis context. It accepts the
6
6
  * provider/model/reasoning/maxTokens subset of `agentOptions`; other start
7
- * features remain unsupported. The ONE thing it reads off `request.parent`
8
- * is the session's workspace cwd. This plugin uses named
7
+ * features remain unsupported. The child retains its parent's origin directory
8
+ * while starting in the directory selected by the subagent service. This plugin uses named
9
9
  * exports only; a default would hide its loader metadata (see
10
10
  * `docs/postmortem/0001-acp-default-export-drops-inject.md`).
11
11
  * @module @deepseek-ai/dsh-subagent-dsh-sdk
@@ -26,15 +26,6 @@ export interface Config {
26
26
  patches: string[];
27
27
  /** Absolute isolated Harness home for every nested child process. */
28
28
  dshHome: string;
29
- /**
30
- * Working directory override for the child process and its SDK session
31
- * workspace. Must be non-empty; a relative path resolves against the
32
- * harness launch directory at load, and the result must be an existing
33
- * directory. When omitted, each child inherits its delegating parent
34
- * session's cwd — and starting one from a parent session that has no cwd
35
- * fails.
36
- */
37
- cwd?: string;
38
29
  /** Provider route the child runtime initializes with (default `deepseek-official`). */
39
30
  provider: string;
40
31
  /** Model the child runtime initializes with (default `deepseek-v4-flash`). */
@@ -23,12 +23,10 @@ export interface SdkRunSpec {
23
23
  patches: string[];
24
24
  /** Absolute isolated Harness home for the nested runtime. */
25
25
  dshHome: string;
26
- /**
27
- * Absolute working directory for the child process AND the workspace cwd
28
- * of its SDK session. The provider resolves it before this spec exists:
29
- * config override, else the delegating parent session's workspace.
30
- */
26
+ /** Absolute initial working directory selected by the delegating runtime. */
31
27
  cwd: string;
28
+ /** Parent origin directory recorded independently of the child's effective directory. */
29
+ originCwd?: string | undefined;
32
30
  /** Provider route the child runtime initializes with. */
33
31
  provider: string;
34
32
  /** Model the child runtime initializes with. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-subagent-dsh-sdk",
3
3
  "description": "Out-of-process SDK subagent backend: drives a child DeepSeek Harness runtime subprocess over stdio JSON-RPC through the TypeScript SDK client",
4
- "version": "0.2.0-rc.2",
4
+ "version": "0.2.1-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,38 +27,38 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/cordis": "~4.0.4",
31
- "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
32
- "@deepseek-ai/dsh-sdk-client": "0.2.0-rc.2",
33
- "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
34
- "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2",
35
- "@deepseek-ai/dsh-subagent": "0.2.0-rc.2",
36
- "@deepseek-ai/dsh-session": "0.2.0-rc.2"
30
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1",
31
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.2",
32
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.2",
33
+ "@deepseek-ai/dsh-sdk-client": "0.2.1-alpha.2",
34
+ "@deepseek-ai/dsh-session": "0.2.1-alpha.2",
35
+ "@deepseek-ai/dsh-subagent": "0.2.1-alpha.2",
36
+ "@deepseek-ai/dsh-subprocess": "0.2.1-alpha.2"
37
37
  },
38
38
  "dependencies": {
39
- "@deepseek-ai/dsh-brand": "0.2.0-rc.2",
40
- "@deepseek-ai/schemastery": "~3.18.4"
39
+ "@deepseek-ai/dsh-brand": "0.2.1-alpha.2",
40
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.1"
41
41
  },
42
42
  "devDependencies": {
43
- "@deepseek-ai/cordis": "~4.0.4",
44
- "@deepseek-ai/cordis-plugin-loader": "~1.0.5",
45
- "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
46
- "@deepseek-ai/dsh-agent-instructions": "0.2.0-rc.2",
47
- "@deepseek-ai/dsh-app-boot": "0.2.0-rc.2",
48
- "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
49
- "@deepseek-ai/dsh-llm-deepseek": "0.2.0-rc.2",
50
- "@deepseek-ai/dsh-llm-deepseek-api-key": "0.2.0-rc.2",
51
- "@deepseek-ai/dsh-sdk-client": "0.2.0-rc.2",
52
- "@deepseek-ai/dsh-sdk-protocol": "0.2.0-rc.2",
53
- "@deepseek-ai/dsh-session": "0.2.0-rc.2",
54
- "@deepseek-ai/dsh-session-checkpoint-policy": "0.2.0-rc.2",
55
- "@deepseek-ai/dsh-session-persistence-jsonl": "0.2.0-rc.2",
56
- "@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
57
- "@deepseek-ai/dsh-loader-smoke": "0.2.0-rc.2",
58
- "@deepseek-ai/dsh-subagent": "0.2.0-rc.2",
59
- "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2",
60
- "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
61
- "@deepseek-ai/dsh-tool-subagent": "0.2.0-rc.2",
62
- "@deepseek-ai/dsh-skill-filesystem": "0.2.0-rc.2"
43
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1",
44
+ "@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.1",
45
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.2",
46
+ "@deepseek-ai/dsh-agent-instructions": "0.2.1-alpha.2",
47
+ "@deepseek-ai/dsh-app-boot": "0.2.1-alpha.2",
48
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.2",
49
+ "@deepseek-ai/dsh-llm-deepseek": "0.2.1-alpha.2",
50
+ "@deepseek-ai/dsh-llm-deepseek-api-key": "0.2.1-alpha.2",
51
+ "@deepseek-ai/dsh-loader-smoke": "0.2.1-alpha.2",
52
+ "@deepseek-ai/dsh-sdk-client": "0.2.1-alpha.2",
53
+ "@deepseek-ai/dsh-sdk-protocol": "0.2.1-alpha.2",
54
+ "@deepseek-ai/dsh-session": "0.2.1-alpha.2",
55
+ "@deepseek-ai/dsh-session-checkpoint-policy": "0.2.1-alpha.2",
56
+ "@deepseek-ai/dsh-session-persistence-jsonl": "0.2.1-alpha.2",
57
+ "@deepseek-ai/dsh-session-projection": "0.2.1-alpha.2",
58
+ "@deepseek-ai/dsh-skill-filesystem": "0.2.1-alpha.2",
59
+ "@deepseek-ai/dsh-subagent": "0.2.1-alpha.2",
60
+ "@deepseek-ai/dsh-subprocess": "0.2.1-alpha.2",
61
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-alpha.2",
62
+ "@deepseek-ai/dsh-tool-subagent": "0.2.1-alpha.2"
63
63
  }
64
64
  }