@@ -1069,6 +1069,51 @@ EOF`,
10691069 expect ( approvalInput ?. outcome ?. exitCode ) . toBe ( 0 ) ;
10701070 } ) ;
10711071
1072+ it ( "uses async agent followups for explicit webchat approval mode" , async ( ) => {
1073+ buildExecApprovalFollowupTargetMock . mockImplementation ( ( value ) => value ) ;
1074+ resolveExecHostApprovalContextMock . mockReturnValue ( {
1075+ approvals : { allowlist : [ ] , file : { version : 1 , agents : { } } } ,
1076+ hostSecurity : "allowlist" ,
1077+ hostAsk : "always" ,
1078+ askFallback : "deny" ,
1079+ } ) ;
1080+ resolveApprovalDecisionOrUndefinedMock . mockResolvedValue ( "allow-once" ) ;
1081+ createExecApprovalDecisionStateMock . mockReturnValue ( {
1082+ baseDecision : { timedOut : false } ,
1083+ approvedByAsk : true ,
1084+ deniedReason : null ,
1085+ } ) ;
1086+ runExecProcessMock . mockResolvedValue ( {
1087+ session : { id : "sess-1" } ,
1088+ promise : Promise . resolve ( {
1089+ status : "completed" ,
1090+ exitCode : 0 ,
1091+ timedOut : false ,
1092+ aggregated : "done" ,
1093+ } ) ,
1094+ } ) ;
1095+
1096+ const result = await runGatewayAllowlist ( {
1097+ command : "openclaw sessions export-trajectory --json" ,
1098+ approvalFollowupMode : "agent" ,
1099+ sessionId : "approval-session" ,
1100+ sessionStore : "/tmp/openclaw-sessions.json" ,
1101+ turnSourceChannel : "webchat" ,
1102+ } ) ;
1103+
1104+ expect ( result . pendingResult ?. details . status ) . toBe ( "approval-pending" ) ;
1105+ await vi . waitFor ( ( ) => {
1106+ expect ( sendExecApprovalFollowupResultMock ) . toHaveBeenCalledTimes ( 1 ) ;
1107+ } ) ;
1108+ expect ( requireBuildFollowupTargetInput ( 0 ) ) . toMatchObject ( {
1109+ direct : false ,
1110+ expectedSessionId : "approval-session" ,
1111+ sessionStore : "/tmp/openclaw-sessions.json" ,
1112+ } ) ;
1113+ expect ( requireSentFollowupTarget ( 0 ) ?. direct ) . toBe ( false ) ;
1114+ expect ( requireSentFollowupText ( 0 ) ) . toContain ( "done" ) ;
1115+ } ) ;
1116+
10721117 it ( "waits inline for webchat approval so the exec tool can return real output to the model" , async ( ) => {
10731118 resolveApprovalDecisionOrUndefinedMock . mockResolvedValue ( "allow-once" ) ;
10741119 createExecApprovalDecisionStateMock . mockReturnValue ( {
0 commit comments