@@ -656,6 +656,23 @@ describe("tool-loop-detection", () => {
656656 messageId : `plugin_volatile_${ index } ` ,
657657 } ) ,
658658 } ,
659+ {
660+ name : "qa-channel plugin message result" ,
661+ payloadAt : ( index : number ) => ( {
662+ message : {
663+ id : `qa_volatile_${ index } ` ,
664+ accountId : "default" ,
665+ direction : "outbound" ,
666+ conversation : {
667+ id : "loop-room" ,
668+ chatType : "channel" ,
669+ } ,
670+ senderId : "openclaw" ,
671+ text : "hello" ,
672+ timestamp : 1_800_000_000_000 + index ,
673+ } ,
674+ } ) ,
675+ } ,
659676 ] ;
660677
661678 for ( const { name, payloadAt } of cases ) {
@@ -685,37 +702,62 @@ describe("tool-loop-detection", () => {
685702 } ) ;
686703
687704 it ( "does not block repeated message sends when stable delivery facts change" , ( ) => {
688- const state = createState ( ) ;
689705 const params = { action : "send" , to : "feishu:chat-1" , content : "hello" } ;
706+ const cases = [
707+ {
708+ name : "chat id changes" ,
709+ payloadAt : ( index : number ) => ( {
710+ channel : "feishu" ,
711+ to : "feishu:chat-1" ,
712+ via : "direct" ,
713+ result : {
714+ ok : true ,
715+ chatId : `oc_chat_${ index } ` ,
716+ messageId : `om_volatile_${ index } ` ,
717+ } ,
718+ } ) ,
719+ } ,
720+ {
721+ name : "qa-channel conversation id changes" ,
722+ payloadAt : ( index : number ) => ( {
723+ message : {
724+ id : `qa_volatile_${ index } ` ,
725+ accountId : "default" ,
726+ direction : "outbound" ,
727+ conversation : {
728+ id : `loop-room-${ index } ` ,
729+ chatType : "channel" ,
730+ } ,
731+ senderId : "openclaw" ,
732+ text : "hello" ,
733+ timestamp : 1_800_000_000_000 + index ,
734+ } ,
735+ } ) ,
736+ } ,
737+ ] ;
690738
691- for ( let index = 0 ; index < CRITICAL_THRESHOLD ; index += 1 ) {
692- const payload = {
693- channel : "feishu" ,
694- to : "feishu:chat-1" ,
695- via : "direct" ,
696- result : {
697- ok : true ,
698- chatId : `oc_chat_${ index } ` ,
699- messageId : `om_volatile_${ index } ` ,
700- } ,
701- } ;
702- recordSuccessfulCall (
703- state ,
704- "message" ,
705- params ,
706- {
707- content : [ { type : "text" , text : JSON . stringify ( payload ) } ] ,
708- details : payload ,
709- } ,
710- index ,
711- ) ;
712- }
739+ for ( const { name, payloadAt } of cases ) {
740+ const state = createState ( ) ;
741+ for ( let index = 0 ; index < CRITICAL_THRESHOLD ; index += 1 ) {
742+ const payload = payloadAt ( index ) ;
743+ recordSuccessfulCall (
744+ state ,
745+ "message" ,
746+ params ,
747+ {
748+ content : [ { type : "text" , text : JSON . stringify ( payload ) } ] ,
749+ details : payload ,
750+ } ,
751+ index ,
752+ ) ;
753+ }
713754
714- const loopResult = detectToolCallLoop ( state , "message" , params , enabledLoopDetectionConfig ) ;
715- expect ( loopResult . stuck ) . toBe ( true ) ;
716- if ( loopResult . stuck ) {
717- expect ( loopResult . level ) . toBe ( "warning" ) ;
718- expect ( loopResult . detector ) . toBe ( "generic_repeat" ) ;
755+ const loopResult = detectToolCallLoop ( state , "message" , params , enabledLoopDetectionConfig ) ;
756+ expect ( loopResult . stuck , name ) . toBe ( true ) ;
757+ if ( loopResult . stuck ) {
758+ expect ( loopResult . level , name ) . toBe ( "warning" ) ;
759+ expect ( loopResult . detector , name ) . toBe ( "generic_repeat" ) ;
760+ }
719761 }
720762 } ) ;
721763
0 commit comments