fix(session): type turn/end error as one structured failure

TurnEndReasonMap.error now carries a single `error: LlmFailure` field:
an LlmError keeps its structured facts, any other error flattens to
errorChain text under the UNKNOWN code. Consumers read message/code
directly instead of defending against an unknown union — this also fixes
errorChain() rendering structured failures as '[object Object]' in the
TUI and ACP error paths. Document the turn-stopping contract: a
concludesTurn result never short-circuits already-submitted next-step
work (same-step additionalContexts or racing steering still runs), data
decides.
This commit is contained in:
_Kerman
2026-08-03 16:33:23 +08:00
parent 8b975edf44
commit a597763393
30 changed files with 81 additions and 54 deletions
+15 -4
View File
@@ -60,6 +60,20 @@ function requestProposal(header: EpochHeader): LlmCallConfig {
}
/** Drives one session through turn and step boundaries. */
/**
* Shape a caught turn error into its durable reason: every failure is a
* structured LlmFailure — an `LlmError` keeps its facts, anything else
* flattens to `errorChain` text under the `UNKNOWN` code.
*/
function turnErrorReason(error: unknown): Extract<TurnEndReason, { kind: 'error' }> {
return {
kind: 'error',
error: error instanceof LlmError
? error.failure
: { message: errorChain(error), code: 'UNKNOWN' },
}
}
export class ReactLoopAgent implements Agent {
readonly inbox: Inbox
private phase: Phase
@@ -279,10 +293,7 @@ export class ReactLoopAgent implements Agent {
turnEnds = { kind: 'aborted', reason: signal.reason as AgentCancelCause }
throw error
}
turnEnds = {
kind: 'error',
error: error instanceof LlmError ? error.failure : errorChain(error),
}
turnEnds = turnErrorReason(error)
this.throwError(error)
} finally {
try {