Repository: microsoft/botbuilder-python

Issue:
Port: Fix issues with ExpectReplies and Invoke in SkillDialog
While adding BufferedReplies, type of InvokeResponse should be added to TurnState:

```cs
                        if (activity.Type == ActivityTypesEx.InvokeResponse)
                        {
                            TurnState.Add(BotFrameworkAdapter.InvokeResponseKey, activity);
                        }
```

https://github.com/microsoft/botbuilder-dotnet/pull/4845

And, on the Skill Dialog side, only one InvokeResponse should be sent.


Discussion:
Hi @axelsrz, would you be able to address this one and fix it for the next R11 rc?

Thanks  

Resolving patch:
diff --git a/libraries/botbuilder-core/botbuilder/core/turn_context.py b/libraries/botbuilder-core/botbuilder/core/turn_context.py
--- a/libraries/botbuilder-core/botbuilder/core/turn_context.py
+++ b/libraries/botbuilder-core/botbuilder/core/turn_context.py
@@ -18,6 +18,10 @@
 
 
 class TurnContext:
+
+    # Same constant as in the BF Adapter, duplicating here to avoid circular dependency
+    _INVOKE_RESPONSE_KEY = "BotFrameworkAdapter.InvokeResponse"
+
     def __init__(self, adapter_or_context, request: Activity = None):
         """
         Creates a new TurnContext instance.
@@ -202,6 +206,11 @@ async def logic():
                 responses = []
                 for activity in output:
                     self.buffered_reply_activities.append(activity)
+                    # Ensure the TurnState has the InvokeResponseKey, since this activity
+                    # is not being sent through the adapter, where it would be added to TurnState.
+                    if activity.type == ActivityTypes.invoke_response:
+                        self.turn_state[TurnContext._INVOKE_RESPONSE_KEY] = activity
+
                     responses.append(ResourceResponse())
 
                 if sent_non_trace_activity:
diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py
--- a/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py
+++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py
@@ -244,6 +244,8 @@ async def _send_to_skill(
             # Process replies in the response.Body.
             response.body: List[Activity]
             response.body = ExpectedReplies().deserialize(response.body).activities
+            # Track sent invoke responses, so more than one is not sent.
+            sent_invoke_response = False
 
             for from_skill_activity in response.body:
                 if from_skill_activity.type == ActivityTypes.end_of_conversation:
@@ -254,12 +256,18 @@ async def _send_to_skill(
                     await self.dialog_options.conversation_id_factory.delete_conversation_reference(
                         skill_conversation_id
                     )
-                elif await self._intercept_oauth_cards(
+                elif not sent_invoke_response and await self._intercept_oauth_cards(
                     context, from_skill_activity, self.dialog_options.connection_name
                 ):
-                    # do nothing. Token exchange succeeded, so no oauthcard needs to be shown to the user
-                    pass
+                    # Token exchange succeeded, so no oauthcard needs to be shown to the user
+                    sent_invoke_response = True
                 else:
+                    # If an invoke response has already been sent we should ignore future invoke responses as this
+                    # represents a bug in the skill.
+                    if from_skill_activity.type == ActivityTypes.invoke_response:
+                        if sent_invoke_response:
+                            continue
+                        sent_invoke_response = True
                     # Send the response back to the channel.
                     await context.send_activity(from_skill_activity)
 


Test patch: