refactor(controller): Replace legacy _set_status calls with direct property assignment

This commit is contained in:
2026-05-09 08:36:33 -04:00
parent 406b822477
commit b3065b0b17
+49 -56
View File
@@ -671,13 +671,7 @@ class AppController:
else:
ai_client._gemini_cli_adapter.binary_path = str(path)
def _set_status(self, status: str) -> None:
"""Thread-safe update of ai_status via the GUI task queue."""
self.ai_status = status
def _set_mma_status(self, status: str) -> None:
"""Thread-safe update of mma_status via the GUI task queue."""
self.mma_status = status
def _set_rag_status(self, status: str) -> None:
"""Thread-safe update of rag_status via the GUI task queue."""
@@ -1236,7 +1230,7 @@ class AppController:
session_dir = log_path.parent
if not log_file.exists():
self._set_status(f"log file not found: {log_file}")
self.ai_status = f"log file not found: {log_file}"
return
def _resolve_log_ref(content: Any, session_dir: Path) -> str:
@@ -1376,7 +1370,7 @@ class AppController:
except json.JSONDecodeError:
continue
except Exception as e:
self._set_status(f"log load error: {e}")
self.ai_status = f"log load error: {e}"
return
self.session_usage = new_usage
@@ -1395,7 +1389,7 @@ class AppController:
self.prior_tool_calls = final_tool_calls
self.is_viewing_prior_session = True
self._trigger_gui_refresh()
self._set_status(f"viewing prior session: {session_dir.name} ({len(entries)} entries)")
self.ai_status = f"viewing prior session: {session_dir.name} ({len(entries)} entries)"
def cb_exit_prior_session(self):
@@ -1418,11 +1412,11 @@ class AppController:
self.prior_disc_entries.clear()
self.prior_tool_calls.clear()
self._trigger_gui_refresh()
self._set_status('idle')
self.ai_status = 'idle'
def cb_prune_logs(self) -> None:
"""Manually triggers the log pruning process with aggressive thresholds."""
self._set_status("Manual prune started (Age > 0d, Size < 100KB)...")
self.ai_status = "Manual prune started (Age > 0d, Size < 100KB)..."
def run_manual_prune() -> None:
try:
@@ -1433,9 +1427,9 @@ class AppController:
# Aggressive: Prune anything not whitelisted, even if just created, if under 100KB
# Note: max_age_days=0 means cutoff is NOW.
pruner.prune(max_age_days=0, min_size_kb=100)
self._set_status("Manual prune complete.")
self.ai_status = "Manual prune complete."
except Exception as e:
self._set_status(f"Manual prune error: {e}")
self.ai_status = f"Manual prune error: {e}"
print(f"Error during manual log pruning: {e}")
thread = threading.Thread(target=run_manual_prune, daemon=True)
@@ -1481,7 +1475,7 @@ class AppController:
thread.start()
def _fetch_models(self, provider: str) -> None:
self._set_status("fetching models...")
self.ai_status = "fetching models..."
def do_fetch() -> None:
try:
@@ -1497,9 +1491,9 @@ class AppController:
if self.current_model not in models_list and models_list:
self.current_model = models_list[0]
ai_client.set_provider(self._current_provider, self.current_model)
self._set_status(f"models loaded: {len(models_list)}")
self.ai_status = f"models loaded: {len(models_list)}"
except Exception as e:
self._set_status(f"model fetch error: {e}")
self.ai_status = f"model fetch error: {e}"
self.models_thread = threading.Thread(target=do_fetch, daemon=True)
self.models_thread.start()
@@ -1611,7 +1605,7 @@ class AppController:
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
"""Processes a UserRequestEvent by calling the AI client."""
self._set_status('sending...')
self.ai_status = 'sending...'
ai_client.set_current_tier(None) # Ensure main discussion is untagged
# Clear response area for new turn
self.ai_response = ""
@@ -1785,9 +1779,9 @@ class AppController:
def _confirm_and_run(self, script: str, base_dir: str, qa_callback: Optional[Callable[[str], str]] = None, patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> Optional[str]:
if self.test_hooks_enabled and not getattr(self, "ui_manual_approve", False):
self._set_status("running powershell...")
self.ai_status = "running powershell..."
output = shell_runner.run_powershell(script, base_dir, qa_callback=qa_callback, patch_callback=patch_callback)
self._set_status("powershell done, awaiting AI...")
self.ai_status = "powershell done, awaiting AI..."
return output
dialog = ConfirmDialog(script, base_dir)
is_headless = "--headless" in sys.argv
@@ -1814,10 +1808,10 @@ class AppController:
if not approved:
self._append_tool_log(final_script, "REJECTED by user")
return None
self._set_status("running powershell...")
self.ai_status = "running powershell..."
output = shell_runner.run_powershell(final_script, base_dir, qa_callback=qa_callback, patch_callback=patch_callback)
self._append_tool_log(final_script, output)
self._set_status("powershell done, awaiting AI...")
self.ai_status = "powershell done, awaiting AI..."
return output
def _append_tool_log(self, script: str, result: str, source_tier: str | None = None, elapsed_ms: float = 0.0) -> None:
@@ -2165,7 +2159,7 @@ class AppController:
self._flush_to_project()
self._flush_to_config()
models.save_config(self.config)
self._set_status("config saved")
self.ai_status = "config saved"
def _cb_reset_base_prompt(self, user_data=None) -> None:
self.ui_base_system_prompt = ai_client._SYSTEM_PROMPT
@@ -2187,7 +2181,7 @@ class AppController:
def _switch_project(self, path: str) -> None:
if not Path(path).exists():
self._set_status(f"project file not found: {path}")
self.ai_status = f"project file not found: {path}"
return
self._flush_to_project()
try:
@@ -2199,10 +2193,10 @@ class AppController:
from src.personas import PersonaManager
self.persona_manager = PersonaManager(new_root)
except Exception as e:
self._set_status(f"failed to load project: {e}")
self.ai_status = f"failed to load project: {e}"
return
self._refresh_from_project()
self._set_status(f"switched to: {Path(path).stem}")
self.ai_status = f"switched to: {Path(path).stem}"
def _refresh_from_project(self) -> None:
# Deserialize FileItems in files.paths
@@ -2391,9 +2385,9 @@ class AppController:
else:
self.disc_entries = []
self._recalculate_session_usage()
self._set_status(f"Loaded track: {state.metadata.name}")
self.ai_status = f"Loaded track: {state.metadata.name}"
except Exception as e:
self._set_status(f"Load track error: {e}")
self.ai_status = f"Load track error: {e}"
print(f"Error loading track {track_id}: {e}")
def _save_active_project(self) -> None:
@@ -2402,7 +2396,7 @@ class AppController:
cleaned = project_manager.clean_nones(self.project)
project_manager.save_project(cleaned, self.active_project_path)
except Exception as e:
self._set_status(f"save error: {e}")
self.ai_status = f"save error: {e}"
def _get_discussion_names(self) -> list[str]:
disc_sec = self.project.get("discussion", {})
@@ -2414,7 +2408,7 @@ class AppController:
disc_sec = self.project.get("discussion", {})
discussions = disc_sec.get("discussions", {})
if name not in discussions:
self._set_status(f"discussion not found: {name}")
self.ai_status = f"discussion not found: {name}"
return
self.active_discussion = name
self._track_discussion_active = False
@@ -2422,7 +2416,7 @@ class AppController:
disc_data = discussions[name]
with self._disc_entries_lock:
self.disc_entries = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
self._set_status(f"discussion: {name}")
self.ai_status = f"discussion: {name}"
def _flush_disc_entries_to_project(self) -> None:
history_strings = [project_manager.entry_to_str(e) for e in self.disc_entries]
@@ -2439,7 +2433,7 @@ class AppController:
disc_sec = self.project.setdefault("discussion", {})
discussions = disc_sec.setdefault("discussions", {})
if name in discussions:
self._set_status(f"discussion '{name}' already exists")
self.ai_status = f"discussion '{name}' already exists"
return
discussions[name] = project_manager.default_discussion()
self._switch_discussion(name)
@@ -2464,7 +2458,7 @@ class AppController:
if old_name not in discussions:
return
if new_name in discussions:
self._set_status(f"discussion '{new_name}' already exists")
self.ai_status = f"discussion '{new_name}' already exists"
return
discussions[new_name] = discussions.pop(old_name)
if self.active_discussion == old_name:
@@ -2475,7 +2469,7 @@ class AppController:
disc_sec = self.project.get("discussion", {})
discussions = disc_sec.get("discussions", {})
if len(discussions) <= 1:
self._set_status("cannot delete the last discussion")
self.ai_status = "cannot delete the last discussion"
return
if name not in discussions:
return
@@ -2558,7 +2552,7 @@ class AppController:
discussions = disc_sec.get("discussions", {})
for d_name in discussions:
discussions[d_name]["history"] = []
self._set_status("session reset")
self.ai_status = "session reset"
self.ai_response = ""
self.ui_ai_input = ""
self.ui_manual_approve = False
@@ -2581,11 +2575,11 @@ class AppController:
md, path, *_ = self._do_generate()
self.last_md = md
self.last_md_path = path
self._set_status(f"md written: {path.name}")
self.ai_status = f"md written: {path.name}"
# Refresh token budget metrics with CURRENT md
self._refresh_api_metrics({}, md_content=md)
except Exception as e:
self._set_status(f"error: {e}")
self.ai_status = f"error: {e}"
threading.Thread(target=worker, daemon=True).start()
def _handle_generate_send(self) -> None:
@@ -2600,7 +2594,7 @@ class AppController:
self.last_md = md
self.last_md_path = path
self.last_file_items = file_items
self._set_status("sending...")
self.ai_status = "sending..."
user_msg = self.ui_ai_input
# RAG Retrieval
@@ -2639,7 +2633,7 @@ class AppController:
except Exception as e:
sys.stderr.write(f"[DEBUG] _do_generate ERROR: {e}\n{traceback.format_exc()}\n")
sys.stderr.flush()
self._set_status(f"generate error: {e}")
self.ai_status = f"generate error: {e}"
threading.Thread(target=worker, daemon=True).start()
def _recalculate_session_usage(self) -> None:
@@ -2819,7 +2813,7 @@ class AppController:
sys.stderr.write("[DEBUG] _cb_plan_epic _bg_task started\n")
sys.stderr.flush()
try:
self._set_status("Planning Epic (Tier 1)...")
self.ai_status = "Planning Epic (Tier 1)..."
history = orchestrator_pm.get_track_history_summary()
sys.stderr.write(f"[DEBUG] History summary length: {len(history)}\n")
sys.stderr.flush()
@@ -2856,7 +2850,7 @@ class AppController:
"payload": tracks
})
except Exception as e:
self._set_status(f"Epic plan error: {e}")
self.ai_status = f"Epic plan error: {e}"
print(f"ERROR in _cb_plan_epic background task: {e}")
threading.Thread(target=_bg_task, daemon=True).start()
@@ -2866,7 +2860,7 @@ class AppController:
def _bg_task() -> None:
sys.stderr.write("[DEBUG] _cb_accept_tracks _bg_task started\n")
# Generate skeletons once
self._set_status("Phase 2: Generating skeletons for all tracks...")
self.ai_status = "Phase 2: Generating skeletons for all tracks..."
sys.stderr.write("[DEBUG] Creating ASTParser...\n")
parser = ASTParser(language="python")
generated_skeletons = ""
@@ -2876,7 +2870,7 @@ class AppController:
sys.stderr.write(f"[DEBUG] Scanning {len(files_to_scan)} files for skeletons...\n")
for i, file_path in enumerate(files_to_scan):
try:
self._set_status(f"Phase 2: Scanning files ({i+1}/{len(files_to_scan)})...")
self.ai_status = f"Phase 2: Scanning files ({i+1}/{len(files_to_scan)})..."
abs_path = Path(self.active_project_root) / file_path
if abs_path.exists() and abs_path.suffix == ".py":
with open(abs_path, "r", encoding="utf-8") as f:
@@ -2886,19 +2880,19 @@ class AppController:
sys.stderr.write(f"[DEBUG] Error parsing skeleton for {file_path}: {e}\n")
except Exception as e:
sys.stderr.write(f"[DEBUG] Error in scan loop: {e}\n")
self._set_status(f"Error generating skeletons: {e}")
self.ai_status = f"Error generating skeletons: {e}"
return # Exit if skeleton generation fails
sys.stderr.write("[DEBUG] Skeleton generation complete. Starting tracks...\n")
# Now loop through tracks and call _start_track_logic with generated skeletons
total_tracks = len(self.proposed_tracks)
for i, track_data in enumerate(self.proposed_tracks):
title = track_data.get("title") or track_data.get("goal", "Untitled Track")
self._set_status(f"Processing track {i+1} of {total_tracks}: '{title}'...")
self.ai_status = f"Processing track {i+1} of {total_tracks}: '{title}'..."
self._start_track_logic(track_data, skeletons_str=generated_skeletons) # Pass skeletons
sys.stderr.write("[DEBUG] All tracks started. Refreshing...\n")
with self._pending_gui_tasks_lock:
self._pending_gui_tasks.append({'action': 'refresh_from_project'}) # Ensure UI refresh after tracks are started
self._set_status(f"All {total_tracks} tracks accepted and execution started.")
self.ai_status = f"All {total_tracks} tracks accepted and execution started."
threading.Thread(target=_bg_task, daemon=True).start()
def _cb_start_track(self, user_data: Any = None) -> None:
@@ -2911,25 +2905,25 @@ class AppController:
if self.active_track and self.active_track.id == track_id:
# Use the active track object directly to start execution
print(f"[DEBUG] _cb_start_track: track_id={self.active_track.id}, desc={self.active_track.description}")
self._set_mma_status("running")
self.mma_status = "running"
engine = multi_agent_conductor.ConductorEngine(self.active_track, self.event_queue, auto_queue=not self.mma_step_mode)
self.engines[self.active_track.id] = engine
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=self.active_track.id)
full_md, _, _ = aggregate.run(flat)
threading.Thread(target=engine.run, kwargs={"md_content": full_md}, daemon=True).start()
self._set_status(f"Track '{self.active_track.description}' started.")
self.ai_status = f"Track '{self.active_track.description}' started."
elif self.active_track and self.active_track.id != track_id:
# load_track failed but active_track is still wrong - reload explicitly
print(f"[DEBUG] _cb_start_track: load failed, trying reload track_id={track_id}")
self._cb_load_track(track_id)
if self.active_track and self.active_track.id == track_id:
self._set_mma_status("running")
self.mma_status = "running"
engine = multi_agent_conductor.ConductorEngine(self.active_track, self.event_queue, auto_queue=not self.mma_step_mode)
self.engines[self.active_track.id] = engine
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=self.active_track.id)
full_md, _, _ = aggregate.run(flat)
threading.Thread(target=engine.run, kwargs={"md_content": full_md}, daemon=True).start()
self._set_status(f"Track '{self.active_track.description}' started.")
self.ai_status = f"Track '{self.active_track.description}' started."
return
idx = 0
if isinstance(user_data, int):
@@ -2940,15 +2934,15 @@ class AppController:
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._set_status(f"Track '{title}' started.")
self.ai_status = f"Track '{title}' started."
def _start_track_logic(self, track_data: dict[str, Any], skeletons_str: str | None = None) -> None:
try:
goal = track_data.get("goal", "")
title = track_data.get("title") or track_data.get("goal", "Untitled Track")
self._set_status(f"Phase 2: Generating tickets for {title}...")
self.ai_status = f"Phase 2: Generating tickets for {title}..."
skeletons = skeletons_str or "" # Use provided skeletons or empty
self._set_status("Phase 2: Calling Tech Lead...")
self.ai_status = "Phase 2: Calling Tech Lead..."
_t2_baseline = len(ai_client.get_comms_log())
raw_tickets = conductor_tech_lead.generate_tickets(goal, skeletons)
_t2_new = ai_client.get_comms_log()[_t2_baseline:]
@@ -2966,10 +2960,10 @@ class AppController:
"args": [_t2_in, _t2_out]
})
if not raw_tickets:
self._set_status(f"Error: No tickets generated for track: {title}")
self.ai_status = f"Error: No tickets generated for track: {title}"
print(f"Warning: No tickets generated for track: {title}")
return
self._set_status("Phase 2: Sorting tickets...")
self.ai_status = "Phase 2: Sorting tickets..."
try:
sorted_tickets_data = conductor_tech_lead.topological_sort(raw_tickets)
except ValueError as e:
@@ -3007,7 +3001,7 @@ class AppController:
# Start the engine in a separate thread
threading.Thread(target=engine.run, kwargs={"md_content": full_md}, daemon=True).start()
except Exception as e:
self._set_status(f"Track start error: {e}")
self.ai_status = f"Track start error: {e}"
print(f"ERROR in _start_track_logic: {e}")
def _cb_ticket_retry(self, ticket_id: str) -> None:
@@ -3198,4 +3192,3 @@ class AppController:
else:
self.active_tickets = []