fix: Robustness improvements for RAG tests and GUI stability
- Added import sys to src/api_hook_client.py. - Fixed App.__getattr__ to use direct attribute access on controller to avoid recursion. - Simplified _get_app_attr and _has_app_attr in src/api_hooks.py. - Centralized RAG and symbol enrichment in AppController._handle_request_event. - Updated ests/test_symbol_parsing.py to match the new enrichment flow. - Removed redundant task appending from i_status and mma_status setters. - Improved _sync_rag_engine to only set 'ready' status after indexing is confirmed. - Updated est_status_encapsulation.py to reflect setter changes.
This commit is contained in:
+61
-33
@@ -982,6 +982,7 @@ class AppController:
|
||||
'rag_mcp_tool': 'rag_mcp_tool',
|
||||
'rag_chunk_size': 'rag_chunk_size',
|
||||
'rag_chunk_overlap': 'rag_chunk_overlap',
|
||||
'rag_collection_name': 'rag_collection_name',
|
||||
'mcp_config_json': 'mcp_config_json',
|
||||
'mma_active_tier': 'active_tier',
|
||||
'ui_new_track_name': 'ui_new_track_name',
|
||||
@@ -1028,7 +1029,7 @@ class AppController:
|
||||
}
|
||||
self._gettable_fields = dict(self._settable_fields)
|
||||
self._gettable_fields.update({
|
||||
'show_windows': 'show_windows',
|
||||
'show_windows': 'show_windows', # Key 'show_windows' maps to field 'show_windows' on controller
|
||||
'ui_focus_agent': 'ui_focus_agent',
|
||||
'active_discussion': 'active_discussion',
|
||||
'_track_discussion_active': '_track_discussion_active',
|
||||
@@ -1164,8 +1165,6 @@ class AppController:
|
||||
@ai_status.setter
|
||||
def ai_status(self, value: str) -> None:
|
||||
self._ai_status = value
|
||||
with self._pending_gui_tasks_lock:
|
||||
self._pending_gui_tasks.append({"action": "set_ai_status", "value": value})
|
||||
|
||||
@property
|
||||
def mma_status(self) -> str:
|
||||
@@ -1174,8 +1173,6 @@ class AppController:
|
||||
@mma_status.setter
|
||||
def mma_status(self, value: str) -> None:
|
||||
self._mma_status = value
|
||||
with self._pending_gui_tasks_lock:
|
||||
self._pending_gui_tasks.append({"action": "set_mma_status", "value": value})
|
||||
|
||||
@property
|
||||
def thinking_indicator(self) -> bool:
|
||||
@@ -1200,10 +1197,11 @@ class AppController:
|
||||
engine = rag_engine.RAGEngine(self.rag_config, self.active_project_root)
|
||||
with self._rag_engine_lock:
|
||||
self.rag_engine = engine
|
||||
self._set_rag_status("ready")
|
||||
# If the engine is empty and we have files, trigger indexing
|
||||
if self.rag_engine and self.rag_engine.is_empty() and self.files:
|
||||
self._rebuild_rag_index()
|
||||
else:
|
||||
self._set_rag_status("ready")
|
||||
except Exception as e:
|
||||
self._set_rag_status(f"error: {e}")
|
||||
sys.stderr.write(f"[DEBUG RAG] Failed to sync engine: {e}\n")
|
||||
@@ -1266,6 +1264,15 @@ class AppController:
|
||||
def rag_mcp_tool(self, value: str) -> None:
|
||||
if self.rag_config: self.rag_config.vector_store.mcp_tool = value
|
||||
|
||||
@property
|
||||
def rag_collection_name(self) -> str:
|
||||
return self.rag_config.vector_store.collection_name if self.rag_config else "manual_slop"
|
||||
@rag_collection_name.setter
|
||||
def rag_collection_name(self, value: str) -> None:
|
||||
if self.rag_config:
|
||||
self.rag_config.vector_store.collection_name = value
|
||||
self._sync_rag_engine()
|
||||
|
||||
@property
|
||||
def mcp_config_json(self) -> str:
|
||||
return json.dumps(self.mcp_config.to_dict()) if self.mcp_config else "{}"
|
||||
@@ -1576,7 +1583,7 @@ class AppController:
|
||||
self.active_discussion = disc_sec.get("active", "main")
|
||||
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
self.disc_entries[:] = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
# UI state
|
||||
self.ui_output_dir = self.project.get("output", {}).get("output_dir", "./md_gen")
|
||||
self.ui_files_base_dir = self.project.get("files", {}).get("base_dir", ".")
|
||||
@@ -1621,7 +1628,7 @@ class AppController:
|
||||
self.rag_engine = None
|
||||
if self.rag_config.enabled:
|
||||
self._sync_rag_engine()
|
||||
|
||||
|
||||
from src.personas import PersonaManager
|
||||
self.persona_manager = PersonaManager(Path(self.active_project_path).parent if self.active_project_path else None)
|
||||
self.personas = self.persona_manager.load_all()
|
||||
@@ -2115,6 +2122,45 @@ class AppController:
|
||||
[C: tests/test_live_gui_integration_v2.py:test_user_request_error_handling, tests/test_live_gui_integration_v2.py:test_user_request_integration_flow, tests/test_rag_integration.py:test_rag_integration]
|
||||
"""
|
||||
self.ai_status = 'sending...'
|
||||
|
||||
user_msg = event.prompt
|
||||
|
||||
# 1. RAG Retrieval (Enrich prompt before logging to history)
|
||||
if self.rag_engine and self.rag_config and self.rag_config.enabled:
|
||||
try:
|
||||
chunks = self.rag_engine.search(user_msg)
|
||||
if chunks:
|
||||
context_block = "## Retrieved Context\n\n"
|
||||
for i, chunk in enumerate(chunks):
|
||||
path = chunk.get("metadata", {}).get("path", "unknown")
|
||||
context_block += f"### Chunk {i+1} (Source: {path})\n{chunk.get('document', '')}\n\n"
|
||||
user_msg = context_block + user_msg
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"RAG search error: {e}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
# 2. Symbol Resolution (Enrich prompt before logging to history)
|
||||
try:
|
||||
symbols = parse_symbols(user_msg)
|
||||
file_paths = [f['path'] for f in event.file_items]
|
||||
for symbol in symbols:
|
||||
res = get_symbol_definition(symbol, file_paths)
|
||||
if res:
|
||||
file_path, definition, line = res
|
||||
user_msg += f'\n\n[Definition: {symbol} from {file_path} (line {line})]\n```python\n{definition}\n```'
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"Symbol resolution error: {e}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
# 3. Log the final enriched prompt to history
|
||||
self.event_queue.put("comms", {
|
||||
"kind": "request",
|
||||
"payload": {
|
||||
"message": user_msg,
|
||||
"collapsed": False
|
||||
}
|
||||
})
|
||||
|
||||
ai_client.set_current_tier(None) # Ensure main discussion is untagged
|
||||
# Clear response area for new turn
|
||||
self.ai_response = ""
|
||||
@@ -2131,7 +2177,7 @@ class AppController:
|
||||
try:
|
||||
resp = ai_client.send(
|
||||
event.stable_md,
|
||||
event.prompt,
|
||||
user_msg,
|
||||
event.base_dir,
|
||||
event.file_items,
|
||||
event.disc_text,
|
||||
@@ -2140,7 +2186,7 @@ class AppController:
|
||||
pre_tool_callback=self._confirm_and_run,
|
||||
qa_callback=ai_client.run_tier4_analysis,
|
||||
patch_callback=ai_client.run_tier4_patch_callback,
|
||||
rag_engine=self.rag_engine
|
||||
rag_engine=None # Already handled above
|
||||
)
|
||||
self.event_queue.put("response", {"text": resp, "status": "done", "role": "AI"})
|
||||
except ai_client.ProviderError as e:
|
||||
@@ -2621,7 +2667,7 @@ class AppController:
|
||||
self.active_discussion = disc_sec.get("active", "main")
|
||||
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
self.disc_entries[:] = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
proj = self.project
|
||||
self.ui_output_dir = proj.get("output", {}).get("output_dir", "./md_gen")
|
||||
self.ui_files_base_dir = proj.get("files", {}).get("base_dir", ".")
|
||||
@@ -2671,7 +2717,7 @@ class AppController:
|
||||
track_history = project_manager.load_track_history(self.active_track.id, self.active_project_root)
|
||||
if track_history:
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = models.parse_history_entries(track_history, self.disc_roles)
|
||||
self.disc_entries[:] = models.parse_history_entries(track_history, self.disc_roles)
|
||||
self.preset_manager.project_root = Path(self.active_project_root)
|
||||
self.presets = self.preset_manager.load_all()
|
||||
self.tool_preset_manager.project_root = Path(self.active_project_root)
|
||||
@@ -2855,9 +2901,9 @@ class AppController:
|
||||
history = project_manager.load_track_history(track_id, self.active_project_root)
|
||||
with self._disc_entries_lock:
|
||||
if history:
|
||||
self.disc_entries = models.parse_history_entries(history, self.disc_roles)
|
||||
self.disc_entries[:] = models.parse_history_entries(history, self.disc_roles)
|
||||
else:
|
||||
self.disc_entries = []
|
||||
self.disc_entries.clear()
|
||||
self._recalculate_session_usage()
|
||||
self.ai_status = f"Loaded track: {state.metadata.name}"
|
||||
except Exception as e:
|
||||
@@ -2898,7 +2944,7 @@ class AppController:
|
||||
disc_sec["active"] = name
|
||||
disc_data = discussions[name]
|
||||
with self._disc_entries_lock:
|
||||
self.disc_entries = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
self.disc_entries[:] = models.parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||
if "context_snapshot" in disc_data:
|
||||
snapshot_data = disc_data["context_snapshot"]
|
||||
self.context_files = [models.FileItem.from_dict(f) if isinstance(f, dict) else models.FileItem(path=str(f)) for f in snapshot_data]
|
||||
@@ -3154,24 +3200,6 @@ class AppController:
|
||||
self.ai_status = "sending..."
|
||||
user_msg = self.ui_ai_input
|
||||
|
||||
# RAG Retrieval
|
||||
if self.rag_engine and self.rag_config and self.rag_config.enabled:
|
||||
chunks = self.rag_engine.search(user_msg)
|
||||
if chunks:
|
||||
context_block = "## Retrieved Context\n\n"
|
||||
for i, chunk in enumerate(chunks):
|
||||
path = chunk.get("metadata", {}).get("path", "unknown")
|
||||
context_block += f"### Chunk {i+1} (Source: {path})\n{chunk.get('document', '')}\n\n"
|
||||
user_msg = context_block + user_msg
|
||||
|
||||
symbols = parse_symbols(user_msg)
|
||||
file_paths = [f['path'] for f in file_items]
|
||||
for symbol in symbols:
|
||||
res = get_symbol_definition(symbol, file_paths)
|
||||
if res:
|
||||
file_path, definition, line = res
|
||||
user_msg += f'\n\n[Definition: {symbol} from {file_path} (line {line})]\n```python\n{definition}\n```'
|
||||
|
||||
base_dir = self.active_project_root
|
||||
# Prepare event payload
|
||||
event_payload = events.UserRequestEvent(
|
||||
|
||||
Reference in New Issue
Block a user