feat(desktop): hoisted todo widget, JSON tool summaries, history grouping & timer fixes

- Hoist todo to first-class widget (shadcn checkboxes, brand colors, no
  tool-accordion). Header derives label from active task; non-active rows fade.
- Replace raw JSON dumps with structured key/value summaries via
  formatToolResultSummary; nested error extraction for clearer failures.
- Fix loaded-session grouping: stitch interleaved assistant/tool iterations
  into one bubble instead of orphaned synthetic messages.
- Stable tool/thinking timers via keyed registry so unmount/scroll doesn't
  reset elapsed counts; gate "running" on real live thread state.
- Reorganize chat-only assistant-ui components under components/chat/.
This commit is contained in:
Brooklyn Nicholson
2026-05-11 16:34:25 -04:00
parent 4b3839a8ee
commit 4dd9732a94
34 changed files with 1234 additions and 306 deletions
@@ -62,6 +62,15 @@ interface QueuedStreamDeltas {
const STREAM_DELTA_FLUSH_MS = 16
// Anonymous progress events that carry todos but no name still belong to the
// todo stream; named todo events are obviously routed there too.
function toTodoPayload(payload: GatewayEventPayload | undefined): GatewayEventPayload | undefined {
if (!payload) {return undefined}
const isTodo = payload.name === 'todo' || (!payload.name && Object.hasOwn(payload, 'todos'))
return isTodo ? { ...payload, name: 'todo', tool_id: payload.tool_id || 'todo-live' } : undefined
}
export function useMessageStream({
activeSessionIdRef,
hydrateFromStoredSession,
@@ -552,17 +561,13 @@ export function useMessageStream({
setCurrentUsage(current => ({ ...current, ...payload.usage }))
}
} else if (event.type === 'tool.start' || event.type === 'tool.progress' || event.type === 'tool.generating') {
if (!sessionId) {
return
}
if (!sessionId) {return}
flushQueuedDeltas(sessionId)
upsertToolCall(sessionId, payload, 'running')
upsertToolCall(sessionId, toTodoPayload(payload) ?? payload, 'running')
} else if (event.type === 'tool.complete') {
if (sessionId) {
flushQueuedDeltas(sessionId)
upsertToolCall(sessionId, payload, 'complete')
upsertToolCall(sessionId, toTodoPayload(payload) ?? payload, 'complete')
}
if (typeof payload?.inline_diff === 'string' && payload.inline_diff.trim()) {