SOLVED

Skill Dialog: endDialog() does not work

%3CLINGO-SUB%20id%3D%22lingo-sub-1476612%22%20slang%3D%22en-US%22%3ESkill%20Dialog%3A%20endDialog()%20does%20not%20work%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-1476612%22%20slang%3D%22en-US%22%3E%3CP%3EHi%2C%3C%2FP%3E%3CP%3EI%20am%20implementing%20a%20dialogRootBot%20that%20will%20call%20a%20skillDialogBot.%26nbsp%3B%26nbsp%3B%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EMy%20dialogRootBot%20is%20able%20to%20invoke%20the%20skillDialogBot.%26nbsp%3B%20The%20conversation%20can%20progress%20until%20the%20point%20where%20the%20skillDialogBot%20is%20supposed%20to%20return%20the%20results%20to%20the%20dialogRootBot.%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EThe%20skillDialogBot%20has%20the%20following%20setup%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-javascript%22%3E%3CCODE%3Ethis.addDialog(new%20TextPrompt(TEXT_PROMPT))%0A%20.addDialog(new%20ConfirmPrompt(CONFIRM_PROMPT))%0A%20.addDialog(new%20WaterfallDialog(WATERFALL_DIALOG%2C%20%5B%0A%20%20this.processStep.bind(this)%0A%20%5D))%3B%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EThe%20processStep%20is%20laid%20out%20like%20this%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3Easync%20processStep(stepContext)%20%7B%0A%20const%20details%20%3D%20stepContext.options%3B%0A%20details.result%20%3D%20%7B%0A%20%20status%3A%20'success'%0A%20%7D%3B%0A%20return%20await%20stepContext.endDialog(stepContext.options)%3B%0A%7D%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3EI%20was%20expecting%20the%20dialogRootBot%20to%20get%20the%20result%20from%20the%20skillDialogBot%20after%20processStep%20has%20called%20endDialog%2C%20but%20that%20never%20happens.%26nbsp%3B%20Instead%2C%20the%20user%20is%20stuck%20with%20the%20skillDialogBot%20until%20the%20user%20manually%20types%20in%20%22abort%22%2C%20which%20is%20the%20command%20the%20dialogRootBot%20is%20monitoring%20to%20cancel%20all%20dialogs%20in%20its%20onContinueDialog()%20implementation%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EHere%20is%20how%20the%20onContinueDialog()%20looks%20like%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3Easync%20onContinueDialog(innerDc)%20%7B%0A%20%20%20%20%20%20%20%20const%20activeSkill%20%3D%20await%20this.activeSkillProperty.get(innerDc.context%2C%20()%20%3D%26gt%3B%20null)%3B%0A%20%20%20%20%20%20%20%20const%20activity%20%3D%20innerDc.context.activity%3B%0A%20%20%20%20%20%20%20%20if%20(activeSkill%20!%3D%20null%20%26amp%3B%26amp%3B%20activity.type%20%3D%3D%3D%20ActivityTypes.Message%20%26amp%3B%26amp%3B%20activity.text)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(activity.text.toLocaleLowerCase()%20%3D%3D%3D%20'abort')%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Cancel%20all%20dialogs%20when%20the%20user%20says%20abort.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20The%20SkillDialog%20automatically%20sends%20an%20EndOfConversation%20message%20to%20the%20skill%20to%20let%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20skill%20know%20that%20it%20needs%20to%20end%20its%20current%20dialogs%2C%20too.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20await%20innerDc.cancelAllDialogs()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20await%20innerDc.replaceDialog(this.initialDialogId%2C%20%7B%20text%3A%20'Request%20canceled!'%20%7D)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20return%20await%20super.onContinueDialog(innerDc)%3B%0A%20%20%20%20%7D%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EI%20modeled%20this%20after%20the%26nbsp%3Bbotbuilder-samples%5Csamples%5Cjavascript_nodejs%5C81.skills-skilldialog%20sample.%26nbsp%3B%20If%20I%20were%20to%20change%20the%20skillDialogBot%20and%20have%20it%20do%20a%20ConfirmPrompt()%20before%20the%20finalStep()'s%20endDialog()%2C%20then%20the%20conversation%20ends%20correctly%20with%20the%20skillDialogBot()%20posting%20the%20dialog's%20results%20to%20the%20rootDialogBot.%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EFor%20the%20sake%20of%20clarity%2C%20this%20is%20how%20the%20bookingDialog%20in%20the%20skills-skilldialog%20sample%20looks%20like%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3E%2F**%0A%20%20%20%20%20*%20Confirm%20the%20information%20the%20user%20has%20provided.%0A%20%20%20%20%20*%2F%0A%20%20%20%20async%20confirmStep(stepContext)%20%7B%0A%20%20%20%20%20%20%20%20const%20bookingDetails%20%3D%20stepContext.options%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Capture%20the%20results%20of%20the%20previous%20step.%0A%20%20%20%20%20%20%20%20bookingDetails.travelDate%20%3D%20stepContext.result%3B%0A%20%20%20%20%20%20%20%20const%20messageText%20%3D%20%60Please%20confirm%2C%20I%20have%20you%20traveling%20to%3A%20%24%7B%20bookingDetails.destination%20%7D%20from%3A%20%24%7B%20bookingDetails.origin%20%7D%20on%3A%20%24%7B%20bookingDetails.travelDate%20%7D.%20Is%20this%20correct%3F%60%3B%0A%20%20%20%20%20%20%20%20const%20msg%20%3D%20MessageFactory.text(messageText%2C%20messageText%2C%20InputHints.ExpectingInput)%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Offer%20a%20YES%2FNO%20prompt.%0A%20%20%20%20%20%20%20%20return%20await%20stepContext.prompt(CONFIRM_PROMPT%2C%20%7B%20prompt%3A%20msg%20%7D)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F**%0A%20%20%20%20%20*%20Complete%20the%20interaction%20and%20end%20the%20dialog.%0A%20%20%20%20%20*%2F%0A%20%20%20%20async%20finalStep(stepContext)%20%7B%0A%20%20%20%20%20%20%20%20if%20(stepContext.result%20%3D%3D%3D%20true)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20bookingDetails%20%3D%20stepContext.options%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20await%20stepContext.endDialog(bookingDetails)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20await%20stepContext.endDialog()%3B%0A%20%20%20%20%7D%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EIs%20it%20not%20possible%20to%20endDialog()%20without%20a%20prompt%3F%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EThank%20You%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-LABS%20id%3D%22lingo-labs-1476612%22%20slang%3D%22en-US%22%3E%3CLINGO-LABEL%3EBot%20Framework%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3EMicrosoft%20Teams%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3Enodejs%3C%2FLINGO-LABEL%3E%3C%2FLINGO-LABS%3E%3CLINGO-SUB%20id%3D%22lingo-sub-1479658%22%20slang%3D%22en-US%22%3ERe%3A%20Skill%20Dialog%3A%20endDialog()%20does%20not%20work%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-1479658%22%20slang%3D%22en-US%22%3E%3CP%3E%3CA%20href%3D%22https%3A%2F%2Ftechcommunity.microsoft.com%2Ft5%2Fuser%2Fviewprofilepage%2Fuser-id%2F493333%22%20target%3D%22_blank%22%3E%40voonsionglum%3C%2FA%3E%2C%26nbsp%3B%3CSPAN%20class%3D%22comment-copy%22%3EEnd%20dialog%20method%20provides%20the%20collected%20data%20as%20return%20value%20back%20to%20the%20parent%20context.%20This%20can%20be%20the%20bot's%20turn%20handler%20or%20an%20earlier%20active%20dialog%20on%20the%20dialog%20stack.%20This%20is%20how%20the%20prompt%20classes%20are%20designed.%20Please%20check%20documentation%20(%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbot-service%2Fbot-builder-dialog-manage-conversation-flow%3Fview%3Dazure-bot-service-4.0%26amp%3Btabs%3Dcsharp%22%20target%3D%22_self%22%20rel%3D%22noopener%20noreferrer%20noopener%20noreferrer%20noopener%20noreferrer%22%3EImplement%20Dialog%3C%2FA%3E%2C%20%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbot-service%2Fbot-builder-concept-dialog%3Fview%3Dazure-bot-service-4.0%23using-dialogs%22%20target%3D%22_self%22%20rel%3D%22noopener%20noreferrer%20noopener%20noreferrer%20noopener%20noreferrer%22%3EUsing%20Dialogs%3C%2FA%3E)to%20get%20the%20clear%20idea%20on%20how%20Dialogs%20work%3C%2FSPAN%3E%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3E%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-SUB%20id%3D%22lingo-sub-1483074%22%20slang%3D%22en-US%22%3ERe%3A%20Skill%20Dialog%3A%20endDialog()%20does%20not%20work%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-1483074%22%20slang%3D%22en-US%22%3E%3CP%3EFor%20the%20sake%20of%20reference%2C%20when%20using%20stepContext.beginDialog()%2C%20it%20appears%20that%20we%20cannot%20reliably%20endDialog()%20if%20the%20waterfall%20dialog%20does%20not%20have%20a%20prompt%20step%20added.%26nbsp%3B%20If%20there%20is%20a%20use%20case%20where%20we%20want%20to%20use%20a%20skillDialogBot%20and%20call%20a%20specific%20dialog%20in%20the%20skillDialogBot%20via%20stepContext.beginDialog()%2C%20and%20the%20called%20dialog%20is%20only%20doing%20processing%20(eg.%20call%20REST%20APIs)%20without%20the%20need%20to%20prompt%20users%20for%20further%20information%2C%20at%20the%20end%20of%20the%20processing%2C%20we%20can%20opt%20to%20end%20the%20conversation%20instead.%26nbsp%3B%26nbsp%3B%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EHence%2C%20assuming%20we%20have%20finalStep()%20bound%20to%20the%20waterfall%20dialog%20as%20the%20last%20step%2C%20just%20use%20the%20following%20in%20finalStep()%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-javascript%22%3E%3CCODE%3Ereturn%20await%20stepContext.context.sendActivity(%7B%0A%20%20%20type%3A%20ActivityTypes.EndOfConversation%2C%0A%20code%3A%20EndOfConversationCodes.CompletedSuccessfully%2C%0A%20value%3A%20stepContext.options%0A%7D)%3B%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3C%2FLINGO-BODY%3E
Highlighted
Contributor

