@@ -261,7 +261,33 @@ export async function runNonInteractive(
261261 const rewriteTarget = rewriteConfig ?. target ?? 'both' ;
262262 const turnBuffer = rewriter ? new TurnBuffer ( ) : null ;
263263 let rewriteTurnIndex = 0 ;
264- const pendingRewrites : Array < Promise < void > > = [ ] ;
264+ const pendingRewrites : Array <
265+ Promise < { turnIdx : number ; text : string } | null >
266+ > = [ ] ;
267+
268+ /**
269+ * Emit all settled rewrite results via the adapter.
270+ * Must be called from the main control flow (not inside async promises)
271+ * to avoid concurrent adapter state corruption.
272+ */
273+ const emitSettledRewrites = async ( ) => {
274+ if ( pendingRewrites . length === 0 ) return ;
275+ const results = await Promise . allSettled ( pendingRewrites ) ;
276+ pendingRewrites . length = 0 ;
277+ for ( const r of results ) {
278+ if ( r . status === 'fulfilled' && r . value ) {
279+ adapter . startAssistantMessage ( ) ;
280+ adapter . processEvent ( {
281+ type : GeminiEventType . Content ,
282+ value : r . value . text ,
283+ _meta : { rewritten : true , turnIndex : r . value . turnIdx } ,
284+ } as unknown as Parameters <
285+ JsonOutputAdapterInterface [ 'processEvent' ]
286+ > [ 0 ] ) ;
287+ adapter . finalizeAssistantMessage ( ) ;
288+ }
289+ }
290+ } ;
265291
266292 if ( rewriter ) {
267293 debugLogger . info ( 'Message rewrite enabled in non-interactive mode' ) ;
@@ -277,6 +303,9 @@ export async function runNonInteractive(
277303 handleMaxTurnsExceededError ( config ) ;
278304 }
279305
306+ // Emit any settled rewrites before starting the next turn
307+ await emitSettledRewrites ( ) ;
308+
280309 const toolCallRequests : ToolCallRequestInfo [ ] = [ ] ;
281310 const apiStartTime = Date . now ( ) ;
282311 const responseStream = geminiClient . sendMessageStream (
@@ -344,7 +373,9 @@ export async function runNonInteractive(
344373 adapter . finalizeAssistantMessage ( ) ;
345374 totalApiDurationMs += Date . now ( ) - apiStartTime ;
346375
347- // Rewrite turn content (async, parallel with tool execution)
376+ // Rewrite turn content (async, parallel with tool execution).
377+ // Only collects rewritten text — emission happens at safe boundaries
378+ // via emitSettledRewrites() to avoid concurrent adapter state corruption.
348379 if ( rewriter && turnBuffer ) {
349380 const content = turnBuffer . flush ( ) ;
350381 if ( content ) {
@@ -365,21 +396,14 @@ export async function runNonInteractive(
365396 debugLogger . info (
366397 `Turn ${ turnIdx } : rewritten ${ rewritten . length } chars` ,
367398 ) ;
368- adapter . startAssistantMessage ( ) ;
369- adapter . processEvent ( {
370- type : GeminiEventType . Content ,
371- value : rewritten ,
372- _meta : { rewritten : true , turnIndex : turnIdx } ,
373- } as unknown as Parameters <
374- JsonOutputAdapterInterface [ 'processEvent' ]
375- > [ 0 ] ) ;
376- adapter . finalizeAssistantMessage ( ) ;
399+ return { turnIdx, text : rewritten } ;
377400 }
378401 } catch ( err ) {
379402 debugLogger . warn (
380403 `Turn ${ turnIdx } : rewrite failed: ${ err instanceof Error ? err . message : String ( err ) } ` ,
381404 ) ;
382405 }
406+ return null ;
383407 } ) ( ) ,
384408 ) ;
385409 }
@@ -544,7 +568,7 @@ export async function runNonInteractive(
544568 adapter . finalizeAssistantMessage ( ) ;
545569 totalApiDurationMs += Date . now ( ) - cronApiStartTime ;
546570
547- // Flush turn buffer and rewrite for cron path (async)
571+ // Flush turn buffer and rewrite for cron path (async, collect only )
548572 if ( rewriter && turnBuffer ) {
549573 const content = turnBuffer . flush ( ) ;
550574 if ( content ) {
@@ -565,29 +589,22 @@ export async function runNonInteractive(
565589 debugLogger . info (
566590 `Cron turn ${ turnIdx } : rewritten ${ rewritten . length } chars` ,
567591 ) ;
568- adapter . startAssistantMessage ( ) ;
569- adapter . processEvent ( {
570- type : GeminiEventType . Content ,
571- value : rewritten ,
572- _meta : {
573- rewritten : true ,
574- turnIndex : turnIdx ,
575- } ,
576- } as unknown as Parameters <
577- JsonOutputAdapterInterface [ 'processEvent' ]
578- > [ 0 ] ) ;
579- adapter . finalizeAssistantMessage ( ) ;
592+ return { turnIdx, text : rewritten } ;
580593 }
581594 } catch ( err ) {
582595 debugLogger . warn (
583596 `Cron turn ${ turnIdx } : rewrite failed: ${ err instanceof Error ? err . message : String ( err ) } ` ,
584597 ) ;
585598 }
599+ return null ;
586600 } ) ( ) ,
587601 ) ;
588602 }
589603 }
590604
605+ // Emit settled rewrites before next cron turn
606+ await emitSettledRewrites ( ) ;
607+
591608 if ( cronToolCallRequests . length > 0 ) {
592609 const cronToolResponseParts : Part [ ] = [ ] ;
593610
@@ -654,11 +671,8 @@ export async function runNonInteractive(
654671 } ) ;
655672 }
656673
657- // Wait for all pending async rewrites before emitting result
658- if ( pendingRewrites . length > 0 ) {
659- await Promise . allSettled ( pendingRewrites ) ;
660- pendingRewrites . length = 0 ;
661- }
674+ // Emit all remaining rewrites before emitting result
675+ await emitSettledRewrites ( ) ;
662676
663677 const metrics = uiTelemetryService . getMetrics ( ) ;
664678 const usage = computeUsageFromMetrics ( metrics ) ;
0 commit comments