fix(subagent): keep output past an empty terminal message with one selection rule

A max-tokens step that assembled only tool-call blocks appends an
EMPTY-content assistant/message (the usage host). Three consumers each
hand-rolled output selection and all let it erase the child's real
answer: the in-process readResult and the Activation subagent/end
capture took the last message unfiltered, and the SDK backend let any
message beat its streamed-text fallback; the in-process driver also had
no streamed-text fallback for cancelled turns.

dsh-subagent now owns the canonical rule in src/assistant-output.ts
(last non-empty assistant message, else the accumulated text-delta
stream) and all three consumers apply it. Regression tests in all three
packages fail under the previous selections.

Closes #1514
This commit is contained in:
Hypatia May
2026-08-10 11:15:01 +08:00
parent abaf8f5061
commit 1db1cda464
29 changed files with 344 additions and 66 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/subagent.md
subagent.md: 99c54e0696c9c3585c50285ecb69b14c90d83c11
subagent.zh.md: c5a79247a81f5174662f2b5d1ed2d3c20cf6c672
subagent.md: 4d72ca7a3fd42438e46a50558287e0e85450963d
subagent.zh.md: e8b1c948fa0ac3fe47097f0dd8f2532bdb4a8d20

View File

@@ -293,7 +293,12 @@ The outcome of a one-shot run, resolved by `SubagentRun.result`. `structured` is
* The terminal outcome of a subagent run, resolved by {@link SubagentRun.result}.
*/
interface SubagentResult {
/** The child's final assistant output (the last assistant message's content). */
/**
* The child's final assistant output: the content of the last NON-EMPTY
* assistant message (an empty-content message hosts only usage and is
* skipped), else the text streamed before the turn was cut short, or `[]`
* when the child produced none.
*/
readonly output: ContentBlock[]
/**
* The structured result after a requested `outputSchema` was successfully
@@ -613,7 +618,7 @@ async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>
Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) · [MessageId](llm-streaming.md) · [SessionId](core.md)
Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:168`](../../packages/subagent/subagent/src/index.ts)
<a id="subagent-events"></a>
@@ -639,7 +644,7 @@ A published child settled. Scope-filtered dispatch uses the same delegating pare
Types: [Scoped](scope.md)
Source: [`packages/subagent/subagent/src/index.ts:162`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:163`](../../packages/subagent/subagent/src/index.ts)
<a id="subagentprovider-added--emit"></a>
@@ -656,7 +661,7 @@ A provider became resolvable in the registry.
'subagent/provider-added'(provider: SubagentProvider): void
```
Source: [`packages/subagent/subagent/src/index.ts:136`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:137`](../../packages/subagent/subagent/src/index.ts)
<a id="subagentprovider-removed--emit"></a>
@@ -673,7 +678,7 @@ A provider left the registry. Accepted runs remain holder-owned.
'subagent/provider-removed'(name: string): void
```
Source: [`packages/subagent/subagent/src/index.ts:142`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:143`](../../packages/subagent/subagent/src/index.ts)
<a id="subagentstart--emit"></a>
@@ -697,5 +702,5 @@ A provider established a published child. For in-process providers, `ctx.agents.
Types: [Scoped](scope.md)
Source: [`packages/subagent/subagent/src/index.ts:153`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:154`](../../packages/subagent/subagent/src/index.ts)
<!-- END GENERATED cordis-surface -->

View File

@@ -293,7 +293,12 @@ type SubagentDescendantListEntry = SubagentListEntry & {
* The terminal outcome of a subagent run, resolved by {@link SubagentRun.result}.
*/
interface SubagentResult {
/** The child's final assistant output (the last assistant message's content). */
/**
* The child's final assistant output: the content of the last NON-EMPTY
* assistant message (an empty-content message hosts only usage and is
* skipped), else the text streamed before the turn was cut short, or `[]`
* when the child produced none.
*/
readonly output: ContentBlock[]
/**
* The structured result after a requested `outputSchema` was successfully
@@ -615,7 +620,7 @@ async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>
Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) · [MessageId](llm-streaming.md) · [SessionId](core.md)
Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:168`](../../packages/subagent/subagent/src/index.ts)
<a id="subagent-events"></a>
@@ -641,7 +646,7 @@ A published child settled. Scope-filtered dispatch uses the same delegating pare
Types: [Scoped](scope.md)
Source: [`packages/subagent/subagent/src/index.ts:162`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:163`](../../packages/subagent/subagent/src/index.ts)
<a id="subagentprovider-added--emit"></a>
@@ -658,7 +663,7 @@ A provider became resolvable in the registry.
'subagent/provider-added'(provider: SubagentProvider): void
```
Source: [`packages/subagent/subagent/src/index.ts:136`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:137`](../../packages/subagent/subagent/src/index.ts)
<a id="subagentprovider-removed--emit"></a>
@@ -675,7 +680,7 @@ A provider left the registry. Accepted runs remain holder-owned.
'subagent/provider-removed'(name: string): void
```
Source: [`packages/subagent/subagent/src/index.ts:142`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:143`](../../packages/subagent/subagent/src/index.ts)
<a id="subagentstart--emit"></a>
@@ -699,5 +704,5 @@ A provider established a published child. For in-process providers, `ctx.agents.
Types: [Scoped](scope.md)
Source: [`packages/subagent/subagent/src/index.ts:153`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:154`](../../packages/subagent/subagent/src/index.ts)
<!-- END GENERATED cordis-surface -->