@@ -69,6 +69,7 @@ import { ScreenshotPickerDialog } from "@/components/screenshot-picker-dialog";
6969import { speakText , stopSpeaking } from "@/lib/tts" ;
7070import { computeLineDiff } from "@/lib/diff" ;
7171import { useToast } from "@/components/toast" ;
72+ import { isTransientError } from "@/lib/transient-errors" ;
7273import type {
7374 ChatMessage ,
7475 OllamaModel ,
@@ -223,11 +224,20 @@ const MessageBubble = memo(function MessageBubble({
223224 const { t } = useI18n ( ) ;
224225
225226 if ( m . role === "tool" ) {
227+ // Tool failures get a visually distinct card — an agent run that hit
228+ // an error mid-way should be scannable at a glance, not require
229+ // reading every result body to find where things went wrong.
230+ const isError = m . content . startsWith ( "Error:" ) || m . content === "The user denied this tool call." ;
226231 return (
227232 < div className = "flex flex-col items-start" >
228- < div className = "max-w-[85%] rounded-lg border border-border bg-muted/50 px-3 py-2 font-mono text-xs text-muted-foreground" >
229- < div className = "mb-1 font-sans font-medium text-foreground" >
230- 🔧 { m . toolName } { t . toolResult }
233+ < div
234+ className = { cn (
235+ "max-w-[85%] rounded-lg border px-3 py-2 font-mono text-xs text-muted-foreground" ,
236+ isError ? "border-destructive/40 bg-destructive/5" : "border-border bg-muted/50"
237+ ) }
238+ >
239+ < div className = { cn ( "mb-1 font-sans font-medium" , isError ? "text-destructive" : "text-foreground" ) } >
240+ { isError ? "⚠️" : "🔧" } { m . toolName } { isError ? t . toolFailed : t . toolResult }
231241 </ div >
232242 < pre className = "max-h-48 overflow-auto whitespace-pre-wrap" > { m . content } </ pre >
233243 </ div >
@@ -492,7 +502,7 @@ export default function Chat() {
492502 setAgentWorkspace ( session . agentWorkspace ?? null ) ;
493503 setPendingToolCalls ( [ ] ) ;
494504 setAgentStepCount ( 0 ) ;
495- setPlanSteps ( [ ] ) ;
505+ setPlanSteps ( session . planSteps ?? [ ] ) ;
496506 setAutoApprovedTools ( new Set ( ) ) ;
497507 setWriteDiffPreviews ( { } ) ;
498508 setUndoMessage ( null ) ;
@@ -832,7 +842,7 @@ export default function Chat() {
832842 async function runCompletion (
833843 history : ChatMessage [ ] ,
834844 baseMessages : ChatMessage [ ] ,
835- opts : { isFirstMessage : boolean ; titleSource : string }
845+ opts : { isFirstMessage : boolean ; titleSource : string ; attempt ?: number }
836846 ) {
837847 const parsed = parseModelRef ( model ) ;
838848 if ( ! parsed || ! sessionId ) return ;
@@ -883,6 +893,18 @@ export default function Chat() {
883893 const result = await promise ;
884894 setActiveRequestId ( null ) ;
885895
896+ // One silent retry for errors that usually clear on their own
897+ // (network blips, rate limits, 5xx) — but never for a user-initiated
898+ // stop, and never more than once, so a genuinely down provider still
899+ // fails fast instead of looping.
900+ if ( result . error && ! result . aborted && ( opts . attempt ?? 0 ) === 0 && isTransientError ( result . error ) ) {
901+ toast . info ( t . transientErrorRetrying ) ;
902+ setIsStreaming ( false ) ;
903+ window . api . app . setBusy ( false ) ;
904+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1500 ) ) ;
905+ return runCompletion ( history , baseMessages , { ...opts , attempt : 1 } ) ;
906+ }
907+
886908 if ( result . error ) {
887909 setMessages ( ( m ) => {
888910 const next = [ ...m ] ;
@@ -999,6 +1021,7 @@ export default function Chat() {
9991021 done : Boolean ( ( s as { done ?: unknown } ) ?. done ) ,
10001022 } ) ) ;
10011023 setPlanSteps ( steps ) ;
1024+ if ( sessionId ) window . api . sessions . update ( sessionId , { planSteps : steps } ) ;
10021025 resolveToolCall ( call , "Plan updated." ) ;
10031026 }
10041027
@@ -1098,6 +1121,7 @@ export default function Chat() {
10981121 setImageAttachments ( [ ] ) ;
10991122 setAgentStepCount ( 0 ) ;
11001123 setPlanSteps ( [ ] ) ;
1124+ window . api . sessions . update ( sessionId , { planSteps : [ ] } ) ;
11011125 await runCompletion ( history , baseMessages , { isFirstMessage, titleSource } ) ;
11021126 }
11031127
0 commit comments