@deepseek-ai/dsh-subagent 0.1.0-rc.7 → 0.1.0-rc.8
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 +2 -2
- package/README.md +5 -4
- package/README.zh.md +5 -4
- package/lib/index.js +84 -7
- package/lib/types/continuation.d.ts +21 -2
- package/lib/types/continuation.js +58 -7
- package/lib/types/index.d.ts +11 -0
- package/lib/types/index.js +16 -0
- package/lib/types/out-of-process.d.ts +2 -0
- package/lib/types/out-of-process.js +31 -1
- package/lib/types/run-settlement.js +10 -3
- package/lib/types/types.d.ts +7 -0
- package/package.json +34 -34
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/subagent/subagent/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: e84a6b486253e81ccf7e7df12c4149e6df4ed9f2
|
|
6
|
+
README.zh.md: 4ca619a9f402f3c6b4f648adf350e7ed7a568799
|
package/README.md
CHANGED
|
@@ -16,12 +16,13 @@ The [subagent family overview](../README.md) maps implementations and model-faci
|
|
|
16
16
|
| `getProvider(name)` | Return the provider, or `undefined` when absent. |
|
|
17
17
|
| `list()` | Return provider names in insertion order. |
|
|
18
18
|
| `start(name, request)` | Validate an ordinary caller request, resolve its detached `one-shot` descriptor, then await the provider until a real one-shot child is published. Fulfillment returns a holder-owned `SubagentRun`; rejection means the provider has already cleaned every unpublished startup resource, while post-publication turn or infrastructure faults settle through the run. Continuable children never enter through this operation. |
|
|
19
|
-
| `startContinuable(spec)` | Establish one durable continuable child and deliver its initial prompt. Resolves with `{ childId, messageId }` when the child's inbox accepts that prompt, without waiting for the turn to start or for the message to reach the Session log; any earlier failure rejects with no ids and rolls the child back entirely. Requires `ctx.agents`, session persistence, and a provider with the `prepareContinuable` capability. |
|
|
19
|
+
| `startContinuable(spec)` | Establish one durable continuable child and deliver its initial prompt. Resolves with `{ childId, messageId }` when the child's inbox accepts that prompt, without waiting for the turn to start or for the message to reach the Session log; any earlier failure rejects with no ids and rolls the child back entirely. A caller-reserved `childId` is rejected when the live registries or configured persistence already own it. Requires `ctx.agents`, session persistence, and a provider with the `prepareContinuable` capability. |
|
|
20
20
|
| `followup(parent, childId, content, { source, signal })` | Deliver one later message from the exact live direct parent as the child's next FIFO turn, matching `Agent.followup()` terminology, and return the accepted `MessageId`. A resident child's inbox accepts it directly (waking a waiting Activation); an absent one cold-resumes from its persisted Session. Requires `ctx.agents`; cold resume also requires session persistence. |
|
|
21
21
|
| `interrupt(targetSessionId, authority)` | Interrupt one live continuable child's current turn under a human durable parent address (`{ kind: 'user', parentSessionId }`) or an exact live ancestor Agent (`{ kind: 'ancestor', agent }`). Admission is synchronous and the effect asynchronous: it issues `Agent.cancel(cause, { keepInbox: true })` and returns without waiting for the target to observe the signal. Unclaimed pending inbox work, the Activation, and published descendants are preserved; work already claimed into the interrupted turn is not requeued. An absent target is an accepted no-op; a wrong parent address or a stale, self-targeting, or non-ancestor caller rejects with `UNAUTHORIZED`. |
|
|
22
|
-
| `reportFrom(child, content, { delivery, signal })` | Deliver one selected message from the exact live continuable child to its exact live direct parent and return the accepted stable `MessageId`. Quiet delivery injects context
|
|
22
|
+
| `reportFrom(child, content, { delivery, signal })` | Deliver one selected message from the exact live continuable child to its exact live direct parent and return the accepted stable `MessageId`. Quiet delivery injects next-step context without waking; next-step delivery steers and wakes the parent. |
|
|
23
23
|
| `registerContinuableSetup(contribution)` | Compose an optional deployment capability into each continuable child's unpublished scope, with immediate revocation from resident children. |
|
|
24
24
|
| `drainContinuableDescendants(parents)` | Close admission below exact live host-owned parent Agents, stop only their visible continuable descendants, await materializations admitted below those roots through publication or rollback, then release the selected forests child-first. The cutoff lasts until each exact parent leaves the registry; unrelated parent forests and manager-wide admission remain live. |
|
|
25
|
+
| `drainContinuableChildren(parent, childIds)` | Release only the named resident continuable direct children of one exact live parent, recursively and child-first. It does not close admission or touch siblings, accepts absent ids as no-ops, and rejects a resident child owned by another parent. This is teardown, so unlike `interrupt()` it does not preserve pending inbox work. |
|
|
25
26
|
| `listChildren(parentSessionId, signal?)` | List direct session-backed subagents with their `one-shot`/`continuable` mode, `running`/`inactive` activity, origin-classified one-level `hasChildren` hint, and per-child diagnostics, ordered by `createdAt` then id, without loading or resuming them. Reads the live session store and optional session persistence directly (live-only enumeration when persistence is absent) and requires the mounted `sessionProjections` registry; it does not require `ctx.agents`, the continuation manager, or any query service. |
|
|
26
27
|
| `listDescendants(rootSessionId, signal?)` | Flatten the root's complete session tree in stable pre-order from the same live-preferred corpus, adding each subagent entry's durable `parentId` and root-relative `depth`. Ordinary sessions and one-shot children remain traversal nodes so continuable descendants below them are discovered. Identity, diagnostics, dependencies, and cancellation follow `listChildren()`. |
|
|
27
28
|
|
|
@@ -64,7 +65,7 @@ Both in-process delegation paths fix the child's permission scope at the delegat
|
|
|
64
65
|
|
|
65
66
|
`provider.start(request): Promise<SubagentRun>` is the ownership-transfer boundary; the delegation tool also uses it inside its one-shot Task-backed background path. Before fulfillment, the provider owns setup and must cancel, roll back, and quiesce unpublished resources on every failure. After fulfillment, the caller owns the run and must call `dispose()` on every path; remaining prompt and turn work belongs to `SubagentRun.result`.
|
|
66
67
|
|
|
67
|
-
`SubagentRun.result` resolves to `{ output, structured?, stopReason }`. Child-level failures resolve with a non-`completed` reason; only an infrastructure fault that the seam cannot represent may reject. `dispose()` is idempotent, cancels remaining work, and waits for both result settlement and child-resource quiescence. A result rejection remains on `result`; `dispose()` rejects only for an independent resource-release failure. `output` and the `subagent/end` event's `lastAssistantMessage` use the exported `AssistantOutputFold`/`finalAssistantOutput` helpers to select the child's last non-empty assistant message, or its accumulated assistant text when no such message exists. `output` is `[]` and the event field is absent when the child produced neither ([`SubagentResult
|
|
68
|
+
`SubagentRun.result` resolves to `{ output, structured?, diagnostic?, stopReason }`. Child-level failures resolve with a non-`completed` reason; only an infrastructure fault that the seam cannot represent may reject. A provider may add a safe `diagnostic` to a non-completed result after removing tool inputs, file contents, environment values, credentials, and raw protocol payloads and limiting the complete text to 4096 UTF-8 bytes. The common result type does not define provider categories or lifecycle stages: an out-of-process provider may derive fixed display text from its version-pinned structured product facts and an observed process outcome, while consumers render that text without parsing it. The field is not assistant output: consumers present it separately, and it does not enter `subagent/end.lastAssistantMessage`. `dispose()` is idempotent, cancels remaining work, and waits for both result settlement and child-resource quiescence. A result rejection remains on `result`; `dispose()` rejects only for an independent resource-release failure. `output` and the `subagent/end` event's `lastAssistantMessage` use the exported `AssistantOutputFold`/`finalAssistantOutput` helpers to select the child's last non-empty assistant message, or its accumulated assistant text when no such message exists. `output` is `[]` and the event field is absent when the child produced neither ([`SubagentResult`](../../../docs/subsystems/subagent.md#the-terminal-result-subagentresult) owns the terminal result contract).
|
|
68
69
|
|
|
69
70
|
A local run publishes an ordinary child agent/session before `start()` fulfills, returns that shared session id as `SubagentRun.id`, exposes the exact child as `SubagentRun.localAgent`, records `request.parent.session.id` in the child's `parentSession` header, and appends the resolved descriptor inside its initial turn. Remote providers instead mint a parent-scoped lifecycle id and return `localAgent: undefined`; without a local child session, their one-shot runs are not part of trace-backed enumeration.
|
|
70
71
|
|
|
@@ -146,7 +147,7 @@ Prefix-stable within a child: the statement never changes during the child's lif
|
|
|
146
147
|
|
|
147
148
|
- **ACP children remain one-shot and are not trace-enumerable** — an ACP run has no local child session in the parent's session corpus. An ACP `prepareContinuable` requires persisting the remote session id in provider-specific descriptor data and a per-child continuation advertisement, since ACP `loadSession` support is negotiated per child rather than established by the method's presence. Remote providers also require a separate Activation ownership contract with equivalent authenticated control and child-first quiescence before they support continuable children.
|
|
148
149
|
- **No host-user continuation** — `followup()` requires the exact live direct parent. Only `interrupt()` accepts a durable parent-address user authority, because stopping a turn is idempotent and delivers no content; a future host adapter needs a concrete authenticated interaction before the seam gains a user delivery capability.
|
|
149
|
-
- **
|
|
150
|
+
- **Continuation messages never steer** — parent-to-child continuation messages enqueue later child turns. Child-to-parent reports are independent next-step input and may extend the parent's open turn.
|
|
150
151
|
- **Wake gap during cancellation convergence** — a waking follow-up accepted after the interrupt signal is issued but before the active driver becomes idle remains queued until another waking send. Issue #1838 owns the agent-loop wake latch, which also affects ordinary session cancellation.
|
|
151
152
|
- **Process-local residency** — the Activation inbox and ownership graph do not coordinate two harness processes; concurrent access to one persistence store still requires a durable mailbox and cross-process lease protocol.
|
|
152
153
|
- **No replay of accepted-but-unlogged messages** — only messages written to the child Session log are reconstructable with the source that supplied them. A crash may lose an accepted initial prompt or follow-up that never reached the log; a later authorized message can cold-resume the child, but the lost message is not replayed automatically.
|
package/README.zh.md
CHANGED
|
@@ -16,12 +16,13 @@ subagent seam 允许一个 agent(智能体)通过具名提供方把工作委
|
|
|
16
16
|
| `getProvider(name)` | 返回提供方;不存在时返回 `undefined`。 |
|
|
17
17
|
| `list()` | 按插入顺序返回提供方名称。 |
|
|
18
18
|
| `start(name, request)` | 校验普通调用方请求,解析其已分离的 `one-shot` 描述符,然后等待提供方发布真正的一次性子 agent。兑现时返回由持有方拥有的 `SubagentRun`;如果调用被拒绝,提供方已经清理所有尚未发布的启动资源。发布后的轮次故障或基础设施故障则通过该 run 结算。可继续子 agent 绝不通过此操作进入。 |
|
|
19
|
-
| `startContinuable(spec)` | 建立一个持久化的可继续子 agent,并投递其初始提示词。子 agent 的 inbox 一接受该提示词,调用就会兑现为 `{ childId, messageId }`,无需等待轮次开始,也无需等待消息写入会话日志。在此之前发生的任何失败都会使调用被拒绝,不返回任何 id,并完全回滚该子 agent
|
|
19
|
+
| `startContinuable(spec)` | 建立一个持久化的可继续子 agent,并投递其初始提示词。子 agent 的 inbox 一接受该提示词,调用就会兑现为 `{ childId, messageId }`,无需等待轮次开始,也无需等待消息写入会话日志。在此之前发生的任何失败都会使调用被拒绝,不返回任何 id,并完全回滚该子 agent。如果在线注册表或已配置的持久化已经占用调用方预留的 `childId`,则拒绝该身份。要求 `ctx.agents`、会话持久化以及具备 `prepareContinuable` 能力的提供方。 |
|
|
20
20
|
| `followup(parent, childId, content, { source, signal })` | 将来自确切在线直接父级的一条后续消息作为子 agent 的下一个 FIFO 轮次投递,术语与 `Agent.followup()` 一致,并返回被接受的 `MessageId`。驻留中的子 agent 由其 inbox 直接接受(唤醒处于 waiting 的 Activation);不驻留的则从其持久化会话冷恢复。要求 `ctx.agents`;冷恢复还要求会话持久化。 |
|
|
21
21
|
| `interrupt(targetSessionId, authority)` | 凭人类出示的持久化父级地址 `{ kind: 'user', parentSessionId }`,或确切在线的祖先 Agent `{ kind: 'ancestor', agent }` 进行授权,中断一个在线可继续子级的当前轮次。准入判定同步完成,但取消异步生效:该操作发出 `Agent.cancel(cause, { keepInbox: true })` 后立即返回,不等待目标观察到信号。尚未领取的待处理 inbox 工作、Activation 和已发布的后代均会保留;已经领取到被中断轮次中的工作不会重新入队。目标不存在时视为已接受的空操作;错误的父级地址,或陈旧、指向自身、并非祖先的调用方,会以 `UNAUTHORIZED` 被拒绝。 |
|
|
22
|
-
| `reportFrom(child, content, { delivery, signal })` | 从确切在线可继续 child 向其确切在线直接 parent 投递一条选中消息,并返回已接受的稳定 `MessageId
|
|
22
|
+
| `reportFrom(child, content, { delivery, signal })` | 从确切在线可继续 child 向其确切在线直接 parent 投递一条选中消息,并返回已接受的稳定 `MessageId`。静默投递会注入不唤醒的 next-step 上下文;next-step 投递会 steering 并唤醒 parent。 |
|
|
23
23
|
| `registerContinuableSetup(contribution)` | 把一项可选部署能力组合到每个可继续 child 尚未发布的作用域中,并支持从驻留 child 立即撤销。 |
|
|
24
24
|
| `drainContinuableDescendants(parents)` | 在由 host 拥有的确切在线父级 Agent 之下关闭准入,只停止这些父级可见的可继续后代;等待已在这些根节点下获准的物化过程完成发布或回滚后,再按子级优先顺序释放所选的各棵树。该截止状态会持续到每个确切父级离开注册表;无关的父级树仍在线,管理器全局准入仍保持开放。 |
|
|
25
|
+
| `drainContinuableChildren(parent, childIds)` | 只释放一个确切在线父级的具名驻留可继续直接子级,并递归保持子级优先顺序。它不关闭准入、不影响同级子级,缺失的 id 视为空操作;若驻留子级属于其他父级,则拒绝。这是拆卸操作,因此与 `interrupt()` 不同,它不会保留待处理的 inbox 工作。 |
|
|
25
26
|
| `listChildren(parentSessionId, signal?)` | 按 `createdAt`、再按 id 的顺序列出由会话支撑的直接 subagent,包括其 `one-shot`/`continuable` 模式、`running`/`inactive` 活动状态、根据 origin 分类得出的一层 `hasChildren` 提示,以及每个子级的诊断信息,且不会加载或恢复它们。该操作直接读取在线会话存储和可选的会话持久化(没有持久化时只枚举在线子级),并要求已挂载 `sessionProjections` 注册表;不要求 `ctx.agents`、继续执行管理器或任何查询服务。 |
|
|
26
27
|
| `listDescendants(rootSessionId, signal?)` | 从同一份在线优先语料按稳定 pre-order 展平根的完整会话树,并为每个 subagent 条目附加持久 `parentId` 与相对根的 `depth`。普通会话与一次性 child 仍作为遍历节点,因此其下的可继续后代仍可发现。身份、diagnostic、依赖与取消约定均沿用 `listChildren()`。 |
|
|
27
28
|
|
|
@@ -64,7 +65,7 @@ subagent seam 允许一个 agent(智能体)通过具名提供方把工作委
|
|
|
64
65
|
|
|
65
66
|
`provider.start(request): Promise<SubagentRun>` 是所有权转移边界;委派工具也会在其由 Task 支撑的一次性后台路径中使用它。兑现前,提供方拥有设置过程,并且在任何失败路径上都必须取消、回滚并使尚未发布的资源完全停稳。兑现后,run 的所有权转移给调用方;调用方必须在每条路径上调用 `dispose()`。剩余提示词和轮次工作属于 `SubagentRun.result`。
|
|
66
67
|
|
|
67
|
-
`SubagentRun.result` 兑现为 `{ output, structured?, stopReason }`。子 agent 级失败会以非 `completed` 原因兑现;只有 seam
|
|
68
|
+
`SubagentRun.result` 兑现为 `{ output, structured?, diagnostic?, stopReason }`。子 agent 级失败会以非 `completed` 原因兑现;只有 seam 无法表示的基础设施故障才可以拒绝。提供方可以为非完成结果附加安全的 `diagnostic`:它会先排除工具输入、文件内容、环境值、凭证与原始协议载荷,并把完整文本限制在 4096 个 UTF-8 字节以内。共享结果类型不定义提供方类别或生命周期阶段:进程外提供方可以从锁定版本产品提供的结构化事实与已观测的进程结果派生固定展示文本,而消费方只负责原样呈现,不解析该文本。该字段不是 assistant 输出;消费方会将它分开呈现,它也不会进入 `subagent/end.lastAssistantMessage`。`dispose()` 是幂等的,会取消剩余工作,并等待结果结算以及子 agent 资源完全停稳。result 的拒绝只通过 `result` 本身报告;只有独立的资源释放失败,才会使 `dispose()` 被拒绝。`output` 与 `subagent/end` 事件的 `lastAssistantMessage` 使用导出的 `AssistantOutputFold`/`finalAssistantOutput` 辅助函数选取子 agent 最后一条非空 assistant 消息;若没有这类消息,则选取其累积的 assistant 文本。子 agent 两种输出均未产生时,`output` 为 `[]`,该事件字段缺省(终态结果约定归 [`SubagentResult`](../../../docs/subsystems/subagent.md#the-terminal-result-subagentresult) 所有)。
|
|
68
69
|
|
|
69
70
|
本地运行会在 `start()` 兑现前发布普通的子 agent/会话,把该共享会话 id 作为 `SubagentRun.id` 返回,以 `SubagentRun.localAgent` 公开准确的子 agent,把 `request.parent.session.id` 记录到子 agent 的 `parentSession` header,并在其初始轮次内追加已解析的描述符。远程提供方则生成 parent 作用域的生命周期 id,并返回 `localAgent: undefined`;由于没有本地 child 会话,其一次性运行不会进入基于追踪的枚举结果。
|
|
70
71
|
|
|
@@ -146,7 +147,7 @@ You are a delegated subagent: your permission scope was fixed when you were star
|
|
|
146
147
|
|
|
147
148
|
- **ACP 子 agent 仍为一次性,且无法通过追踪枚举**:ACP 运行在 parent 会话语料中没有本地 child 会话。ACP 的 `prepareContinuable` 需要在提供方专用描述符数据中持久化远端会话 id,以及逐子 agent 的继续执行能力声明,因为 ACP 的 `loadSession` 支持按子 agent 协商,而不是通过方法是否存在来确定。远程提供方还需要一份独立的 Activation 所有权约定,具备等效的经认证控制和子先于父的完全停稳保证,才能支持可继续子 agent。
|
|
148
149
|
- **无 host-user 继续执行**:`followup()` 要求确切在线直接父级。只有 `interrupt()` 接受持久化 parent 地址形式的用户授权,因为停止一个轮次是幂等的且不投递任何内容;未来 host 适配器需要具体的经认证交互,才能让该 seam 获得用户投递能力。
|
|
149
|
-
-
|
|
150
|
+
- **继续执行消息绝不 steering**:parent 到 child 的继续执行消息会排入后续 child 轮次。child 到 parent 的 report 是独立的 next-step 输入,可能延长 parent 已打开的轮次。
|
|
150
151
|
- **取消收敛期间存在唤醒缺口**:中断信号发出后、活动 driver 进入 idle 前被接受的唤醒型 follow-up 会保持排队,直到另一条唤醒发送到达。Issue #1838 负责 agent-loop 的唤醒锁存;普通会话取消也受此影响。
|
|
151
152
|
- **驻留仅限进程内**:Activation inbox 与所有权图不会在两个 harness 进程之间协调;对单个持久化存储的并发访问仍然需要持久化邮箱和跨进程租约协议。
|
|
152
153
|
- **不回放已接受但未记录的消息**:只有写入子 agent 会话日志的消息才能连同提供该消息的来源一起重建。崩溃可能丢失从未写入日志、已被接受的初始提示词或后续消息;此后一条经授权的消息可以冷恢复该子 agent,但丢失的消息不会自动回放。
|
package/lib/index.js
CHANGED
|
@@ -772,9 +772,10 @@ var SubagentContinuationManager = class {
|
|
|
772
772
|
const request = spec.request;
|
|
773
773
|
const parent = request.parent;
|
|
774
774
|
this.assertAdmitting(parent);
|
|
775
|
-
this.requirePersistence();
|
|
775
|
+
const persistence = this.requirePersistence();
|
|
776
776
|
assertSubagentMaxDepth(request.maxDepth);
|
|
777
|
-
const childId = SessionId(randomUUID());
|
|
777
|
+
const childId = spec.childId ?? SessionId(randomUUID());
|
|
778
|
+
this.assertChildIdAvailable(childId);
|
|
778
779
|
const childDepth = resolveChildDepth(parent, request.maxDepth);
|
|
779
780
|
const agentProvider = request.agentOptions?.provider ?? parent.options.provider;
|
|
780
781
|
const agentModel = request.agentOptions?.model ?? parent.options.model;
|
|
@@ -800,6 +801,16 @@ var SubagentContinuationManager = class {
|
|
|
800
801
|
return {
|
|
801
802
|
childId,
|
|
802
803
|
messageId: await this.locks.run(childId, async () => {
|
|
804
|
+
spec.signal.throwIfAborted();
|
|
805
|
+
this.assertAdmitting(parent);
|
|
806
|
+
this.assertChildIdAvailable(childId);
|
|
807
|
+
if (spec.childId !== void 0) {
|
|
808
|
+
const persisted = await persistence.listSnapshots(spec.signal);
|
|
809
|
+
spec.signal.throwIfAborted();
|
|
810
|
+
this.assertAdmitting(parent);
|
|
811
|
+
this.assertChildIdAvailable(childId);
|
|
812
|
+
if (persisted.some((snapshot) => snapshot.header.id === childId)) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
813
|
+
}
|
|
803
814
|
const activation = await this.materialize({
|
|
804
815
|
childId,
|
|
805
816
|
provider: spec.provider,
|
|
@@ -820,6 +831,10 @@ var SubagentContinuationManager = class {
|
|
|
820
831
|
})
|
|
821
832
|
};
|
|
822
833
|
}
|
|
834
|
+
/** Reject one child identity already owned by a live Agent or Session. */
|
|
835
|
+
assertChildIdAvailable(childId) {
|
|
836
|
+
if (this.ctx.agents.get(childId) !== void 0 || this.ctx.get("sessions")?.get(childId) !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
837
|
+
}
|
|
823
838
|
/**
|
|
824
839
|
* Deliver one later message to a known continuable child as its next FIFO
|
|
825
840
|
* turn. Routing depends only on Activation residency: a `running` Activation
|
|
@@ -941,7 +956,7 @@ var SubagentContinuationManager = class {
|
|
|
941
956
|
senderSessionId: activation.childId
|
|
942
957
|
}
|
|
943
958
|
});
|
|
944
|
-
if (delivery === "
|
|
959
|
+
if (delivery === "next-step") this.sendWaking(parent, message, () => {
|
|
945
960
|
this.sendReport(parent, message, delivery);
|
|
946
961
|
});
|
|
947
962
|
else this.sendReport(parent, message, delivery);
|
|
@@ -951,7 +966,7 @@ var SubagentContinuationManager = class {
|
|
|
951
966
|
* Perform one waking send to a parent, accounted against that parent's own
|
|
952
967
|
* Activation when it has one. Registering the id before the send is what
|
|
953
968
|
* keeps a continuation-managed parent from being judged quiescent in the
|
|
954
|
-
* window between
|
|
969
|
+
* window between a waking send and the microtask that admits it.
|
|
955
970
|
* @param parent - the exact live parent receiving the waking message.
|
|
956
971
|
* @param message - the message whose id is accounted.
|
|
957
972
|
* @param send - the synchronous waking send to perform.
|
|
@@ -964,7 +979,7 @@ var SubagentContinuationManager = class {
|
|
|
964
979
|
/** Send one report while translating only the parent's own rejection. */
|
|
965
980
|
sendReport(parent, message, delivery) {
|
|
966
981
|
try {
|
|
967
|
-
if (delivery === "
|
|
982
|
+
if (delivery === "next-step") parent.steer(message);
|
|
968
983
|
else parent.inject(message);
|
|
969
984
|
} catch (error) {
|
|
970
985
|
throw new SubagentError("direct parent is not live; report was not delivered", "PARENT_UNAVAILABLE", { cause: error });
|
|
@@ -1027,6 +1042,28 @@ var SubagentContinuationManager = class {
|
|
|
1027
1042
|
await Promise.all(materializations.map((materialization) => materialization.settled));
|
|
1028
1043
|
await this.disposeRoots(targetRoots, "scoped activation(s)");
|
|
1029
1044
|
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Release selected resident direct children of one exact live parent without
|
|
1047
|
+
* closing admission for the parent's other continuable children. Owned
|
|
1048
|
+
* descendants are released recursively through the same lifecycle.
|
|
1049
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
1050
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
1051
|
+
* @returns once every selected Activation released its handle.
|
|
1052
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target is not the
|
|
1053
|
+
* parent's direct continuable child or the parent identity is stale.
|
|
1054
|
+
*/
|
|
1055
|
+
async drainChildren(parent, childIds) {
|
|
1056
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
|
|
1057
|
+
const targets = [];
|
|
1058
|
+
for (const childId of new Set(childIds)) {
|
|
1059
|
+
const activation = this.activations.get(childId);
|
|
1060
|
+
if (activation === void 0) continue;
|
|
1061
|
+
if (activation.parentSession !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
|
|
1062
|
+
targets.push(activation);
|
|
1063
|
+
}
|
|
1064
|
+
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
1065
|
+
await this.disposeRoots(targets, "selected activation(s)");
|
|
1066
|
+
}
|
|
1030
1067
|
/** Dispose independent roots and report every branch failure after all settle. */
|
|
1031
1068
|
async disposeRoots(roots, failureSubject) {
|
|
1032
1069
|
const reasons = (await Promise.all(roots.map(async (activation) => {
|
|
@@ -2047,6 +2084,23 @@ const subagentIdentityProjectionDefinition = {
|
|
|
2047
2084
|
*
|
|
2048
2085
|
* @module @deepseek-ai/dsh-subagent/out-of-process
|
|
2049
2086
|
*/
|
|
2087
|
+
/** Maximum UTF-8 size of {@link SubagentResult.diagnostic}. */
|
|
2088
|
+
const MAX_SUBAGENT_DIAGNOSTIC_BYTES = 4096;
|
|
2089
|
+
const DIAGNOSTIC_TRUNCATION_SUFFIX = "\n[diagnostic truncated]";
|
|
2090
|
+
const utf8Encoder = new TextEncoder();
|
|
2091
|
+
const utf8Decoder = new TextDecoder();
|
|
2092
|
+
/**
|
|
2093
|
+
* Limit provider-authored failure detail without splitting a UTF-8 sequence.
|
|
2094
|
+
* @param diagnostic - safe diagnostic text produced by the provider.
|
|
2095
|
+
* @returns the original text, or a visibly truncated value within the limit.
|
|
2096
|
+
*/
|
|
2097
|
+
function limitSubagentDiagnostic(diagnostic) {
|
|
2098
|
+
const bytes = utf8Encoder.encode(diagnostic);
|
|
2099
|
+
if (bytes.byteLength <= MAX_SUBAGENT_DIAGNOSTIC_BYTES) return diagnostic;
|
|
2100
|
+
let prefixBytes = MAX_SUBAGENT_DIAGNOSTIC_BYTES - utf8Encoder.encode(DIAGNOSTIC_TRUNCATION_SUFFIX).byteLength;
|
|
2101
|
+
while ((bytes[prefixBytes] & 192) === 128) prefixBytes -= 1;
|
|
2102
|
+
return utf8Decoder.decode(bytes.subarray(0, prefixBytes)) + DIAGNOSTIC_TRUNCATION_SUFFIX;
|
|
2103
|
+
}
|
|
2050
2104
|
/**
|
|
2051
2105
|
* The capability advertisement of an out-of-process backend: NONE. A child in
|
|
2052
2106
|
* another process cannot honor parent-enforced start features
|
|
@@ -2160,8 +2214,11 @@ async function settleRunResult(parts) {
|
|
|
2160
2214
|
try {
|
|
2161
2215
|
parts.onError?.(toError(error), "error");
|
|
2162
2216
|
} catch {}
|
|
2217
|
+
const collected = parts.collectDiagnostic?.();
|
|
2218
|
+
const diagnostic = collected === void 0 ? void 0 : limitSubagentDiagnostic(collected);
|
|
2163
2219
|
return {
|
|
2164
2220
|
output: parts.collectOutput(),
|
|
2221
|
+
...diagnostic === void 0 ? {} : { diagnostic },
|
|
2165
2222
|
stopReason: "error"
|
|
2166
2223
|
};
|
|
2167
2224
|
} finally {
|
|
@@ -2204,6 +2261,11 @@ function subprocessRunHandle(parts) {
|
|
|
2204
2261
|
function finalText(blocks) {
|
|
2205
2262
|
return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
2206
2263
|
}
|
|
2264
|
+
/** Render a failed stop reason with optional provider-authored detail. */
|
|
2265
|
+
function failureDetail(result) {
|
|
2266
|
+
const stopReason = result.stopReason;
|
|
2267
|
+
return result.diagnostic === void 0 ? stopReason : `${stopReason}; diagnostic: ${result.diagnostic}`;
|
|
2268
|
+
}
|
|
2207
2269
|
/**
|
|
2208
2270
|
* Map a child result to the task outcome: completed carries final text,
|
|
2209
2271
|
* aborted is killed, and every other reason is failed without partial output.
|
|
@@ -2221,11 +2283,11 @@ function runOutcome(result) {
|
|
|
2221
2283
|
case "max-tokens":
|
|
2222
2284
|
case "refusal": return {
|
|
2223
2285
|
status: "failed",
|
|
2224
|
-
detail: result
|
|
2286
|
+
detail: failureDetail(result)
|
|
2225
2287
|
};
|
|
2226
2288
|
default: return {
|
|
2227
2289
|
status: "failed",
|
|
2228
|
-
detail:
|
|
2290
|
+
detail: failureDetail(result)
|
|
2229
2291
|
};
|
|
2230
2292
|
}
|
|
2231
2293
|
}
|
|
@@ -2409,6 +2471,21 @@ var SubagentRuntime = class extends Service {
|
|
|
2409
2471
|
await manager.drainDescendants(parents);
|
|
2410
2472
|
}
|
|
2411
2473
|
/**
|
|
2474
|
+
* Release selected resident continuable direct children of one exact live
|
|
2475
|
+
* parent. Other children of the same parent remain admitted and resident.
|
|
2476
|
+
* Absent targets and a manager-less composition are accepted no-ops.
|
|
2477
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
2478
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
2479
|
+
* @returns once every selected Activation released its `AgentHandle`.
|
|
2480
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
|
|
2481
|
+
* different parent or the supplied parent identity is stale.
|
|
2482
|
+
*/
|
|
2483
|
+
async drainContinuableChildren(parent, childIds) {
|
|
2484
|
+
const manager = this.continuations;
|
|
2485
|
+
if (manager === void 0) return;
|
|
2486
|
+
await manager.drainChildren(parent, childIds);
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2412
2489
|
* Enumerate the parent's direct session-backed subagents without loading or
|
|
2413
2490
|
* resuming an Agent and without any query service: the listing merges the live
|
|
2414
2491
|
* session store with optional session persistence (live-preferred) and
|
|
@@ -68,7 +68,7 @@ declare module '@deepseek-ai/dsh-llm' {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
/** Deployment scheduling policy for accepted child reports. */
|
|
71
|
-
export type SubagentReportDelivery = 'quiet' | '
|
|
71
|
+
export type SubagentReportDelivery = 'quiet' | 'next-step';
|
|
72
72
|
/** Options for one continuable child's report to its direct parent. */
|
|
73
73
|
export interface SubagentReportOptions {
|
|
74
74
|
/** Already-resolved parent scheduling policy. */
|
|
@@ -82,6 +82,12 @@ export interface ContinuableStartSpec {
|
|
|
82
82
|
readonly provider: string;
|
|
83
83
|
/** The initial delegation's short `description`, persisted as the child's creation label. */
|
|
84
84
|
readonly label: string;
|
|
85
|
+
/**
|
|
86
|
+
* Optional caller-reserved child identity. Omission preserves the manager's
|
|
87
|
+
* UUID allocation; supplying one lets a durable parent record provisioning
|
|
88
|
+
* before child materialization without a second identity handshake.
|
|
89
|
+
*/
|
|
90
|
+
readonly childId?: SessionId;
|
|
85
91
|
/**
|
|
86
92
|
* The delegation request. The manager reserves the stable child id, resolves
|
|
87
93
|
* the durable descriptor, and composes the child itself.
|
|
@@ -182,6 +188,8 @@ export declare class SubagentContinuationManager {
|
|
|
182
188
|
* @returns the durable child id and the accepted initial prompt's message id.
|
|
183
189
|
*/
|
|
184
190
|
startContinuable(spec: ContinuableStartSpec): Promise<ContinuableStart>;
|
|
191
|
+
/** Reject one child identity already owned by a live Agent or Session. */
|
|
192
|
+
private assertChildIdAvailable;
|
|
185
193
|
/**
|
|
186
194
|
* Deliver one later message to a known continuable child as its next FIFO
|
|
187
195
|
* turn. Routing depends only on Activation residency: a `running` Activation
|
|
@@ -245,7 +253,7 @@ export declare class SubagentContinuationManager {
|
|
|
245
253
|
* Perform one waking send to a parent, accounted against that parent's own
|
|
246
254
|
* Activation when it has one. Registering the id before the send is what
|
|
247
255
|
* keeps a continuation-managed parent from being judged quiescent in the
|
|
248
|
-
* window between
|
|
256
|
+
* window between a waking send and the microtask that admits it.
|
|
249
257
|
* @param parent - the exact live parent receiving the waking message.
|
|
250
258
|
* @param message - the message whose id is accounted.
|
|
251
259
|
* @param send - the synchronous waking send to perform.
|
|
@@ -273,6 +281,17 @@ export declare class SubagentContinuationManager {
|
|
|
273
281
|
* @throws an aggregate error after all scoped branches settle when any failed.
|
|
274
282
|
*/
|
|
275
283
|
drainDescendants(parents: readonly Agent[]): Promise<void>;
|
|
284
|
+
/**
|
|
285
|
+
* Release selected resident direct children of one exact live parent without
|
|
286
|
+
* closing admission for the parent's other continuable children. Owned
|
|
287
|
+
* descendants are released recursively through the same lifecycle.
|
|
288
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
289
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
290
|
+
* @returns once every selected Activation released its handle.
|
|
291
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target is not the
|
|
292
|
+
* parent's direct continuable child or the parent identity is stale.
|
|
293
|
+
*/
|
|
294
|
+
drainChildren(parent: Agent, childIds: readonly SessionId[]): Promise<void>;
|
|
276
295
|
/** Dispose independent roots and report every branch failure after all settle. */
|
|
277
296
|
private disposeRoots;
|
|
278
297
|
/** Return the retained member set for one exact scoped-teardown root. */
|
|
@@ -154,9 +154,10 @@ export class SubagentContinuationManager {
|
|
|
154
154
|
const request = spec.request;
|
|
155
155
|
const parent = request.parent;
|
|
156
156
|
this.assertAdmitting(parent);
|
|
157
|
-
this.requirePersistence();
|
|
157
|
+
const persistence = this.requirePersistence();
|
|
158
158
|
assertSubagentMaxDepth(request.maxDepth);
|
|
159
|
-
const childId = SessionId(randomUUID());
|
|
159
|
+
const childId = spec.childId ?? SessionId(randomUUID());
|
|
160
|
+
this.assertChildIdAvailable(childId);
|
|
160
161
|
const childDepth = resolveChildDepth(parent, request.maxDepth);
|
|
161
162
|
// Snapshot before any await: invalid descriptor JSON rejects the call
|
|
162
163
|
// before a child exists, and the detached value is what reaches the log.
|
|
@@ -184,6 +185,18 @@ export class SubagentContinuationManager {
|
|
|
184
185
|
const lineageSeedLength = prepared.seed?.length ?? 0;
|
|
185
186
|
const seed = seedDescriptorTurn(childId, prepared.seed, descriptor);
|
|
186
187
|
const messageId = await this.locks.run(childId, async () => {
|
|
188
|
+
spec.signal.throwIfAborted();
|
|
189
|
+
this.assertAdmitting(parent);
|
|
190
|
+
this.assertChildIdAvailable(childId);
|
|
191
|
+
if (spec.childId !== undefined) {
|
|
192
|
+
const persisted = await persistence.listSnapshots(spec.signal);
|
|
193
|
+
spec.signal.throwIfAborted();
|
|
194
|
+
this.assertAdmitting(parent);
|
|
195
|
+
this.assertChildIdAvailable(childId);
|
|
196
|
+
if (persisted.some(snapshot => snapshot.header.id === childId)) {
|
|
197
|
+
throw new SubagentError(`subagent "${childId}" already exists`, 'DUPLICATE_CHILD');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
187
200
|
const activation = await this.materialize({
|
|
188
201
|
childId,
|
|
189
202
|
provider: spec.provider,
|
|
@@ -197,6 +210,12 @@ export class SubagentContinuationManager {
|
|
|
197
210
|
});
|
|
198
211
|
return { childId, messageId };
|
|
199
212
|
}
|
|
213
|
+
/** Reject one child identity already owned by a live Agent or Session. */
|
|
214
|
+
assertChildIdAvailable(childId) {
|
|
215
|
+
if (this.ctx.agents.get(childId) !== undefined || this.ctx.get('sessions')?.get(childId) !== undefined) {
|
|
216
|
+
throw new SubagentError(`subagent "${childId}" already exists`, 'DUPLICATE_CHILD');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
200
219
|
/**
|
|
201
220
|
* Deliver one later message to a known continuable child as its next FIFO
|
|
202
221
|
* turn. Routing depends only on Activation residency: a `running` Activation
|
|
@@ -347,7 +366,7 @@ export class SubagentContinuationManager {
|
|
|
347
366
|
senderSessionId: activation.childId,
|
|
348
367
|
},
|
|
349
368
|
});
|
|
350
|
-
if (delivery === '
|
|
369
|
+
if (delivery === 'next-step') {
|
|
351
370
|
this.sendWaking(parent, message, () => { this.sendReport(parent, message, delivery); });
|
|
352
371
|
}
|
|
353
372
|
else {
|
|
@@ -359,7 +378,7 @@ export class SubagentContinuationManager {
|
|
|
359
378
|
* Perform one waking send to a parent, accounted against that parent's own
|
|
360
379
|
* Activation when it has one. Registering the id before the send is what
|
|
361
380
|
* keeps a continuation-managed parent from being judged quiescent in the
|
|
362
|
-
* window between
|
|
381
|
+
* window between a waking send and the microtask that admits it.
|
|
363
382
|
* @param parent - the exact live parent receiving the waking message.
|
|
364
383
|
* @param message - the message whose id is accounted.
|
|
365
384
|
* @param send - the synchronous waking send to perform.
|
|
@@ -376,8 +395,8 @@ export class SubagentContinuationManager {
|
|
|
376
395
|
/** Send one report while translating only the parent's own rejection. */
|
|
377
396
|
sendReport(parent, message, delivery) {
|
|
378
397
|
try {
|
|
379
|
-
if (delivery === '
|
|
380
|
-
parent.
|
|
398
|
+
if (delivery === 'next-step')
|
|
399
|
+
parent.steer(message);
|
|
381
400
|
else
|
|
382
401
|
parent.inject(message);
|
|
383
402
|
}
|
|
@@ -471,6 +490,38 @@ export class SubagentContinuationManager {
|
|
|
471
490
|
await Promise.all(materializations.map(materialization => materialization.settled));
|
|
472
491
|
await this.disposeRoots(targetRoots, 'scoped activation(s)');
|
|
473
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Release selected resident direct children of one exact live parent without
|
|
495
|
+
* closing admission for the parent's other continuable children. Owned
|
|
496
|
+
* descendants are released recursively through the same lifecycle.
|
|
497
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
498
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
499
|
+
* @returns once every selected Activation released its handle.
|
|
500
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target is not the
|
|
501
|
+
* parent's direct continuable child or the parent identity is stale.
|
|
502
|
+
*/
|
|
503
|
+
async drainChildren(parent, childIds) {
|
|
504
|
+
if (this.ctx.agents.get(parent.id) !== parent) {
|
|
505
|
+
throw new SubagentError('selected child teardown requires the exact live parent agent', 'UNAUTHORIZED');
|
|
506
|
+
}
|
|
507
|
+
const targets = [];
|
|
508
|
+
for (const childId of new Set(childIds)) {
|
|
509
|
+
const activation = this.activations.get(childId);
|
|
510
|
+
if (activation === undefined)
|
|
511
|
+
continue;
|
|
512
|
+
if (activation.parentSession !== parent.id || !activation.ancestry.has(parent)) {
|
|
513
|
+
throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, 'UNAUTHORIZED');
|
|
514
|
+
}
|
|
515
|
+
targets.push(activation);
|
|
516
|
+
}
|
|
517
|
+
// Open every transaction before the first await so cancellation propagates
|
|
518
|
+
// across the selected roots in one synchronous span.
|
|
519
|
+
for (const activation of targets) {
|
|
520
|
+
const disposal = this.dispose(activation);
|
|
521
|
+
void disposal.catch(() => undefined);
|
|
522
|
+
}
|
|
523
|
+
await this.disposeRoots(targets, 'selected activation(s)');
|
|
524
|
+
}
|
|
474
525
|
/** Dispose independent roots and report every branch failure after all settle. */
|
|
475
526
|
async disposeRoots(roots, failureSubject) {
|
|
476
527
|
const failures = await Promise.all(roots.map(async (activation) => {
|
|
@@ -814,7 +865,7 @@ export class SubagentContinuationManager {
|
|
|
814
865
|
* @returns the accepted message id.
|
|
815
866
|
*/
|
|
816
867
|
admitWaking(activation, messageId, send) {
|
|
817
|
-
//
|
|
868
|
+
// Waking Agent sends publish inbox events synchronously, so observers must
|
|
818
869
|
// see this Activation as busy before the call begins.
|
|
819
870
|
activation.accepted.add(messageId);
|
|
820
871
|
try {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -182,6 +182,17 @@ export declare class SubagentRuntime extends Service {
|
|
|
182
182
|
* @throws an aggregate error after all branches settle when any failed.
|
|
183
183
|
*/
|
|
184
184
|
drainContinuableDescendants(parents: readonly Agent[]): Promise<void>;
|
|
185
|
+
/**
|
|
186
|
+
* Release selected resident continuable direct children of one exact live
|
|
187
|
+
* parent. Other children of the same parent remain admitted and resident.
|
|
188
|
+
* Absent targets and a manager-less composition are accepted no-ops.
|
|
189
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
190
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
191
|
+
* @returns once every selected Activation released its `AgentHandle`.
|
|
192
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
|
|
193
|
+
* different parent or the supplied parent identity is stale.
|
|
194
|
+
*/
|
|
195
|
+
drainContinuableChildren(parent: Agent, childIds: readonly SessionId[]): Promise<void>;
|
|
185
196
|
/**
|
|
186
197
|
* Enumerate the parent's direct session-backed subagents without loading or
|
|
187
198
|
* resuming an Agent and without any query service: the listing merges the live
|
package/lib/types/index.js
CHANGED
|
@@ -173,6 +173,22 @@ export class SubagentRuntime extends Service {
|
|
|
173
173
|
return;
|
|
174
174
|
await manager.drainDescendants(parents);
|
|
175
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Release selected resident continuable direct children of one exact live
|
|
178
|
+
* parent. Other children of the same parent remain admitted and resident.
|
|
179
|
+
* Absent targets and a manager-less composition are accepted no-ops.
|
|
180
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
181
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
182
|
+
* @returns once every selected Activation released its `AgentHandle`.
|
|
183
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
|
|
184
|
+
* different parent or the supplied parent identity is stale.
|
|
185
|
+
*/
|
|
186
|
+
async drainContinuableChildren(parent, childIds) {
|
|
187
|
+
const manager = this.continuations;
|
|
188
|
+
if (manager === undefined)
|
|
189
|
+
return;
|
|
190
|
+
await manager.drainChildren(parent, childIds);
|
|
191
|
+
}
|
|
176
192
|
/**
|
|
177
193
|
* Enumerate the parent's direct session-backed subagents without loading or
|
|
178
194
|
* resuming an Agent and without any query service: the listing merges the live
|
|
@@ -69,6 +69,8 @@ export interface RunResultSettlement {
|
|
|
69
69
|
attempt: () => Promise<SubagentResult>;
|
|
70
70
|
/** Snapshot the provider exposes when cancellation or failure wins settlement. */
|
|
71
71
|
collectOutput: () => ContentBlock[];
|
|
72
|
+
/** Snapshot safe provider-authored detail when a failure wins settlement. */
|
|
73
|
+
collectDiagnostic?: (() => string | undefined) | undefined;
|
|
72
74
|
/** Whether local cancellation settled before the attempt's outcome is observed. */
|
|
73
75
|
cancelled: () => boolean;
|
|
74
76
|
/** Diagnostic sink for a failure flattened to a stop reason; a throw from it is contained. */
|
|
@@ -12,6 +12,28 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { accessSync, constants, statSync } from 'node:fs';
|
|
14
14
|
import { isAbsolute, resolve } from 'node:path';
|
|
15
|
+
/** Maximum UTF-8 size of {@link SubagentResult.diagnostic}. */
|
|
16
|
+
const MAX_SUBAGENT_DIAGNOSTIC_BYTES = 4_096;
|
|
17
|
+
const DIAGNOSTIC_TRUNCATION_SUFFIX = '\n[diagnostic truncated]';
|
|
18
|
+
const utf8Encoder = new TextEncoder();
|
|
19
|
+
const utf8Decoder = new TextDecoder();
|
|
20
|
+
/**
|
|
21
|
+
* Limit provider-authored failure detail without splitting a UTF-8 sequence.
|
|
22
|
+
* @param diagnostic - safe diagnostic text produced by the provider.
|
|
23
|
+
* @returns the original text, or a visibly truncated value within the limit.
|
|
24
|
+
*/
|
|
25
|
+
function limitSubagentDiagnostic(diagnostic) {
|
|
26
|
+
const bytes = utf8Encoder.encode(diagnostic);
|
|
27
|
+
if (bytes.byteLength <= MAX_SUBAGENT_DIAGNOSTIC_BYTES)
|
|
28
|
+
return diagnostic;
|
|
29
|
+
const suffixBytes = utf8Encoder.encode(DIAGNOSTIC_TRUNCATION_SUFFIX).byteLength;
|
|
30
|
+
let prefixBytes = MAX_SUBAGENT_DIAGNOSTIC_BYTES - suffixBytes;
|
|
31
|
+
while ((bytes[prefixBytes] & 0b1100_0000) === 0b1000_0000) {
|
|
32
|
+
prefixBytes -= 1;
|
|
33
|
+
}
|
|
34
|
+
return utf8Decoder.decode(bytes.subarray(0, prefixBytes))
|
|
35
|
+
+ DIAGNOSTIC_TRUNCATION_SUFFIX;
|
|
36
|
+
}
|
|
15
37
|
/**
|
|
16
38
|
* The capability advertisement of an out-of-process backend: NONE. A child in
|
|
17
39
|
* another process cannot honor parent-enforced start features
|
|
@@ -148,7 +170,15 @@ export async function settleRunResult(parts) {
|
|
|
148
170
|
catch {
|
|
149
171
|
// The diagnostic sink cannot reject the run result.
|
|
150
172
|
}
|
|
151
|
-
|
|
173
|
+
const collected = parts.collectDiagnostic?.();
|
|
174
|
+
const diagnostic = collected === undefined
|
|
175
|
+
? undefined
|
|
176
|
+
: limitSubagentDiagnostic(collected);
|
|
177
|
+
return {
|
|
178
|
+
output: parts.collectOutput(),
|
|
179
|
+
...diagnostic === undefined ? {} : { diagnostic },
|
|
180
|
+
stopReason: 'error',
|
|
181
|
+
};
|
|
152
182
|
}
|
|
153
183
|
finally {
|
|
154
184
|
parts.signal.removeEventListener('abort', parts.onAbort);
|
|
@@ -12,6 +12,13 @@ function finalText(blocks) {
|
|
|
12
12
|
.map(block => block.text)
|
|
13
13
|
.join('');
|
|
14
14
|
}
|
|
15
|
+
/** Render a failed stop reason with optional provider-authored detail. */
|
|
16
|
+
function failureDetail(result) {
|
|
17
|
+
const stopReason = result.stopReason;
|
|
18
|
+
return result.diagnostic === undefined
|
|
19
|
+
? stopReason
|
|
20
|
+
: `${stopReason}; diagnostic: ${result.diagnostic}`;
|
|
21
|
+
}
|
|
15
22
|
/**
|
|
16
23
|
* Map a child result to the task outcome: completed carries final text,
|
|
17
24
|
* aborted is killed, and every other reason is failed without partial output.
|
|
@@ -27,10 +34,10 @@ function runOutcome(result) {
|
|
|
27
34
|
case 'error':
|
|
28
35
|
case 'max-tokens':
|
|
29
36
|
case 'refusal':
|
|
30
|
-
return { status: 'failed', detail: result
|
|
31
|
-
// Merge-extensible reasons remain failures with
|
|
37
|
+
return { status: 'failed', detail: failureDetail(result) };
|
|
38
|
+
// Merge-extensible reasons remain failures with provider-authored detail.
|
|
32
39
|
default:
|
|
33
|
-
return { status: 'failed', detail:
|
|
40
|
+
return { status: 'failed', detail: failureDetail(result) };
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
/**
|
package/lib/types/types.d.ts
CHANGED
|
@@ -218,6 +218,13 @@ export interface SubagentResult {
|
|
|
218
218
|
* schema-agnostic.
|
|
219
219
|
*/
|
|
220
220
|
readonly structured?: unknown;
|
|
221
|
+
/**
|
|
222
|
+
* Provider-authored, non-assistant failure detail for a non-`completed`
|
|
223
|
+
* result. Providers keep this text free of tool inputs, file contents,
|
|
224
|
+
* environment values, credentials, and raw protocol payloads, and limit it
|
|
225
|
+
* to 4096 UTF-8 bytes. Consumers present it separately from {@link output}.
|
|
226
|
+
*/
|
|
227
|
+
readonly diagnostic?: string;
|
|
221
228
|
/** Why the run ended. A non-`completed` reason means `output` may be partial. */
|
|
222
229
|
readonly stopReason: SubagentStopReason;
|
|
223
230
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-subagent",
|
|
3
3
|
"description": "Abstract subagent seam (ctx.subagents): named-provider registry for delegating to child agents",
|
|
4
|
-
"version": "0.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.8",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -40,21 +40,21 @@
|
|
|
40
40
|
"zod": "^4.4.3"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@deepseek-ai/dsh-agent": "^0.1.0-rc.
|
|
44
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
48
|
-
"@deepseek-ai/dsh-sandbox
|
|
49
|
-
"@deepseek-ai/dsh-
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-session": "^0.1.0-rc.
|
|
52
|
-
"@deepseek-ai/dsh-session-projection
|
|
53
|
-
"@deepseek-ai/dsh-session-
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
43
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.8",
|
|
44
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.8",
|
|
45
|
+
"@deepseek-ai/dsh-brand": "^0.1.0-rc.8",
|
|
46
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
|
|
47
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
|
|
48
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.0-rc.8",
|
|
49
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.1.0-rc.8",
|
|
50
|
+
"@deepseek-ai/dsh-scope": "^0.1.0-rc.8",
|
|
51
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.8",
|
|
52
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.0-rc.8",
|
|
53
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.8",
|
|
54
|
+
"@deepseek-ai/dsh-session-projection-cache": "^0.1.0-rc.8",
|
|
55
|
+
"@deepseek-ai/dsh-jobs": "^0.1.0-rc.8",
|
|
56
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
|
|
57
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.0-rc.8",
|
|
58
58
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
@@ -84,23 +84,23 @@
|
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
|
-
"@deepseek-ai/dsh-agent
|
|
88
|
-
"@deepseek-ai/dsh-
|
|
89
|
-
"@deepseek-ai/dsh-
|
|
90
|
-
"@deepseek-ai/dsh-
|
|
91
|
-
"@deepseek-ai/dsh-
|
|
92
|
-
"@deepseek-ai/dsh-
|
|
93
|
-
"@deepseek-ai/dsh-sandbox-policy": "^0.1.0-rc.
|
|
94
|
-
"@deepseek-ai/dsh-
|
|
95
|
-
"@deepseek-ai/dsh-session
|
|
96
|
-
"@deepseek-ai/dsh-session-
|
|
97
|
-
"@deepseek-ai/dsh-
|
|
98
|
-
"@deepseek-ai/dsh-session-projection-cache": "^0.1.0-rc.
|
|
99
|
-
"@deepseek-ai/dsh-storage": "^0.1.0-rc.
|
|
100
|
-
"@deepseek-ai/dsh-
|
|
101
|
-
"@deepseek-ai/
|
|
102
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.
|
|
103
|
-
"@deepseek-ai/dsh-
|
|
104
|
-
"@deepseek-ai/
|
|
87
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.8",
|
|
88
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.8",
|
|
89
|
+
"@deepseek-ai/dsh-brand": "^0.1.0-rc.8",
|
|
90
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
|
|
91
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
|
|
92
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.0-rc.8",
|
|
93
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.1.0-rc.8",
|
|
94
|
+
"@deepseek-ai/dsh-scope": "^0.1.0-rc.8",
|
|
95
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.8",
|
|
96
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.8",
|
|
97
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.0-rc.8",
|
|
98
|
+
"@deepseek-ai/dsh-session-projection-cache": "^0.1.0-rc.8",
|
|
99
|
+
"@deepseek-ai/dsh-storage": "^0.1.0-rc.8",
|
|
100
|
+
"@deepseek-ai/dsh-storage-domain": "^0.1.0-rc.8",
|
|
101
|
+
"@deepseek-ai/dsh-jobs": "^0.1.0-rc.8",
|
|
102
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
|
|
103
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.0-rc.8",
|
|
104
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
105
105
|
}
|
|
106
106
|
}
|