1
/**2
* Settlement of one ONE-SHOT subagent run into a background-Task outcome. Only3
* the one-shot background path uses Jobs; continuable children have no Task,4
* no per-message result, and no Task cancellation.5
*6
* @module @deepseek-ai/dsh-subagent/run-settlement7
*/9
import type { ContentBlock } from '@deepseek-ai/dsh-llm'10
import type { JobOutcome } from '@deepseek-ai/dsh-jobs'11
import type { SubagentResult, SubagentRun } from './types.ts'13
/** Flatten a child's final output blocks to the task's final text. */14
function finalText(blocks: readonly ContentBlock[]): string {15
return blocks16
.filter((block): block is Extract<ContentBlock, { type: 'text' }> => block.type === 'text')17
.map(block => block.text)18
.join('')19
}21
/** Render a failed stop reason with optional provider-authored detail. */22
function failureDetail(result: SubagentResult): string {23
const stopReason = result.stopReason24
return result.diagnostic === undefined25
? stopReason26
: `${stopReason}; diagnostic: ${result.diagnostic}`27
}29
/**30
* Map a child result to the task outcome: completed carries final text, local31
* cancellation (`aborted` without a diagnostic) is killed, and provider-32
* diagnosed remote aborts plus every other reason are failed without partial33
* output.34
* @param result - child terminal result.35
* @returns outcome for the `ctx.jobs` registration.36
*/37
function runOutcome(result: SubagentResult): JobOutcome {38
switch (result.stopReason) {39
case 'completed':40
return { status: 'completed', result: finalText(result.output) }41
case 'aborted':42
return result.diagnostic === undefined43
? { status: 'killed' }44
: { status: 'failed', detail: failureDetail(result) }45
case 'error':46
case 'max-tokens':47
case 'refusal':48
return { status: 'failed', detail: failureDetail(result) }49
// Merge-extensible reasons remain failures with provider-authored detail.50
default:51
return { status: 'failed', detail: failureDetail(result) }52
}53
}55
/**56
* Await the child result, dispose the run, then return its task outcome. Result57
* and disposal failures become `failed`; when both fail, both details survive.58
* @param run - live run to settle and release.59
* @returns outcome after child resources are released.60
*/61
export async function settleRun(run: SubagentRun): Promise<JobOutcome> {62
let outcome: JobOutcome63
try {64
outcome = runOutcome(await run.result)65
} catch (error: unknown) {66
outcome = { status: 'failed', detail: String(error) }67
}68
try {69
await run.dispose()70
} catch (error: unknown) {71
const prefix = outcome.detail === undefined ? '' : `${outcome.detail}; `72
return { status: 'failed', detail: `${prefix}dispose failed: ${String(error)}` }73
}74
return outcome75
}