WIP: PAIN4
This commit is contained in:
@@ -298,7 +298,6 @@ class AppController:
|
||||
'show_confirm_modal': 'show_confirm_modal',
|
||||
'mma_epic_input': 'ui_epic_input',
|
||||
'mma_status': 'mma_status',
|
||||
'ai_status': 'ai_status',
|
||||
'mma_active_tier': 'active_tier',
|
||||
'ui_new_track_name': 'ui_new_track_name',
|
||||
'ui_new_track_desc': 'ui_new_track_desc',
|
||||
@@ -314,11 +313,26 @@ class AppController:
|
||||
'mma_streams': 'mma_streams',
|
||||
'active_track': 'active_track',
|
||||
'active_tickets': 'active_tickets',
|
||||
'tracks': 'tracks'
|
||||
'tracks': 'tracks',
|
||||
'thinking_indicator': 'thinking_indicator',
|
||||
'operations_live_indicator': 'operations_live_indicator',
|
||||
'prior_session_indicator': 'prior_session_indicator'
|
||||
})
|
||||
|
||||
self._init_actions()
|
||||
|
||||
@property
|
||||
def thinking_indicator(self) -> bool:
|
||||
return self.ai_status in ("sending...", "streaming...")
|
||||
|
||||
@property
|
||||
def operations_live_indicator(self) -> bool:
|
||||
return not self.is_viewing_prior_session
|
||||
|
||||
@property
|
||||
def prior_session_indicator(self) -> bool:
|
||||
return self.is_viewing_prior_session
|
||||
|
||||
def _init_actions(self) -> None:
|
||||
# Set up state-related action maps
|
||||
self._clickable_actions: dict[str, Callable[..., Any]] = {
|
||||
@@ -357,6 +371,14 @@ class AppController:
|
||||
"payload": status
|
||||
})
|
||||
|
||||
def _set_mma_status(self, status: str) -> None:
|
||||
"""Thread-safe update of mma_status via the GUI task queue."""
|
||||
with self._pending_gui_tasks_lock:
|
||||
self._pending_gui_tasks.append({
|
||||
"action": "set_mma_status",
|
||||
"payload": status
|
||||
})
|
||||
|
||||
def _process_pending_gui_tasks(self) -> None:
|
||||
if not self._pending_gui_tasks:
|
||||
return
|
||||
@@ -375,6 +397,8 @@ class AppController:
|
||||
self.ai_status = task.get("payload", "")
|
||||
sys.stderr.write(f"[DEBUG] Updated ai_status via task to: {self.ai_status}\n")
|
||||
sys.stderr.flush()
|
||||
elif action == "set_mma_status":
|
||||
self.mma_status = task.get("payload", "")
|
||||
elif action == "handle_ai_response":
|
||||
payload = task.get("payload", {})
|
||||
text = payload.get("text", "")
|
||||
@@ -660,11 +684,11 @@ class AppController:
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except Exception as e:
|
||||
self.ai_status = f"log load error: {e}"
|
||||
self._set_status(f"log load error: {e}")
|
||||
return
|
||||
self.prior_session_entries = entries
|
||||
self.is_viewing_prior_session = True
|
||||
self.ai_status = f"viewing prior session: {Path(path).name} ({len(entries)} entries)"
|
||||
self._set_status(f"viewing prior session: {Path(path).name} ({len(entries)} entries)")
|
||||
|
||||
def _load_active_project(self) -> None:
|
||||
"""Loads the active project configuration, with fallbacks."""
|
||||
@@ -705,7 +729,7 @@ class AppController:
|
||||
thread.start()
|
||||
|
||||
def _fetch_models(self, provider: str) -> None:
|
||||
self.ai_status = "fetching models..."
|
||||
self._set_status("fetching models...")
|
||||
def do_fetch() -> None:
|
||||
try:
|
||||
models = ai_client.list_models(provider)
|
||||
@@ -713,9 +737,9 @@ class AppController:
|
||||
if self.current_model not in models and models:
|
||||
self.current_model = models[0]
|
||||
ai_client.set_provider(self._current_provider, self.current_model)
|
||||
self.ai_status = f"models loaded: {len(models)}"
|
||||
self._set_status(f"models loaded: {len(models)}")
|
||||
except Exception as e:
|
||||
self.ai_status = f"model fetch error: {e}"
|
||||
self._set_status(f"model fetch error: {e}")
|
||||
self.models_thread = threading.Thread(target=do_fetch, daemon=True)
|
||||
self.models_thread.start()
|
||||
|
||||
@@ -822,6 +846,7 @@ class AppController:
|
||||
|
||||
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
|
||||
"""Processes a UserRequestEvent by calling the AI client."""
|
||||
ai_client.current_tier = None # Ensure main discussion is untagged
|
||||
if self.ui_auto_add_history:
|
||||
with self._pending_history_adds_lock:
|
||||
self._pending_history_adds.append({
|
||||
@@ -931,10 +956,10 @@ class AppController:
|
||||
if self.test_hooks_enabled and not getattr(self, "ui_manual_approve", False):
|
||||
sys.stderr.write("[DEBUG] Auto-approving script.\n")
|
||||
sys.stderr.flush()
|
||||
self.ai_status = "running powershell..."
|
||||
self._set_status("running powershell...")
|
||||
output = shell_runner.run_powershell(script, base_dir, qa_callback=qa_callback)
|
||||
self._append_tool_log(script, output)
|
||||
self.ai_status = "powershell done, awaiting AI..."
|
||||
self._set_status("powershell done, awaiting AI...")
|
||||
return output
|
||||
|
||||
sys.stderr.write("[DEBUG] Creating ConfirmDialog.\n")
|
||||
@@ -972,10 +997,10 @@ class AppController:
|
||||
if not approved:
|
||||
self._append_tool_log(final_script, "REJECTED by user")
|
||||
return None
|
||||
self.ai_status = "running powershell..."
|
||||
self._set_status("running powershell...")
|
||||
output = shell_runner.run_powershell(final_script, base_dir, qa_callback=qa_callback)
|
||||
self._append_tool_log(final_script, output)
|
||||
self.ai_status = "powershell done, awaiting AI..."
|
||||
self._set_status("powershell done, awaiting AI...")
|
||||
return output
|
||||
|
||||
def _append_tool_log(self, script: str, result: str, source_tier: str | None = None) -> None:
|
||||
@@ -1068,12 +1093,53 @@ class AppController:
|
||||
state[key] = val
|
||||
return state
|
||||
|
||||
@api.get("/api/gui/mma_status", dependencies=[Depends(get_api_key)])
|
||||
def get_mma_status() -> dict[str, Any]:
|
||||
"""Dedicated endpoint for MMA-related status."""
|
||||
return {
|
||||
"mma_status": self.mma_status,
|
||||
"ai_status": self.ai_status,
|
||||
"mma_streams": self.mma_streams,
|
||||
"active_tier": self.active_tier,
|
||||
"active_tickets": self.active_tickets,
|
||||
"proposed_tracks": self.proposed_tracks
|
||||
}
|
||||
|
||||
@api.post("/api/gui", dependencies=[Depends(get_api_key)])
|
||||
def post_gui(req: dict) -> dict[str, str]:
|
||||
"""Pushes a GUI task to the event queue."""
|
||||
self.event_queue.put("gui_task", req)
|
||||
return {"status": "queued"}
|
||||
|
||||
@api.get("/api/session", dependencies=[Depends(get_api_key)])
|
||||
def get_api_session() -> dict[str, Any]:
|
||||
"""Returns current discussion session entries."""
|
||||
with self._disc_entries_lock:
|
||||
return {"session": {"entries": self.disc_entries}}
|
||||
|
||||
@api.post("/api/session", dependencies=[Depends(get_api_key)])
|
||||
def post_api_session(req: dict) -> dict[str, str]:
|
||||
"""Updates session entries."""
|
||||
entries = req.get("entries", [])
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = entries
|
||||
return {"status": "updated"}
|
||||
|
||||
@api.get("/api/project", dependencies=[Depends(get_api_key)])
|
||||
def get_api_project() -> dict[str, Any]:
|
||||
"""Returns current project data."""
|
||||
return {"project": self.project}
|
||||
|
||||
@api.get("/api/performance", dependencies=[Depends(get_api_key)])
|
||||
def get_performance() -> dict[str, Any]:
|
||||
"""Returns performance monitor metrics."""
|
||||
return {"performance": self.perf_monitor.get_metrics()}
|
||||
|
||||
@api.get("/api/gui/diagnostics", dependencies=[Depends(get_api_key)])
|
||||
def get_diagnostics() -> dict[str, Any]:
|
||||
"""Alias for performance metrics."""
|
||||
return self.perf_monitor.get_metrics()
|
||||
|
||||
@api.get("/status", dependencies=[Depends(get_api_key)])
|
||||
def status() -> dict[str, Any]:
|
||||
"""Returns the current status of the application."""
|
||||
@@ -1234,7 +1300,7 @@ class AppController:
|
||||
self._save_active_project()
|
||||
self._flush_to_config()
|
||||
save_config(self.config)
|
||||
self.ai_status = "config saved"
|
||||
self._set_status("config saved")
|
||||
|
||||
def _cb_disc_create(self) -> None:
|
||||
nm = self.ui_disc_new_name_input.strip()
|
||||
@@ -1244,7 +1310,7 @@ class AppController:
|
||||
|
||||
def _switch_project(self, path: str) -> None:
|
||||
if not Path(path).exists():
|
||||
self.ai_status = f"project file not found: {path}"
|
||||
self._set_status(f"project file not found: {path}")
|
||||
return
|
||||
self._flush_to_project()
|
||||
self._save_active_project()
|
||||
@@ -1252,11 +1318,10 @@ class AppController:
|
||||
self.project = project_manager.load_project(path)
|
||||
self.active_project_path = path
|
||||
except Exception as e:
|
||||
self.ai_status = f"failed to load project: {e}"
|
||||
self._set_status(f"failed to load project: {e}")
|
||||
return
|
||||
self._refresh_from_project()
|
||||
self.ai_status = f"switched to: {Path(path).stem}"
|
||||
|
||||
self._set_status(f"switched to: {Path(path).stem}")
|
||||
def _refresh_from_project(self) -> None:
|
||||
self.files = list(self.project.get("files", {}).get("paths", []))
|
||||
self.screenshots = list(self.project.get("screenshots", {}).get("paths", []))
|
||||
@@ -1270,10 +1335,11 @@ class AppController:
|
||||
self.ui_output_dir = proj.get("output", {}).get("output_dir", "./md_gen")
|
||||
self.ui_files_base_dir = proj.get("files", {}).get("base_dir", ".")
|
||||
self.ui_shots_base_dir = proj.get("screenshots", {}).get("base_dir", ".")
|
||||
self.ui_project_git_dir = proj.get("project", {}).get("git_dir", "")
|
||||
self.ui_project_system_prompt = proj.get("project", {}).get("system_prompt", "")
|
||||
self.ui_project_main_context = proj.get("project", {}).get("main_context", "")
|
||||
self.ui_gemini_cli_path = proj.get("gemini_cli", {}).get("binary_path", "gemini")
|
||||
proj_meta = self.project.get("project", {})
|
||||
self.ui_project_git_dir = proj_meta.get("git_dir", "")
|
||||
self.ui_project_system_prompt = proj_meta.get("system_prompt", "")
|
||||
self.ui_project_main_context = proj_meta.get("main_context", "")
|
||||
self.ui_gemini_cli_path = self.project.get("gemini_cli", {}).get("binary_path", "gemini")
|
||||
self.ui_auto_add_history = proj.get("discussion", {}).get("auto_add", False)
|
||||
self.ui_auto_scroll_comms = proj.get("project", {}).get("auto_scroll_comms", True)
|
||||
self.ui_auto_scroll_tool_calls = proj.get("project", {}).get("auto_scroll_tool_calls", True)
|
||||
@@ -1337,9 +1403,9 @@ class AppController:
|
||||
else:
|
||||
self.disc_entries = []
|
||||
self._recalculate_session_usage()
|
||||
self.ai_status = f"Loaded track: {state.metadata.name}"
|
||||
self._set_status(f"Loaded track: {state.metadata.name}")
|
||||
except Exception as e:
|
||||
self.ai_status = f"Load track error: {e}"
|
||||
self._set_status(f"Load track error: {e}")
|
||||
print(f"Error loading track {track_id}: {e}")
|
||||
|
||||
def _save_active_project(self) -> None:
|
||||
@@ -1347,7 +1413,7 @@ class AppController:
|
||||
try:
|
||||
project_manager.save_project(self.project, self.active_project_path)
|
||||
except Exception as e:
|
||||
self.ai_status = f"save error: {e}"
|
||||
self._set_status(f"save error: {e}")
|
||||
|
||||
def _get_discussion_names(self) -> list[str]:
|
||||
disc_sec = self.project.get("discussion", {})
|
||||
@@ -1359,7 +1425,7 @@ class AppController:
|
||||
disc_sec = self.project.get("discussion", {})
|
||||
discussions = disc_sec.get("discussions", {})
|
||||
if name not in discussions:
|
||||
self.ai_status = f"discussion not found: {name}"
|
||||
self._set_status(f"discussion not found: {name}")
|
||||
return
|
||||
self.active_discussion = name
|
||||
self._track_discussion_active = False
|
||||
@@ -1367,7 +1433,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.ai_status = f"discussion: {name}"
|
||||
self._set_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]
|
||||
@@ -1384,7 +1450,7 @@ class AppController:
|
||||
disc_sec = self.project.setdefault("discussion", {})
|
||||
discussions = disc_sec.setdefault("discussions", {})
|
||||
if name in discussions:
|
||||
self.ai_status = f"discussion '{name}' already exists"
|
||||
self._set_status(f"discussion '{name}' already exists")
|
||||
return
|
||||
discussions[name] = project_manager.default_discussion()
|
||||
self._switch_discussion(name)
|
||||
@@ -1395,7 +1461,7 @@ class AppController:
|
||||
if old_name not in discussions:
|
||||
return
|
||||
if new_name in discussions:
|
||||
self.ai_status = f"discussion '{new_name}' already exists"
|
||||
self._set_status(f"discussion '{new_name}' already exists")
|
||||
return
|
||||
discussions[new_name] = discussions.pop(old_name)
|
||||
if self.active_discussion == old_name:
|
||||
@@ -1406,7 +1472,7 @@ class AppController:
|
||||
disc_sec = self.project.get("discussion", {})
|
||||
discussions = disc_sec.get("discussions", {})
|
||||
if len(discussions) <= 1:
|
||||
self.ai_status = "cannot delete the last discussion"
|
||||
self._set_status("cannot delete the last discussion")
|
||||
return
|
||||
if name not in discussions:
|
||||
return
|
||||
@@ -1489,7 +1555,7 @@ class AppController:
|
||||
for d_name in discussions:
|
||||
discussions[d_name]["history"] = []
|
||||
|
||||
self.ai_status = "session reset"
|
||||
self._set_status("session reset")
|
||||
self.ai_response = ""
|
||||
self.ui_ai_input = ""
|
||||
self.ui_manual_approve = False
|
||||
@@ -1512,11 +1578,11 @@ class AppController:
|
||||
md, path, *_ = self._do_generate()
|
||||
self.last_md = md
|
||||
self.last_md_path = path
|
||||
self.ai_status = f"md written: {path.name}"
|
||||
self._set_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.ai_status = f"error: {e}"
|
||||
self._set_status(f"error: {e}")
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _handle_generate_send(self) -> None:
|
||||
@@ -1531,7 +1597,7 @@ class AppController:
|
||||
self.last_md_path = path
|
||||
self.last_file_items = file_items
|
||||
|
||||
self.ai_status = "sending..."
|
||||
self._set_status("sending...")
|
||||
user_msg = self.ui_ai_input
|
||||
base_dir = self.ui_files_base_dir
|
||||
sys.stderr.write(f"[DEBUG] _do_generate success. Prompt: {user_msg[:50]}...\n")
|
||||
@@ -1552,7 +1618,7 @@ class AppController:
|
||||
import traceback
|
||||
sys.stderr.write(f"[DEBUG] _do_generate ERROR: {e}\n{traceback.format_exc()}\n")
|
||||
sys.stderr.flush()
|
||||
self.ai_status = f"generate error: {e}"
|
||||
self._set_status(f"generate error: {e}")
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _recalculate_session_usage(self) -> None:
|
||||
@@ -1652,7 +1718,7 @@ class AppController:
|
||||
sys.stderr.write("[DEBUG] _cb_plan_epic _bg_task started\n")
|
||||
sys.stderr.flush()
|
||||
try:
|
||||
self.ai_status = "Planning Epic (Tier 1)..."
|
||||
self._set_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()
|
||||
@@ -1687,7 +1753,7 @@ class AppController:
|
||||
"payload": tracks
|
||||
})
|
||||
except Exception as e:
|
||||
self.ai_status = f"Epic plan error: {e}"
|
||||
self._set_status(f"Epic plan error: {e}")
|
||||
print(f"ERROR in _cb_plan_epic background task: {e}")
|
||||
threading.Thread(target=_bg_task, daemon=True).start()
|
||||
|
||||
@@ -1744,12 +1810,12 @@ class AppController:
|
||||
|
||||
if self.active_track:
|
||||
# Use the active track object directly to start execution
|
||||
self.mma_status = "running"
|
||||
self._set_mma_status("running")
|
||||
engine = multi_agent_conductor.ConductorEngine(self.active_track, self.event_queue, auto_queue=not self.mma_step_mode)
|
||||
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.ai_status = f"Track '{self.active_track.description}' started."
|
||||
self._set_status(f"Track '{self.active_track.description}' started.")
|
||||
return
|
||||
|
||||
idx = 0
|
||||
@@ -1761,7 +1827,7 @@ 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.ai_status = f"Track '{title}' started."
|
||||
self._set_status(f"Track '{title}' started.")
|
||||
|
||||
def _start_track_logic(self, track_data: dict[str, Any], skeletons_str: str | None = None) -> None:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user