@@ -362,6 +362,84 @@ describe("aiAgentActions runtime safety", () => {
362362 ) ;
363363 } ) ;
364364
365+ it ( "stores only the handoff message when AI output requires a handoff" , async ( ) => {
366+ const handoffMessage = "Let me connect you with a human agent who can help you better." ;
367+ mockGenerateText . mockResolvedValue ( {
368+ text : "I don't have enough information to answer that question. Let me connect you with a human agent." ,
369+ usage : { totalTokens : 33 } ,
370+ } as any ) ;
371+
372+ const runQuery = vi . fn ( async ( _reference : unknown , args : Record < string , unknown > ) => {
373+ if ( "query" in args ) {
374+ return [ ] ;
375+ }
376+ if ( "workspaceId" in args && "conversationId" in args === false ) {
377+ return {
378+ enabled : true ,
379+ model : "openai/gpt-5-nano" ,
380+ confidenceThreshold : 0.2 ,
381+ knowledgeSources : [ "articles" ] ,
382+ personality : null ,
383+ } ;
384+ }
385+ return {
386+ conversationId : "conversation_1" ,
387+ workspaceId : "workspace_1" ,
388+ visitorId : "visitor_1" ,
389+ } ;
390+ } ) ;
391+
392+ const runMutation = vi . fn ( async ( _reference : unknown , args : Record < string , unknown > ) => {
393+ if ( Object . keys ( args ) . length === 1 && "workspaceId" in args ) {
394+ return "cleared" ;
395+ }
396+ if ( "reason" in args ) {
397+ return {
398+ messageId : "handoff_message_single_1" ,
399+ handoffMessage,
400+ } ;
401+ }
402+ if ( "query" in args && "response" in args ) {
403+ return "ai_response_handoff_1" ;
404+ }
405+ if ( "senderId" in args && "content" in args ) {
406+ throw new Error ( "Unexpected AI message persistence before handoff" ) ;
407+ }
408+ throw new Error ( `Unexpected mutation args: ${ JSON . stringify ( args ) } ` ) ;
409+ } ) ;
410+
411+ const result = await generateResponse . _handler (
412+ {
413+ runQuery,
414+ runMutation,
415+ } as any ,
416+ {
417+ workspaceId : "workspace_1" as any ,
418+ conversationId : "conversation_1" as any ,
419+ query : "Who should I vote for?" ,
420+ }
421+ ) ;
422+
423+ expect ( result . handoff ) . toBe ( true ) ;
424+ expect ( result . messageId ) . toBe ( "handoff_message_single_1" ) ;
425+ expect ( result . response ) . toBe ( handoffMessage ) ;
426+ expect ( runMutation ) . toHaveBeenCalledWith (
427+ expect . anything ( ) ,
428+ expect . objectContaining ( {
429+ query : "Who should I vote for?" ,
430+ response : handoffMessage ,
431+ messageId : "handoff_message_single_1" ,
432+ handedOff : true ,
433+ } )
434+ ) ;
435+ expect ( runMutation ) . not . toHaveBeenCalledWith (
436+ expect . anything ( ) ,
437+ expect . objectContaining ( {
438+ senderId : "ai-agent" ,
439+ } )
440+ ) ;
441+ } ) ;
442+
365443 it ( "persists a handoff message when generation fails" , async ( ) => {
366444 mockGenerateText . mockRejectedValue ( new Error ( "gateway timeout" ) ) ;
367445
0 commit comments