Hi,

I am implementing a dialogRootBot that will call a skillDialogBot.  

 

My dialogRootBot is able to invoke the skillDialogBot.  The conversation can progress until the point where the skillDialogBot is supposed to return the results to the dialogRootBot.

 

The skillDialogBot has the following setup

this.addDialog(new TextPrompt(TEXT_PROMPT))
	.addDialog(new ConfirmPrompt(CONFIRM_PROMPT))
	.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
		this.processStep.bind(this)
	]));

 

The processStep is laid out like this

async processStep(stepContext) {
	const details = stepContext.options;
	details.result = {
		status: 'success'
	};
	return await stepContext.endDialog(stepContext.options);
}

I was expecting the dialogRootBot to get the result from the skillDialogBot after processStep has called endDialog, but that never happens.  Instead, the user is stuck with the skillDialogBot until the user manually types in "abort", which is the command the dialogRootBot is monitoring to cancel all dialogs in its onContinueDialog() implementation

 

Here is how the onContinueDialog() looks like

async onContinueDialog(innerDc) {
        const activeSkill = await this.activeSkillProperty.get(innerDc.context, () => null);
        const activity = innerDc.context.activity;
        if (activeSkill != null && activity.type === ActivityTypes.Message && activity.text) {
            if (activity.text.toLocaleLowerCase() === 'abort') {
                // Cancel all dialogs when the user says abort.
                // The SkillDialog automatically sends an EndOfConversation message to the skill to let the
                // skill know that it needs to end its current dialogs, too.
                await innerDc.cancelAllDialogs();
                return await innerDc.replaceDialog(this.initialDialogId, { text: 'Request canceled!' });
            }
        }

        return await super.onContinueDialog(innerDc);
    }

 

