feat(06-01): create /consensus command handler

- Add consensus_command handler with typing indicator
- Add _format_consensus helper for Markdown output
- Register CommandHandler("consensus") in __init__.py
- Displays existing consensus or generates new one

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikkel Georgsen 2026-01-16 20:06:52 +00:00
parent 8242de5289
commit ee9f8ca3a4
2 changed files with 126 additions and 1 deletions

View file

@ -9,6 +9,7 @@ from telegram.ext import Application, CommandHandler, MessageHandler, filters
from moai.bot.handlers.commands import help_command, start_command
from moai.bot.handlers.discussion import (
ask_command,
consensus_command,
discuss_command,
mention_handler,
next_command,
@ -42,6 +43,7 @@ def register_handlers(app: Application) -> None:
app.add_handler(CommandHandler("discuss", discuss_command))
app.add_handler(CommandHandler("next", next_command))
app.add_handler(CommandHandler("stop", stop_command))
app.add_handler(CommandHandler("consensus", consensus_command))
# @mention handler - MessageHandler registered AFTER CommandHandlers
# Matches messages starting with @claude, @gpt, or @gemini followed by content

View file

@ -6,15 +6,22 @@ from telegram.ext import ContextTypes
from moai.bot.handlers.projects import get_selected_project
from moai.core.ai_client import MODEL_MAP, get_ai_client
from moai.core.models import DiscussionType, RoundType
from moai.core.orchestrator import query_model_direct, query_models_parallel, run_discussion_round
from moai.core.orchestrator import (
generate_consensus,
query_model_direct,
query_models_parallel,
run_discussion_round,
)
from moai.core.services.discussion import (
complete_discussion,
create_discussion,
create_message,
create_round,
get_active_discussion,
get_consensus,
get_current_round,
get_discussion,
save_consensus,
)
@ -425,3 +432,119 @@ async def mention_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
except Exception as e:
await update.message.reply_text(f"Error: {e}")
async def consensus_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /consensus command - generate or display consensus summary.
Requires a selected project with an active discussion. If a consensus
already exists, displays it. Otherwise generates a new one using AI.
Examples:
/consensus
"""
# Require a selected project
project = await get_selected_project(context)
if project is None:
await update.message.reply_text("No project selected. Use /project select <name> first.")
return
# Check for active discussion
discussion = await get_active_discussion(project.id)
if discussion is None:
await update.message.reply_text(
"No active discussion. Start one with /open <question> first."
)
return
# Check if consensus already exists
existing_consensus = await get_consensus(discussion.id)
if existing_consensus is not None:
# Display existing consensus
response_text = _format_consensus(
agreements=existing_consensus.agreements,
disagreements=existing_consensus.disagreements,
generated_by=existing_consensus.generated_by,
)
await update.message.reply_text(response_text, parse_mode="Markdown")
return
# Show typing indicator while generating
await update.message.chat.send_action("typing")
try:
# Reload discussion with full context
discussion = await get_discussion(discussion.id)
# Generate consensus
consensus_model = "claude"
result = await generate_consensus(discussion, model=consensus_model)
# Check for errors
if "error" in result:
await update.message.reply_text(f"Failed to generate consensus: {result['error']}")
return
# Save consensus
await save_consensus(
discussion_id=discussion.id,
agreements=result["agreements"],
disagreements=result["disagreements"],
generated_by=consensus_model,
)
# Format and display
response_text = _format_consensus(
agreements=result["agreements"],
disagreements=result["disagreements"],
generated_by=consensus_model,
)
await update.message.reply_text(response_text, parse_mode="Markdown")
except Exception as e:
await update.message.reply_text(f"Error: {e}")
def _format_consensus(
agreements: list,
disagreements: list,
generated_by: str,
) -> str:
"""Format consensus data into a Markdown string.
Args:
agreements: List of agreement strings.
disagreements: List of disagreement dicts with topic and positions.
generated_by: The model that generated the consensus.
Returns:
Formatted Markdown string.
"""
lines = ["*Consensus Summary*\n"]
# Agreements section
lines.append("*Agreements:*")
if agreements:
for point in agreements:
lines.append(f"- {point}")
else:
lines.append("- None identified")
lines.append("")
# Disagreements section
lines.append("*Disagreements:*")
if disagreements:
for disagreement in disagreements:
topic = disagreement.get("topic", "Unknown topic")
positions = disagreement.get("positions", {})
lines.append(f"- *{topic}*")
for model, position in positions.items():
lines.append(f" - {model.title()}: {position}")
else:
lines.append("- None identified")
lines.append("")
# Footer
lines.append(f"_Generated by {generated_by.title()}_")
return "\n".join(lines)