@@ -201,6 +201,13 @@ export async function appendExactAssistantMessageToSessionTranscript(params: {
201201 return { ok : true , sessionFile, messageId : existingMessageId } ;
202202 }
203203
204+ const latestEquivalentAssistantId = isRedundantDeliveryMirror ( params . message )
205+ ? await findLatestEquivalentAssistantMessageId ( sessionFile , params . message )
206+ : undefined ;
207+ if ( latestEquivalentAssistantId ) {
208+ return { ok : true , sessionFile, messageId : latestEquivalentAssistantId } ;
209+ }
210+
204211 const message = {
205212 ...params . message ,
206213 ...( explicitIdempotencyKey ? { idempotencyKey : explicitIdempotencyKey } : { } ) ,
@@ -252,3 +259,73 @@ async function transcriptHasIdempotencyKey(
252259 }
253260 return undefined ;
254261}
262+
263+ function isRedundantDeliveryMirror ( message : SessionTranscriptAssistantMessage ) : boolean {
264+ return message . provider === "openclaw" && message . model === "delivery-mirror" ;
265+ }
266+
267+ function extractAssistantMessageText ( message : SessionTranscriptAssistantMessage ) : string | null {
268+ if ( typeof message . text === "string" && message . text . trim ( ) ) {
269+ return message . text . trim ( ) ;
270+ }
271+ if ( ! Array . isArray ( message . content ) ) {
272+ return null ;
273+ }
274+
275+ const parts = message . content
276+ . filter (
277+ (
278+ part ,
279+ ) : part is {
280+ type : "text" ;
281+ text : string ;
282+ } => part . type === "text" && typeof part . text === "string" && part . text . trim ( ) . length > 0 ,
283+ )
284+ . map ( ( part ) => part . text . trim ( ) ) ;
285+
286+ return parts . length > 0 ? parts . join ( "\n" ) . trim ( ) : null ;
287+ }
288+
289+ async function findLatestEquivalentAssistantMessageId (
290+ transcriptPath : string ,
291+ message : SessionTranscriptAssistantMessage ,
292+ ) : Promise < string | undefined > {
293+ const expectedText = extractAssistantMessageText ( message ) ;
294+ if ( ! expectedText ) {
295+ return undefined ;
296+ }
297+
298+ try {
299+ const raw = await fs . promises . readFile ( transcriptPath , "utf-8" ) ;
300+ const lines = raw . split ( / \r ? \n / ) ;
301+ for ( let index = lines . length - 1 ; index >= 0 ; index -= 1 ) {
302+ const line = lines [ index ] ;
303+ if ( ! line . trim ( ) ) {
304+ continue ;
305+ }
306+ try {
307+ const parsed = JSON . parse ( line ) as {
308+ id ?: unknown ;
309+ message ?: SessionTranscriptAssistantMessage ;
310+ } ;
311+ const candidate = parsed . message ;
312+ if ( ! candidate || candidate . role !== "assistant" ) {
313+ continue ;
314+ }
315+ const candidateText = extractAssistantMessageText ( candidate ) ;
316+ if ( candidateText !== expectedText ) {
317+ continue ;
318+ }
319+ if ( typeof parsed . id === "string" && parsed . id ) {
320+ return parsed . id ;
321+ }
322+ } catch {
323+ continue ;
324+ }
325+ }
326+ } catch {
327+ return undefined ;
328+ }
329+
330+ return undefined ;
331+ }
0 commit comments