mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(agent-loop): surface throwing step-end listener as turn error via failTurn
closeStep() previously caught and silently swallowed a throw from
agent/step-end emit. In the normal no-tool/no-steering path, this
caused runTurn to reach closeTurn(true) with reason still
{kind:completed}, so the session recorded a completed turn with
zero error events — even though a plugin had failed at a loop
boundary. This violates the contract that a throwing plugin is
contained as a turn error, not a silent success.
Now the catch calls failTurn(toError(error)), which appends the
single error event and sets reason={kind:error,…}. failTurn is
idempotent (errorReported guard), so existing error paths that
call failTurn after closeStep are unaffected.
Add regression test: a throwing agent/step-end listener during a
successful step now produces exactly one error event, a turn/end
with reason error, balanced boundaries (step/end before turn/end),
and a surviving loop.
This commit is contained in:
@@ -180,9 +180,12 @@ async function runTurn(ctx: Context, agent: LoopAgent, handle: LoopHandle, turn:
|
||||
session.append('step/end', { turn, step })
|
||||
try {
|
||||
ctx.emit('agent/step-end', agent, turn, step)
|
||||
} catch {
|
||||
// contained: step/end is already recorded, so balance holds; a throwing
|
||||
// step-end listener is the listener's bug, not the loop's.
|
||||
} catch (error: unknown) {
|
||||
// step/end is already recorded so balance holds; surface the throwing
|
||||
// listener as a turn error via failTurn (idempotent). This prevents a
|
||||
// throwing step-end listener from producing a silent "completed" turn
|
||||
// when the step itself succeeded (the normal-path closeStep call).
|
||||
failTurn(toError(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user