Version: main at 51c8356 (also present in 92e2fc6).
A run that ends in a handoff (refusal or escalation) shows nothing about it until the conversation is reopened.
TaskChat renders the explanation from m.handoff / m.incompleteReason, and both are only ever populated by msgsFromTurns, which runs when a saved session is hydrated:
// ui/src/lib/conversation.ts (msgsFromTurns)
incompleteReason: (t as { incomplete_reason?: string }).incomplete_reason || undefined,
handoff: (t as { handoff?: Record<string, unknown> | null }).handoff || undefined,
The live paths keep only the status and drop the terminal event's response.incomplete_details:
// ui/src/lib/conversation.ts
case 'response.incomplete':
busUpdateLast(sid, (a) => { a.status = 'incomplete'; });
Steps
- Start a turn on a harness whose run can hand off, and watch it live.
- The turn settles as incomplete with the generic badge and no reason or handoff text.
- Reload the page or reopen the conversation — the handoff block appears.
Expected: the same explanation for a turn that just finished as for one read back from history.
Fix shape: copy incomplete_details (reason and handoff) from the terminal response into the assistant message in the live paths, both the POST stream and the event bus, as msgsFromTurns does.
Version:
mainat 51c8356 (also present in 92e2fc6).A run that ends in a handoff (refusal or escalation) shows nothing about it until the conversation is reopened.
TaskChatrenders the explanation fromm.handoff/m.incompleteReason, and both are only ever populated bymsgsFromTurns, which runs when a saved session is hydrated:The live paths keep only the status and drop the terminal event's
response.incomplete_details:Steps
Expected: the same explanation for a turn that just finished as for one read back from history.
Fix shape: copy
incomplete_details(reason and handoff) from the terminal response into the assistant message in the live paths, both the POST stream and the event bus, asmsgsFromTurnsdoes.