@@ -25,6 +25,18 @@ const isProposedToolName = (toolName: ToolContentBlock['toolName']): boolean =>
2525const getBaseToolName = ( toolName : ToolContentBlock [ 'toolName' ] ) : string =>
2626 isProposedToolName ( toolName ) ? toolName . slice ( 'propose_' . length ) : toolName
2727
28+ const SUCCESSFUL_EDIT_MESSAGES = [
29+ 'String replace applied successfully' ,
30+ 'Created file successfully' ,
31+ 'Created new file' ,
32+ 'Overwrote file successfully' ,
33+ 'Wrote file successfully' ,
34+ 'Updated file' ,
35+ 'Proposed new file' ,
36+ 'Proposed changes' ,
37+ 'Proposed string replacement' ,
38+ ] as const
39+
2840const hasProposedTools = ( blocks ?: ContentBlock [ ] ) : boolean => {
2941 if ( ! blocks || blocks . length === 0 ) return false
3042
@@ -221,32 +233,55 @@ export function extractFilePath(toolBlock: ToolContentBlock): string | null {
221233 * For proposed tools (implementors): construct diff from input replacements.
222234 */
223235export function extractDiff ( toolBlock : ToolContentBlock ) : string | null {
236+ let hasSuccessfulOutput = false
237+
224238 // First try to get from outputRaw (for executed tool results)
225239 // outputRaw is typically an array like [{type: "json", value: {unifiedDiff: "..."}}]
226240 const outputRaw = toolBlock . outputRaw as unknown
227241 if ( Array . isArray ( outputRaw ) && outputRaw [ 0 ] ?. value ) {
228242 const value = outputRaw [ 0 ] . value as Record < string , unknown >
243+ if ( hasErrorMessage ( value ) ) return null
244+ if ( isSuccessfulEditMessage ( value . message ) ) hasSuccessfulOutput = true
229245 if ( value . unifiedDiff ) return value . unifiedDiff as string
230246 if ( value . patch ) return value . patch as string
231247 }
232248 // Also check direct properties (in case format differs)
233249 if ( typeof outputRaw === 'object' && outputRaw !== null ) {
234250 const rawObj = outputRaw as Record < string , unknown >
251+ if ( hasErrorMessage ( rawObj ) ) return null
252+ if ( isSuccessfulEditMessage ( rawObj . message ) ) hasSuccessfulOutput = true
235253 if ( rawObj . unifiedDiff ) return rawObj . unifiedDiff as string
236254 if ( rawObj . patch ) return rawObj . patch as string
237255 }
238256
239257 // Try to get from output string (key: value format)
240258 const outputStr = typeof toolBlock . output === 'string' ? toolBlock . output : ''
259+ const message = extractValueForKey ( outputStr , 'message' )
241260 const diffFromOutput =
242261 extractValueForKey ( outputStr , 'unifiedDiff' ) ||
243262 extractValueForKey ( outputStr , 'patch' )
244263
264+ if ( hasFailedEditOutput ( { outputStr, message, diffFromOutput } ) ) {
265+ return null
266+ }
267+ if ( isSuccessfulEditMessage ( message ) ) {
268+ hasSuccessfulOutput = true
269+ }
270+
245271 if ( diffFromOutput ) {
246272 return diffFromOutput
247273 }
248274
249- // For proposed edits (no output yet): construct diff from input
275+ // For proposed/pending edits, or confirmed successful executions, construct
276+ // the preview from input when the result omits a diff.
277+ const canUseInputFallback =
278+ isProposedToolName ( toolBlock . toolName ) ||
279+ outputStr === '' ||
280+ hasSuccessfulOutput
281+ if ( ! canUseInputFallback ) {
282+ return null
283+ }
284+
250285 const input = toolBlock . input as Record < string , unknown >
251286 const baseToolName = getBaseToolName ( toolBlock . toolName )
252287
@@ -271,6 +306,70 @@ export function extractDiff(toolBlock: ToolContentBlock): string | null {
271306 return null
272307}
273308
309+ function hasErrorMessage ( value : Record < string , unknown > ) : boolean {
310+ return Boolean ( value . errorMessage || ( value . value as any ) ?. errorMessage )
311+ }
312+
313+ function hasFailedEditOutput ( params : {
314+ outputStr : string
315+ message : string | null
316+ diffFromOutput : string | null
317+ } ) : boolean {
318+ const { outputStr, message, diffFromOutput } = params
319+ const trimmedOutput = outputStr . trim ( )
320+ if ( ! trimmedOutput ) {
321+ return false
322+ }
323+ if (
324+ extractValueForKey ( outputStr , 'errorMessage' ) ||
325+ isErrorOutput ( outputStr )
326+ ) {
327+ return true
328+ }
329+ if ( diffFromOutput || isSuccessfulEditMessage ( message ) ) {
330+ return false
331+ }
332+ return ! isSuccessfulEditMessage ( trimmedOutput )
333+ }
334+
335+ function isFailedEditToolBlock ( toolBlock : ToolContentBlock ) : boolean {
336+ const outputRaw = toolBlock . outputRaw as unknown
337+ if ( Array . isArray ( outputRaw ) && outputRaw [ 0 ] ?. value ) {
338+ const value = outputRaw [ 0 ] . value as Record < string , unknown >
339+ if ( hasErrorMessage ( value ) ) return true
340+ }
341+ if ( typeof outputRaw === 'object' && outputRaw !== null ) {
342+ const rawObj = outputRaw as Record < string , unknown >
343+ if ( hasErrorMessage ( rawObj ) ) return true
344+ }
345+
346+ const outputStr = typeof toolBlock . output === 'string' ? toolBlock . output : ''
347+ const message = extractValueForKey ( outputStr , 'message' )
348+ const diffFromOutput =
349+ extractValueForKey ( outputStr , 'unifiedDiff' ) ||
350+ extractValueForKey ( outputStr , 'patch' )
351+ return hasFailedEditOutput ( { outputStr, message, diffFromOutput } )
352+ }
353+
354+ function isSuccessfulEditMessage ( message : unknown ) : boolean {
355+ if ( typeof message !== 'string' ) {
356+ return false
357+ }
358+
359+ return message
360+ . split ( '\n' )
361+ . some ( ( line ) =>
362+ SUCCESSFUL_EDIT_MESSAGES . some ( ( successMessage ) =>
363+ line . trim ( ) . startsWith ( successMessage ) ,
364+ ) ,
365+ )
366+ }
367+
368+ function isErrorOutput ( output : string ) : boolean {
369+ const trimmedOutput = output . trim ( )
370+ return trimmedOutput . startsWith ( 'Error:' ) || trimmedOutput . startsWith ( 'Failed ' )
371+ }
372+
274373/**
275374 * Construct a simple diff view from str_replace replacements.
276375 */
@@ -425,6 +524,8 @@ export function getFileStatsFromBlocks(
425524 block . toolName as ( typeof ALL_EDIT_TOOL_NAMES ) [ number ] ,
426525 )
427526 ) {
527+ if ( isFailedEditToolBlock ( block ) ) continue
528+
428529 const filePath = extractFilePath ( block )
429530 if ( ! filePath ) continue
430531
@@ -475,6 +576,8 @@ export function buildActivityTimeline(
475576 block . toolName as ( typeof ALL_EDIT_TOOL_NAMES ) [ number ] ,
476577 )
477578 ) {
579+ if ( isFailedEditToolBlock ( block ) ) continue
580+
478581 const filePath = extractFilePath ( block )
479582 const diff = extractDiff ( block )
480583 const isCreate = isCreateFile ( block )
0 commit comments