@@ -234,7 +234,6 @@ describe('Supervisor Pattern Integration Tests', () => {
234234 maxSteps : 3 ,
235235 onIterationComplete : ( ctx : IterationCompleteContext ) => {
236236 iterations . push ( ctx . iteration ) ;
237- return { continue : true } ;
238237 } ,
239238 } ) ;
240239
@@ -561,7 +560,7 @@ describe('Supervisor Pattern Integration Tests', () => {
561560
562561 it ( 'should accept iteration complete hook configuration' , async ( ) => {
563562 const iterationHook = vi . fn ( ( ) => {
564- return { continue : true } ;
563+ return undefined ;
565564 } ) ;
566565
567566 const agent = new Agent ( {
@@ -1610,7 +1609,6 @@ describe('Supervisor Pattern - onIterationComplete Hook Integration', () => {
16101609 maxSteps : 5 ,
16111610 onIterationComplete : ( ctx : IterationCompleteContext ) => {
16121611 iterations . push ( ctx . iteration ) ;
1613- return { continue : true } ;
16141612 } ,
16151613 } ) ;
16161614
@@ -2038,12 +2036,103 @@ describe('Supervisor Pattern - onIterationComplete Hook Integration', () => {
20382036 } ,
20392037 } ) ;
20402038
2041- // When the model returns stop (isFinal), the loop ends after that iteration
2042- // even if the hook returns continue: true with feedback. Feedback only adds
2043- // a user message for the *next* iteration when the loop would naturally continue
2044- // (e.g. during a tool-call sequence). Here the model says stop on iteration 1
2045- // so the loop ends and the hook is called exactly once.
2046- expect ( iterationCount ) . toBe ( 1 ) ;
2039+ expect ( iterationCount ) . toBe ( 2 ) ;
2040+ } ) ;
2041+
2042+ it ( 'should allow onIterationComplete continue:true to override final stop in stream (issue #14134)' , async ( ) => {
2043+ const iterations : number [ ] = [ ] ;
2044+ let callCount = 0 ;
2045+
2046+ const agent = new Agent ( {
2047+ id : 'continue-override-stream-agent' ,
2048+ name : 'Continue Override Stream Agent' ,
2049+ instructions : 'You may take multiple turns.' ,
2050+ model : new MockLanguageModelV2 ( {
2051+ doGenerate : async ( ) => {
2052+ callCount ++ ;
2053+ if ( callCount === 1 ) {
2054+ return {
2055+ rawCall : { rawPrompt : null , rawSettings : { } } ,
2056+ finishReason : 'stop' ,
2057+ usage : { inputTokens : 10 , outputTokens : 20 , totalTokens : 30 } ,
2058+ text : 'First response ' ,
2059+ content : [ { type : 'text' , text : 'First response ' } ] ,
2060+ warnings : [ ] ,
2061+ } ;
2062+ }
2063+
2064+ return {
2065+ rawCall : { rawPrompt : null , rawSettings : { } } ,
2066+ finishReason : 'stop' ,
2067+ usage : { inputTokens : 10 , outputTokens : 20 , totalTokens : 30 } ,
2068+ text : 'Second response' ,
2069+ content : [ { type : 'text' , text : 'Second response' } ] ,
2070+ warnings : [ ] ,
2071+ } ;
2072+ } ,
2073+ doStream : async ( ) => {
2074+ callCount ++ ;
2075+ const currentCall = callCount ;
2076+ const responseText = currentCall === 1 ? 'First response ' : 'Second response' ;
2077+
2078+ return {
2079+ rawCall : { rawPrompt : null , rawSettings : { } } ,
2080+ warnings : [ ] ,
2081+ stream : convertArrayToReadableStream ( [
2082+ { type : 'stream-start' , warnings : [ ] } ,
2083+ {
2084+ type : 'response-metadata' ,
2085+ id : `id-${ currentCall } ` ,
2086+ modelId : 'mock-model-id' ,
2087+ timestamp : new Date ( 0 ) ,
2088+ } ,
2089+ { type : 'text-start' , id : `text-${ currentCall } ` } ,
2090+ { type : 'text-delta' , id : `text-${ currentCall } ` , delta : responseText } ,
2091+ { type : 'text-end' , id : `text-${ currentCall } ` } ,
2092+ {
2093+ type : 'finish' ,
2094+ finishReason : 'stop' ,
2095+ usage : { inputTokens : 10 , outputTokens : 20 , totalTokens : 30 } ,
2096+ } ,
2097+ ] ) ,
2098+ } ;
2099+ } ,
2100+ } ) ,
2101+ memory : new MockMemory ( ) ,
2102+ } ) ;
2103+
2104+ const mastra = new Mastra ( {
2105+ agents : {
2106+ 'continue-override-stream-agent' : agent ,
2107+ } ,
2108+ storage : new InMemoryStore ( ) ,
2109+ } ) ;
2110+
2111+ const testAgent = mastra . getAgent ( 'continue-override-stream-agent' ) ;
2112+
2113+ const result = await testAgent . stream ( 'Take multiple turns' , {
2114+ maxSteps : 5 ,
2115+ onIterationComplete : ctx => {
2116+ iterations . push ( ctx . iteration ) ;
2117+ if ( ctx . iteration === 1 ) {
2118+ return { continue : true } ;
2119+ }
2120+ } ,
2121+ } ) ;
2122+
2123+ const reader = result . fullStream . getReader ( ) ;
2124+ while ( true ) {
2125+ const { done } = await reader . read ( ) ;
2126+ if ( done ) break ;
2127+ }
2128+
2129+ const text = await result . text ;
2130+
2131+ // When the model returns stop (isFinal), the hook's continue:true should be
2132+ // able to request another iteration in the streaming supervisor loop.
2133+ expect ( iterations ) . toEqual ( [ 1 , 2 ] ) ;
2134+ expect ( callCount ) . toBe ( 2 ) ;
2135+ expect ( text ) . toBe ( 'First response Second response' ) ;
20472136 } ) ;
20482137
20492138 it ( 'should accept onIterationComplete configuration without errors' , async ( ) => {
0 commit comments