@@ -152,12 +152,94 @@ function terminalMessageReplacesStreamFallback(message: unknown, fallback: unkno
152152 ) ;
153153}
154154
155- export function appendTerminalAssistantMessage ( messages : unknown [ ] , message : unknown ) : unknown [ ] {
155+ function isHiddenAssistantContentBlockType ( type : string ) : boolean {
156+ return type === "thinking" || type === "reasoning" || type === "redacted_thinking" ;
157+ }
158+
159+ function assistantMessageContentSignature ( message : unknown ) : string | null {
160+ if ( ! message || typeof message !== "object" ) {
161+ return null ;
162+ }
163+ const record = message as { content ?: unknown ; role ?: unknown } ;
164+ const role = normalizeLowercaseStringOrEmpty ( record . role ) ;
165+ if ( role !== "assistant" ) {
166+ return null ;
167+ }
168+ if ( Array . isArray ( record . content ) ) {
169+ const displayBlocks = record . content . filter ( ( block ) => {
170+ if ( ! block || typeof block !== "object" ) {
171+ return false ;
172+ }
173+ const type = normalizeLowercaseStringOrEmpty ( ( block as { type ?: unknown } ) . type ) ;
174+ return (
175+ type !== "text" &&
176+ type !== "input_text" &&
177+ type !== "output_text" &&
178+ ! isHiddenAssistantContentBlockType ( type )
179+ ) ;
180+ } ) ;
181+ if ( displayBlocks . length > 0 ) {
182+ return JSON . stringify ( record . content ) ;
183+ }
184+ }
185+ const text = extractText ( message ) ?. trim ( ) ;
186+ return text ? `text:${ text } ` : null ;
187+ }
188+
189+ function terminalMessageDuplicatesExisting ( message : unknown , existing : unknown ) : boolean {
190+ const terminalSignature = assistantMessageContentSignature ( message ) ;
191+ return Boolean (
192+ terminalSignature && terminalSignature === assistantMessageContentSignature ( existing ) ,
193+ ) ;
194+ }
195+
196+ function hasTranscriptMeta ( message : unknown ) : boolean {
197+ return Boolean (
198+ message &&
199+ typeof message === "object" &&
200+ ( message as { __openclaw ?: unknown } ) [ "__openclaw" ] &&
201+ typeof ( message as { __openclaw ?: unknown } ) [ "__openclaw" ] === "object" ,
202+ ) ;
203+ }
204+
205+ function existingMessageMatchesReplacementText (
206+ existing : unknown ,
207+ replacementTexts : readonly string [ ] ,
208+ ) : boolean {
209+ if ( replacementTexts . length === 0 ) {
210+ return false ;
211+ }
212+ if ( ! existing || typeof existing !== "object" ) {
213+ return false ;
214+ }
215+ const role = normalizeLowercaseStringOrEmpty ( ( existing as { role ?: unknown } ) . role ) ;
216+ if ( role !== "assistant" ) {
217+ return false ;
218+ }
219+ if ( ! hasTranscriptMeta ( existing ) ) {
220+ return false ;
221+ }
222+ const text = extractText ( existing ) ?. trim ( ) ;
223+ return Boolean ( text && replacementTexts . some ( ( candidate ) => text === candidate ) ) ;
224+ }
225+
226+ export function appendTerminalAssistantMessage (
227+ messages : unknown [ ] ,
228+ message : unknown ,
229+ opts ?: { replacementTexts ?: readonly string [ ] } ,
230+ ) : unknown [ ] {
231+ const replacementTexts = ( opts ?. replacementTexts ?? [ ] )
232+ . map ( ( text ) => text . trim ( ) )
233+ . filter ( ( text ) => text . length > 0 ) ;
156234 const retainedMessages = messages . filter ( ( existing , index ) => {
157235 if ( index <= lastUserMessageIndex ( messages ) ) {
158236 return true ;
159237 }
160- return ! terminalMessageReplacesStreamFallback ( message , existing ) ;
238+ return (
239+ ! terminalMessageReplacesStreamFallback ( message , existing ) &&
240+ ! terminalMessageDuplicatesExisting ( message , existing ) &&
241+ ! existingMessageMatchesReplacementText ( existing , replacementTexts )
242+ ) ;
161243 } ) ;
162244 return [ ...retainedMessages , message ] ;
163245}
0 commit comments