I modeled this after the botbuilder-samples\samples\javascript_nodejs\81.skills-skilldialog sample.  If I were to change the skillDialogBot and have it do a ConfirmPrompt() before the finalStep()'s endDialog(), then the conversation ends correctly with the skillDialogBot() posting the dialog's results to the rootDialogBot.

 

For the sake of clarity, this is how the bookingDialog in the skills-skilldialog sample looks like

 

/**
     * Confirm the information the user has provided.
     */
    async confirmStep(stepContext) {
        const bookingDetails = stepContext.options;

        // Capture the results of the previous step.
        bookingDetails.travelDate = stepContext.result;
        const messageText = `Please confirm, I have you traveling to: ${ bookingDetails.destination } from: ${ bookingDetails.origin } on: ${ bookingDetails.travelDate }. Is this correct?`;
        const msg = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);

        // Offer a YES/NO prompt.
        return await stepContext.prompt(CONFIRM_PROMPT, { prompt: msg });
    }

    /**
     * Complete the interaction and end the dialog.
     */
    async finalStep(stepContext) {
        if (stepContext.result === true) {
            const bookingDetails = stepContext.options;
            return await stepContext.endDialog(bookingDetails);
        }
        return await stepContext.endDialog();
    }

 

Is it not possible to endDialog() without a prompt?

 

Thank You

2 Replies
Highlighted
Best Response confirmed by voonsionglum (Contributor)
Solution

@voonsionglumEnd dialog method provides the collected data as return value back to the parent context. This can be the bot's turn handler or an earlier active dialog on the dialog stack. This is how the prompt classes are designed. Please check documentation (Implement Dialog, Using Dialogs)to get the clear idea on how Dialogs work 

Highlighted

For the sake of reference, when using stepContext.beginDialog(), it appears that we cannot reliably endDialog() if the waterfall dialog does not have a prompt step added.  If there is a use case where we want to use a skillDialogBot and call a specific dialog in the skillDialogBot via stepContext.beginDialog(), and the called dialog is only doing processing (eg. call REST APIs) without the need to prompt users for further information, at the end of the processing, we can opt to end the conversation instead.  

 

Hence, assuming we have finalStep() bound to the waterfall dialog as the last step, just use the following in finalStep()

 

return await stepContext.context.sendActivity({
   type: ActivityTypes.EndOfConversation,
	code: EndOfConversationCodes.CompletedSuccessfully,
	value: stepContext.options
});