- config.py: extracted load_config(), find_workspace_config(), load_discussion_config() from cli.py; fixed Windows root detection - cli.py: run_discussion() now creates DiscussionEngine + EventEmitter, subscribes to events with ANSI-color output (identical to before), delegates to engine.run() - backends/custom/run.py: deprecation shim with DeprecationWarning, delegates to DiscussionEngine - 16 new config tests, all 184 tests passing Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
179 lines
5.5 KiB
Python
179 lines
5.5 KiB
Python
"""Legacy backend — delegates to DiscussionEngine.
|
|
|
|
This module is deprecated. Use meeting_room.engine.DiscussionEngine directly.
|
|
"""
|
|
|
|
import warnings
|
|
|
|
warnings.warn(
|
|
"backends.custom.run is deprecated. Use meeting_room.engine.DiscussionEngine instead.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
|
|
from meeting_room.api_client import APIClient
|
|
from meeting_room.config import load_config
|
|
from meeting_room.engine import DiscussionEngine
|
|
from meeting_room.events import (
|
|
AGENT_ERROR,
|
|
AGENT_MESSAGE,
|
|
AGENT_TURN_START,
|
|
DISCUSSION_END,
|
|
DISCUSSION_START,
|
|
ROUND_END,
|
|
ROUND_START,
|
|
TOOL_CALL,
|
|
TOOL_RESULT,
|
|
EventEmitter,
|
|
)
|
|
from meeting_room.models import ProviderConfig
|
|
from meeting_room.scenario_loader import load_scenario
|
|
from meeting_room.tools import ToolRegistry
|
|
|
|
# ── ANSI colors (kept for backward-compatible output) ──
|
|
|
|
COLORS = {
|
|
"moderator": "\033[96m",
|
|
"skeptic": "\033[91m",
|
|
"idea_generator": "\033[92m",
|
|
"analyst": "\033[93m",
|
|
}
|
|
TOOL_COLOR = "\033[90m"
|
|
RESET = "\033[0m"
|
|
BOLD = "\033[1m"
|
|
DIM = "\033[2m"
|
|
|
|
|
|
def run(scenario_path: str, config: dict) -> list[dict]:
|
|
"""Run a discussion and return conversation in legacy dict format.
|
|
|
|
This function is deprecated. Use DiscussionEngine directly.
|
|
|
|
Parameters
|
|
----------
|
|
scenario_path : str
|
|
Path to the scenario file (.md or .yaml).
|
|
config : dict
|
|
Raw config dict (as returned by :func:`load_config`).
|
|
|
|
Returns
|
|
-------
|
|
list[dict]
|
|
Conversation in legacy format ``[{role_id, name, content}, ...]``.
|
|
"""
|
|
from meeting_room.config import load_discussion_config
|
|
|
|
# Build typed config from the raw dict
|
|
discussion_config = load_discussion_config()
|
|
|
|
# Apply workdir from raw config if present
|
|
if "defaults" in config and "workdir" in config["defaults"]:
|
|
discussion_config.defaults.workdir = config["defaults"]["workdir"]
|
|
|
|
# Build engine components
|
|
providers = {
|
|
name: ProviderConfig(**vals)
|
|
for name, vals in config.get("providers", {}).items()
|
|
}
|
|
api_client = APIClient(providers=providers)
|
|
tool_registry = ToolRegistry(workdir=discussion_config.defaults.workdir)
|
|
event_bus = EventEmitter()
|
|
|
|
# Subscribe event handlers for print output (same format as before)
|
|
_subscribe_legacy_handlers(event_bus)
|
|
|
|
# Load scenario
|
|
scenario_data = load_scenario(scenario_path)
|
|
|
|
# Create and run engine
|
|
engine = DiscussionEngine(
|
|
config=discussion_config,
|
|
api_client=api_client,
|
|
tool_registry=tool_registry,
|
|
events=event_bus,
|
|
)
|
|
messages = engine.run(scenario_data)
|
|
|
|
# Convert Message objects to legacy dict format
|
|
conversation = [
|
|
{"role_id": m.role_id, "name": m.name, "content": m.content}
|
|
for m in messages
|
|
]
|
|
return conversation
|
|
|
|
|
|
def _subscribe_legacy_handlers(bus: EventEmitter) -> None:
|
|
"""Wire up event handlers that produce the same ANSI output as before."""
|
|
|
|
def on_discussion_start(event) -> None:
|
|
data = event.data
|
|
print(f"\n{BOLD}{'=' * 60}")
|
|
print(f" MEETING ROOM — {data['name']}")
|
|
print(f"{'=' * 60}{RESET}\n")
|
|
|
|
problem = data.get("problem", "")
|
|
if problem:
|
|
print(f"{BOLD}Problem:{RESET}")
|
|
for line in problem.split("\n")[:5]:
|
|
print(f" {line}")
|
|
if problem.count("\n") > 5:
|
|
print(" ...")
|
|
print()
|
|
|
|
participants = data.get("participants", [])
|
|
if participants:
|
|
print(f"{BOLD}Participants:{RESET}")
|
|
# Note: we don't have role details in the event, just names
|
|
# The engine emits participant names, not full role info.
|
|
for name in participants:
|
|
print(f" {name}")
|
|
print()
|
|
|
|
print(f"{BOLD}Rounds:{RESET} {data['rounds']}")
|
|
print(f"{DIM}{'─' * 60}{RESET}\n")
|
|
|
|
def on_round_start(event) -> None:
|
|
data = event.data
|
|
print(f"{BOLD}[Round {data['round_num']}/{data['total_rounds']}]{RESET}\n")
|
|
|
|
def on_agent_turn_start(event) -> None:
|
|
data = event.data
|
|
color = COLORS.get(data["role_id"], "")
|
|
print(f"{color}{BOLD} {data['name']}:{RESET}", end=" ")
|
|
|
|
def on_agent_message(event) -> None:
|
|
# Content printed inline after name; just end the line
|
|
print()
|
|
|
|
def on_tool_call(event) -> None:
|
|
data = event.data
|
|
print(f"\n{TOOL_COLOR} -> {data['tool_name']}({data['arguments']}){RESET}")
|
|
|
|
def on_tool_result(event) -> None:
|
|
data = event.data
|
|
preview = data["result_preview"]
|
|
suffix = "..." if len(data.get("full_result", "")) > 200 else ""
|
|
print(f"{TOOL_COLOR} <- {preview}{suffix}{RESET}")
|
|
|
|
def on_agent_error(event) -> None:
|
|
data = event.data
|
|
print(f" [Error: {data['error']}]")
|
|
|
|
def on_round_end(event) -> None:
|
|
print(f"{DIM}{'─' * 60}{RESET}\n")
|
|
|
|
def on_discussion_end(event) -> None:
|
|
data = event.data
|
|
print(f"\n{BOLD}{'=' * 60}")
|
|
print(f" DISCUSSION COMPLETE — {data['total_messages']} messages")
|
|
print(f"{'=' * 60}{RESET}\n")
|
|
|
|
bus.on(DISCUSSION_START, on_discussion_start)
|
|
bus.on(ROUND_START, on_round_start)
|
|
bus.on(AGENT_TURN_START, on_agent_turn_start)
|
|
bus.on(AGENT_MESSAGE, on_agent_message)
|
|
bus.on(TOOL_CALL, on_tool_call)
|
|
bus.on(TOOL_RESULT, on_tool_result)
|
|
bus.on(AGENT_ERROR, on_agent_error)
|
|
bus.on(ROUND_END, on_round_end)
|
|
bus.on(DISCUSSION_END, on_discussion_end) |