feat(mma): Finalize Orchestrator Integration and fix all regressions
This commit is contained in:
201
gui_2.py
201
gui_2.py
@@ -251,6 +251,13 @@ class App:
|
||||
self.proposed_tracks: list[dict] = []
|
||||
self._show_track_proposal_modal = False
|
||||
|
||||
self.mma_tier_usage = {
|
||||
"Tier 1": {"input": 0, "output": 0},
|
||||
"Tier 2": {"input": 0, "output": 0},
|
||||
"Tier 3": {"input": 0, "output": 0},
|
||||
"Tier 4": {"input": 0, "output": 0},
|
||||
}
|
||||
|
||||
self._tool_log: list[tuple[str, str]] = []
|
||||
self._comms_log: list[dict] = []
|
||||
|
||||
@@ -403,7 +410,10 @@ class App:
|
||||
'token_budget_pct': '_token_budget_pct',
|
||||
'token_budget_current': '_token_budget_current',
|
||||
'token_budget_label': '_token_budget_label',
|
||||
'show_confirm_modal': 'show_confirm_modal'
|
||||
'show_confirm_modal': 'show_confirm_modal',
|
||||
'mma_epic_input': 'ui_epic_input',
|
||||
'mma_status': 'mma_status',
|
||||
'mma_active_tier': 'active_tier'
|
||||
}
|
||||
|
||||
self._clickable_actions = {
|
||||
@@ -414,6 +424,9 @@ class App:
|
||||
'btn_reject_script': self._handle_reject_script,
|
||||
'btn_project_save': self._cb_project_save,
|
||||
'btn_disc_create': self._cb_disc_create,
|
||||
'btn_mma_plan_epic': self._cb_plan_epic,
|
||||
'btn_mma_accept_tracks': self._cb_accept_tracks,
|
||||
'btn_mma_start_track': self._cb_start_track,
|
||||
}
|
||||
self._predefined_callbacks = {
|
||||
'_test_callback_func_write_to_file': self._test_callback_func_write_to_file
|
||||
@@ -899,6 +912,7 @@ class App:
|
||||
payload = task.get("payload", {})
|
||||
self.mma_status = payload.get("status", "idle")
|
||||
self.active_tier = payload.get("active_tier")
|
||||
self.mma_tier_usage = payload.get("tier_usage", self.mma_tier_usage)
|
||||
self.active_track = payload.get("track")
|
||||
self.active_tickets = payload.get("tickets", [])
|
||||
|
||||
@@ -921,7 +935,17 @@ class App:
|
||||
if item == "btn_project_new_automated":
|
||||
self._cb_new_project_automated(user_data)
|
||||
elif item in self._clickable_actions:
|
||||
self._clickable_actions[item]()
|
||||
# Check if it's a method that accepts user_data
|
||||
import inspect
|
||||
func = self._clickable_actions[item]
|
||||
try:
|
||||
sig = inspect.signature(func)
|
||||
if 'user_data' in sig.parameters:
|
||||
func(user_data=user_data)
|
||||
else:
|
||||
func()
|
||||
except Exception:
|
||||
func()
|
||||
|
||||
elif action == "select_list_item":
|
||||
item = task.get("listbox", task.get("item"))
|
||||
@@ -1098,6 +1122,20 @@ class App:
|
||||
self._loop.create_task(self._process_event_queue())
|
||||
self._loop.run_forever()
|
||||
|
||||
def shutdown(self):
|
||||
"""Cleanly shuts down the app's background tasks."""
|
||||
if self._loop.is_running():
|
||||
self._loop.call_soon_threadsafe(self._loop.stop)
|
||||
|
||||
if self._loop_thread.is_alive():
|
||||
self._loop_thread.join(timeout=2.0)
|
||||
|
||||
# Join other threads if they exist
|
||||
if self.send_thread and self.send_thread.is_alive():
|
||||
self.send_thread.join(timeout=1.0)
|
||||
if self.models_thread and self.models_thread.is_alive():
|
||||
self.models_thread.join(timeout=1.0)
|
||||
|
||||
async def _process_event_queue(self):
|
||||
"""Listens for and processes events from the AsyncEventQueue."""
|
||||
while True:
|
||||
@@ -1971,68 +2009,83 @@ class App:
|
||||
|
||||
def _cb_accept_tracks(self):
|
||||
def _bg_task():
|
||||
try:
|
||||
self.ai_status = "Generating tickets (Tier 2)..."
|
||||
|
||||
# 1. Get skeletons for context
|
||||
parser = ASTParser(language="python")
|
||||
skeletons = ""
|
||||
for file_path in self.files:
|
||||
try:
|
||||
abs_path = Path(self.ui_files_base_dir) / file_path
|
||||
if abs_path.exists() and abs_path.suffix == ".py":
|
||||
with open(abs_path, "r", encoding="utf-8") as f:
|
||||
code = f.read()
|
||||
skeletons += f"\nFile: {file_path}\n{parser.get_skeleton(code)}\n"
|
||||
except Exception as e:
|
||||
print(f"Error parsing skeleton for {file_path}: {e}")
|
||||
|
||||
# 2. For each proposed track, generate and sort tickets
|
||||
for track_data in self.proposed_tracks:
|
||||
goal = track_data.get("goal", "")
|
||||
title = track_data.get("title", "Untitled Track")
|
||||
|
||||
raw_tickets = conductor_tech_lead.generate_tickets(goal, skeletons)
|
||||
if not raw_tickets:
|
||||
print(f"Warning: No tickets generated for track: {title}")
|
||||
continue
|
||||
|
||||
try:
|
||||
sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets)
|
||||
except ValueError as e:
|
||||
print(f"Dependency error in track '{title}': {e}")
|
||||
# Fallback to unsorted if sort fails? Or skip?
|
||||
sorted_tickets_data = raw_tickets
|
||||
|
||||
# 3. Create Track and Ticket objects
|
||||
tickets = []
|
||||
for t_data in sorted_tickets_data:
|
||||
ticket = Ticket(
|
||||
id=t_data["id"],
|
||||
description=t_data["description"],
|
||||
status=t_data.get("status", "todo"),
|
||||
assigned_to=t_data.get("assigned_to", "unassigned"),
|
||||
depends_on=t_data.get("depends_on", []),
|
||||
step_mode=t_data.get("step_mode", False)
|
||||
)
|
||||
tickets.append(ticket)
|
||||
|
||||
track_id = f"track_{uuid.uuid4().hex[:8]}"
|
||||
track = Track(id=track_id, description=title, tickets=tickets)
|
||||
|
||||
# 4. Initialize ConductorEngine and run_linear loop
|
||||
engine = multi_agent_conductor.ConductorEngine(track, self.event_queue)
|
||||
|
||||
# Schedule the coroutine on the internal event loop
|
||||
asyncio.run_coroutine_threadsafe(engine.run_linear(), self._loop)
|
||||
|
||||
self.ai_status = "Tracks accepted and execution started."
|
||||
except Exception as e:
|
||||
self.ai_status = f"Track acceptance error: {e}"
|
||||
print(f"ERROR in _cb_accept_tracks background task: {e}")
|
||||
|
||||
for track_data in self.proposed_tracks:
|
||||
self._start_track_logic(track_data)
|
||||
self.ai_status = "Tracks accepted and execution started."
|
||||
threading.Thread(target=_bg_task, daemon=True).start()
|
||||
|
||||
def _cb_start_track(self, user_data=None):
|
||||
idx = 0
|
||||
if isinstance(user_data, int):
|
||||
idx = user_data
|
||||
elif isinstance(user_data, dict):
|
||||
idx = user_data.get("index", 0)
|
||||
|
||||
if 0 <= idx < len(self.proposed_tracks):
|
||||
track_data = self.proposed_tracks[idx]
|
||||
title = track_data.get("title") or track_data.get("goal", "Untitled Track")
|
||||
threading.Thread(target=lambda: self._start_track_logic(track_data), daemon=True).start()
|
||||
self.ai_status = f"Track '{title}' started."
|
||||
|
||||
def _start_track_logic(self, track_data):
|
||||
try:
|
||||
goal = track_data.get("goal", "")
|
||||
title = track_data.get("title") or track_data.get("goal", "Untitled Track")
|
||||
self.ai_status = f"Phase 2: Generating tickets for {title}..."
|
||||
|
||||
# 1. Get skeletons for context
|
||||
parser = ASTParser(language="python")
|
||||
skeletons = ""
|
||||
for i, file_path in enumerate(self.files):
|
||||
try:
|
||||
self.ai_status = f"Phase 2: Scanning files ({i+1}/{len(self.files)})..."
|
||||
abs_path = Path(self.ui_files_base_dir) / file_path
|
||||
if abs_path.exists() and abs_path.suffix == ".py":
|
||||
with open(abs_path, "r", encoding="utf-8") as f:
|
||||
code = f.read()
|
||||
skeletons += f"\nFile: {file_path}\n{parser.get_skeleton(code)}\n"
|
||||
except Exception as e:
|
||||
print(f"Error parsing skeleton for {file_path}: {e}")
|
||||
|
||||
self.ai_status = "Phase 2: Calling Tech Lead..."
|
||||
raw_tickets = conductor_tech_lead.generate_tickets(goal, skeletons)
|
||||
if not raw_tickets:
|
||||
self.ai_status = f"Error: No tickets generated for track: {title}"
|
||||
print(f"Warning: No tickets generated for track: {title}")
|
||||
return
|
||||
|
||||
self.ai_status = "Phase 2: Sorting tickets..."
|
||||
try:
|
||||
sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets)
|
||||
except ValueError as e:
|
||||
print(f"Dependency error in track '{title}': {e}")
|
||||
sorted_tickets_data = raw_tickets
|
||||
|
||||
# 3. Create Track and Ticket objects
|
||||
tickets = []
|
||||
for t_data in sorted_tickets_data:
|
||||
ticket = Ticket(
|
||||
id=t_data["id"],
|
||||
description=t_data.get("description") or t_data.get("goal", "No description"),
|
||||
status=t_data.get("status", "todo"),
|
||||
assigned_to=t_data.get("assigned_to", "unassigned"),
|
||||
depends_on=t_data.get("depends_on", []),
|
||||
step_mode=t_data.get("step_mode", False)
|
||||
)
|
||||
tickets.append(ticket)
|
||||
|
||||
track_id = f"track_{uuid.uuid4().hex[:8]}"
|
||||
track = Track(id=track_id, description=title, tickets=tickets)
|
||||
|
||||
# 4. Initialize ConductorEngine and run_linear loop
|
||||
engine = multi_agent_conductor.ConductorEngine(track, self.event_queue)
|
||||
|
||||
# Schedule the coroutine on the internal event loop
|
||||
asyncio.run_coroutine_threadsafe(engine.run_linear(), self._loop)
|
||||
except Exception as e:
|
||||
self.ai_status = f"Track start error: {e}"
|
||||
print(f"ERROR in _start_track_logic: {e}")
|
||||
|
||||
def _render_track_proposal_modal(self):
|
||||
if self._show_track_proposal_modal:
|
||||
imgui.open_popup("Track Proposal")
|
||||
@@ -2047,6 +2100,8 @@ class App:
|
||||
for idx, track in enumerate(self.proposed_tracks):
|
||||
imgui.text_colored(C_LBL, f"Track {idx+1}: {track.get('title', 'Untitled')}")
|
||||
imgui.text_wrapped(f"Goal: {track.get('goal', 'N/A')}")
|
||||
if imgui.button(f"Start This Track##{idx}"):
|
||||
self._cb_start_track(idx)
|
||||
imgui.separator()
|
||||
|
||||
if imgui.button("Accept", imgui.ImVec2(120, 0)):
|
||||
@@ -2592,9 +2647,29 @@ class App:
|
||||
else:
|
||||
imgui.text_disabled("No active MMA track.")
|
||||
|
||||
# 3. Token Usage Table
|
||||
imgui.separator()
|
||||
imgui.text("Tier Usage (Tokens)")
|
||||
if imgui.begin_table("mma_usage", 3, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
|
||||
imgui.table_setup_column("Tier")
|
||||
imgui.table_setup_column("Input")
|
||||
imgui.table_setup_column("Output")
|
||||
imgui.table_headers_row()
|
||||
|
||||
usage = self.mma_tier_usage
|
||||
for tier, stats in usage.items():
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text(tier)
|
||||
imgui.table_next_column()
|
||||
imgui.text(f"{stats.get('input', 0):,}")
|
||||
imgui.table_next_column()
|
||||
imgui.text(f"{stats.get('output', 0):,}")
|
||||
imgui.end_table()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 3. Ticket Queue
|
||||
# 4. Ticket Queue
|
||||
imgui.text("Ticket Queue")
|
||||
if imgui.begin_table("mma_tickets", 4, imgui.TableFlags_.borders_inner_h | imgui.TableFlags_.resizable):
|
||||
imgui.table_setup_column("ID", imgui.TableColumnFlags_.width_fixed, 80)
|
||||
|
||||
Reference in New Issue
Block a user