fix(subagent): preserve published run failures

This commit is contained in:
Dudu-0223
2026-07-31 14:02:03 +08:00
committed by Tianyi Cui
parent 8b0a7a5d8d
commit a977ef30ee
57 changed files with 444 additions and 208 deletions

View File

@@ -134,6 +134,47 @@ function stopReasonError(result: SubagentResult): string | undefined {
}
}
type ForegroundToolResult = {
readonly kind: 'foreground'
readonly runId: SubagentRun['id']
readonly output: JsonValue[]
}
/**
* Collect and release one foreground run without letting disposal replace an
* independent result failure.
*/
async function settleForegroundRun(run: SubagentRun): Promise<ForegroundToolResult> {
const [execution] = await Promise.allSettled([
run.result.then((result): ForegroundToolResult => {
const error = stopReasonError(result)
if (error !== undefined) {
// The registry converts this throw to isError; partial output is not success.
throw new Error(error)
}
return {
kind: 'foreground',
runId: run.id,
// Content blocks already cross durable JSON boundaries elsewhere;
// the registry performs the authoritative lossless snapshot here.
output: result.output as unknown as JsonValue[],
}
}),
])
const [disposal] = await Promise.allSettled([Promise.resolve().then(() => run.dispose())])
if (execution.status === 'rejected') {
if (disposal.status === 'rejected') {
throw new AggregateError(
[execution.reason, disposal.reason],
`subagent run failed: ${String(execution.reason)}; dispose failed: ${String(disposal.reason)}`,
)
}
throw execution.reason
}
if (disposal.status === 'rejected') throw disposal.reason
return execution.value
}
/**
* Model-facing wording from the provider's conversation-history descriptor
* ({@link SubagentProvider.inheritsParentContext}).
@@ -335,25 +376,7 @@ export function apply(ctx: Context, config: Config): void {
...request,
signal: exec.signal,
})
try {
const result = await run.result
const error = stopReasonError(result)
if (error !== undefined) {
// The registry converts this throw to isError; partial output is not success.
throw new Error(error)
}
return {
kind: 'foreground' as const,
runId: run.id,
// Content blocks already cross durable JSON boundaries elsewhere;
// the registry performs the authoritative lossless snapshot here.
output: result.output as unknown as JsonValue[],
}
} finally {
// Dispose before returning so no child session outlives the call.
await run.dispose()
}
return settleForegroundRun(run)
},
}))
}