grammyjs / conversations

Conversational interfaces for grammY.
https://grammy.dev/plugins/conversations
MIT License
53 stars 17 forks source link

Error: Unexpected operation performed during replay (expected 'api' but was 'wait')! It looks like the conversation builder function is non-deterministic, or it relies on external data sources. #6

Closed Karabur closed 2 years ago

Karabur commented 2 years ago

Conversation fails when used inside a menu:

steps to reproduce:

type BotContext = Context & ConversationFlavor & SessionFlavor type BotConversation = Conversation

interface SessionData {}

const bot = new Bot('token') const menu = new Menu('menu')

async function startBot() { bot.use( session({ initial: (): SessionData => ({}), }) ) bot.use(conversations())

bot.use(createConversation(testConversation, 'test')) menu.text('Click me', (ctx) => ctx.conversation.enter('test'))

bot.use(menu)

bot.on('message', (ctx) => ctx.reply('Here is a menu', { reply_markup: menu, }) )

bot.catch((err) => console.error(err))

bot.start({ timeout: 5 }) }

async function testConversation(conversation: BotConversation, ctx: BotContext) { await ctx.reply('Type anything') ctx = await conversation.wait() await ctx.reply(got ${ctx.message?.text}) }

startBot()

KnorpelSenf commented 2 years ago

I can reproduce the problem. It is caused because the menu answers the callback query asynchronously in the background, so it won't block the middleware execution. This is done for performance reasons. However, concurrent middleware execution is by nature non-deterministic. The current version of the conversations plugin is not smart enough to handle that. We have already discussed in the group chat (https://t.me/grammyjs/52658) how to support this use case. It is not an easy problem, but I think we found a good new abstraction for the internal data structures that will enable this plugin to track conversation builder functions with a certain degree of non-determinism. This will be implemented before a stable version is released.

For now, you can disable autoAnswer in the menu plugin.

const menu = new Menu<BotContext>("menu", { autoAnswer: false });

This will prevent the plugin from performing the concurrent call. Note that you will now have to call ctx.answerCallbackQuery() manually. This is not ideal, but I do not think there is another workaround at the time which could be used until the conversations plugin fixes it.

I will leave this issue open until the plugin migrated to the new internal data structures.

I have a suspicion that #2 could be fixed the same way, but there is not enough information to reproduce it, so it could also be an unrelated problem.

KnorpelSenf commented 2 years ago

Can you confirm that #15 fixes the problem?