diff --git a/src/app_controller.py b/src/app_controller.py index c2e5624..a1124a8 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -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 = [] -