diff --git a/src/moai/bot/handlers/__init__.py b/src/moai/bot/handlers/__init__.py index f17a52a..d7326c4 100644 --- a/src/moai/bot/handlers/__init__.py +++ b/src/moai/bot/handlers/__init__.py @@ -7,7 +7,13 @@ project management, discussion commands, and export functionality. from telegram.ext import Application, CommandHandler from moai.bot.handlers.commands import help_command, start_command -from moai.bot.handlers.discussion import ask_command, discuss_command, open_command +from moai.bot.handlers.discussion import ( + ask_command, + discuss_command, + next_command, + open_command, + stop_command, +) from moai.bot.handlers.projects import project_command, projects_command from moai.bot.handlers.status import status_command @@ -33,3 +39,5 @@ def register_handlers(app: Application) -> None: app.add_handler(CommandHandler("ask", ask_command)) app.add_handler(CommandHandler("open", open_command)) app.add_handler(CommandHandler("discuss", discuss_command)) + app.add_handler(CommandHandler("next", next_command)) + app.add_handler(CommandHandler("stop", stop_command)) diff --git a/src/moai/bot/handlers/discussion.py b/src/moai/bot/handlers/discussion.py index 396703b..28ac79b 100644 --- a/src/moai/bot/handlers/discussion.py +++ b/src/moai/bot/handlers/discussion.py @@ -8,6 +8,7 @@ from moai.core.ai_client import MODEL_MAP, get_ai_client from moai.core.models import DiscussionType, RoundType from moai.core.orchestrator import query_models_parallel, run_discussion_round from moai.core.services.discussion import ( + complete_discussion, create_discussion, create_message, create_round, @@ -245,3 +246,100 @@ async def discuss_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> except Exception as e: await update.message.reply_text(f"Error: {e}") + + +async def next_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Handle /next command - advance to the next discussion round. + + Requires an active discussion started with /discuss. Runs the next + sequential round with full context from prior rounds. + """ + # Check for active discussion state + state = context.user_data.get("discussion_state") + if state is None: + await update.message.reply_text("No active discussion. Start one with /open then /discuss.") + return + + current_round = state["current_round"] + round_limit = state["round_limit"] + + # Check if already at limit + if current_round > round_limit: + await update.message.reply_text( + f"Round limit ({round_limit}) reached. Start a new discussion with /open." + ) + del context.user_data["discussion_state"] + return + + # Show typing indicator + await update.message.chat.send_action("typing") + + try: + # Load discussion with full context + discussion = await get_discussion(state["discussion_id"]) + if discussion is None: + await update.message.reply_text("Discussion not found.") + del context.user_data["discussion_state"] + return + + # Run the next round + responses = await run_discussion_round( + discussion=discussion, + models=state["models"], + project_name=state["project_name"], + round_number=current_round, + ) + + # Build response text + response_lines = [f"*Round {current_round}/{round_limit}:*\n"] + for model, response in responses.items(): + response_lines.append(f"*{model.title()}:*\n{response}\n") + + if current_round >= round_limit: + # Final round - complete discussion + await complete_discussion(state["discussion_id"]) + response_lines.append(f"\n_Discussion complete ({round_limit} rounds)._") + del context.user_data["discussion_state"] + else: + response_lines.append( + f"\n_Round {current_round}/{round_limit} complete. Use /next or /stop._" + ) + # Update state for next round + context.user_data["discussion_state"]["current_round"] = current_round + 1 + + await update.message.reply_text( + "\n".join(response_lines), + parse_mode="Markdown", + ) + + except Exception as e: + await update.message.reply_text(f"Error: {e}") + + +async def stop_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Handle /stop command - stop the current discussion early. + + Completes the discussion at the current round and clears the session state. + """ + # Check for active discussion state + state = context.user_data.get("discussion_state") + if state is None: + await update.message.reply_text("No active discussion to stop.") + return + + current_round = state["current_round"] + + try: + # Complete the discussion in database + await complete_discussion(state["discussion_id"]) + + # Clear session state + del context.user_data["discussion_state"] + + await update.message.reply_text( + f"_Discussion stopped at round {current_round - 1}. Use /consensus to summarize._", + parse_mode="Markdown", + ) + + except Exception as e: + await update.message.reply_text(f"Error: {e}")