diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 70db5adc..feedcce7 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1431,11 +1431,38 @@ window.marked = marked; return false; } function renderStream(v, api) { - // Drop session init, claude's result line, rate-limit — noise. - // TurnEnd communicates pass/fail; session init isn't actionable. - if (v.type === 'system' && v.subtype === 'init') return; + // Drop claude's result line and rate-limit — noise. TurnEnd + // communicates pass/fail; rate-limit events are noisy status chatter. if (v.type === 'rate_limit_event') return; if (v.type === 'result') return; + // `system` events: `init` is silent startup noise; `api_retry` + // and `api_error` get human-readable notes; unknown subtypes get a + // muted line rather than a raw-JSON dump in the loud `sys` colour. + if (v.type === 'system') { + if (v.subtype === 'init') return; + if (v.subtype === 'api_retry') { + const parts = ['⚠ api retry']; + if (v.attempt != null && v.max_retries != null) + parts.push(v.attempt + '/' + v.max_retries); + if (v.error) parts.push(String(v.error)); + else if (v.error_status) parts.push('HTTP ' + v.error_status); + if (v.retry_delay_ms != null) + parts.push(Math.round(v.retry_delay_ms) + 'ms'); + api.row('note', parts.join(' · ')); + return; + } + if (v.subtype === 'api_error') { + const msg = v.error || v.message + || (v.error_status ? 'HTTP ' + v.error_status : 'unknown'); + api.row('note stderr', '✗ api error · ' + msg); + return; + } + // Other system subtypes (context_window_exceeded, etc.) — render a + // muted note with the subtype label; reserve the loud orange `sys` + // catch-all for truly unrecognised top-level types. + api.row('note', '⚙ ' + (v.subtype || 'system')); + return; + } // Background-task subagent events (claude's `Task` tool spawns // a separate session whose progress lands here as `task_*` // subtypes). Match by subtype so we don't have to track which