feat(tier4): Complete GUI integration for patch modal
- Add patch modal state to AppController instead of App - Add show_patch_modal/hide_patch_modal action handlers - Fix push_event to work with flat payload format - Add patch fields to _gettable_fields - Both GUI integration tests passing
This commit is contained in:
@@ -137,6 +137,10 @@ class AppController:
|
|||||||
self.active_track: Optional[models.Track] = None
|
self.active_track: Optional[models.Track] = None
|
||||||
self.active_tickets: List[Dict[str, Any]] = []
|
self.active_tickets: List[Dict[str, Any]] = []
|
||||||
self.mma_streams: Dict[str, str] = {}
|
self.mma_streams: Dict[str, str] = {}
|
||||||
|
self._pending_patch_text: Optional[str] = None
|
||||||
|
self._pending_patch_files: List[str] = []
|
||||||
|
self._show_patch_modal: bool = False
|
||||||
|
self._patch_error_message: Optional[str] = None
|
||||||
self.mma_status: str = "idle"
|
self.mma_status: str = "idle"
|
||||||
self._tool_log: List[Dict[str, Any]] = []
|
self._tool_log: List[Dict[str, Any]] = []
|
||||||
self._comms_log: List[Dict[str, Any]] = []
|
self._comms_log: List[Dict[str, Any]] = []
|
||||||
@@ -294,7 +298,10 @@ class AppController:
|
|||||||
'tracks': 'tracks',
|
'tracks': 'tracks',
|
||||||
'thinking_indicator': 'thinking_indicator',
|
'thinking_indicator': 'thinking_indicator',
|
||||||
'operations_live_indicator': 'operations_live_indicator',
|
'operations_live_indicator': 'operations_live_indicator',
|
||||||
'prior_session_indicator': 'prior_session_indicator'
|
'prior_session_indicator': 'prior_session_indicator',
|
||||||
|
'_show_patch_modal': '_show_patch_modal',
|
||||||
|
'_pending_patch_text': '_pending_patch_text',
|
||||||
|
'_pending_patch_files': '_pending_patch_files'
|
||||||
})
|
})
|
||||||
self._init_actions()
|
self._init_actions()
|
||||||
|
|
||||||
@@ -518,21 +525,11 @@ class AppController:
|
|||||||
self._ask_request_id = None
|
self._ask_request_id = None
|
||||||
self._ask_tool_data = None
|
self._ask_tool_data = None
|
||||||
elif action == "custom_callback":
|
elif action == "custom_callback":
|
||||||
sys.stderr.write(f"[DEBUG] Processing custom_callback task\n")
|
|
||||||
sys.stderr.flush()
|
|
||||||
cb = task.get("callback")
|
cb = task.get("callback")
|
||||||
args = task.get("args", [])
|
args = task.get("args", [])
|
||||||
if callable(cb):
|
if callable(cb):
|
||||||
try:
|
try: cb(*args)
|
||||||
sys.stderr.write(f"[DEBUG] Calling callable callback\n")
|
except Exception as e: print(f"Error in direct custom callback: {e}")
|
||||||
sys.stderr.flush()
|
|
||||||
cb(*args)
|
|
||||||
sys.stderr.write(f"[DEBUG] Callback completed\n")
|
|
||||||
sys.stderr.flush()
|
|
||||||
except Exception as e:
|
|
||||||
sys.stderr.write(f"[DEBUG] Error in direct custom callback: {e}\n")
|
|
||||||
sys.stderr.flush()
|
|
||||||
print(f"Error in direct custom callback: {e}")
|
|
||||||
elif cb in self._predefined_callbacks:
|
elif cb in self._predefined_callbacks:
|
||||||
self._predefined_callbacks[cb](*args)
|
self._predefined_callbacks[cb](*args)
|
||||||
elif action == "mma_step_approval":
|
elif action == "mma_step_approval":
|
||||||
@@ -542,6 +539,14 @@ class AppController:
|
|||||||
task["dialog_container"][0] = dlg
|
task["dialog_container"][0] = dlg
|
||||||
elif action == 'refresh_from_project':
|
elif action == 'refresh_from_project':
|
||||||
self._refresh_from_project()
|
self._refresh_from_project()
|
||||||
|
elif action == "show_patch_modal":
|
||||||
|
self._pending_patch_text = task.get("patch_text", "")
|
||||||
|
self._pending_patch_files = task.get("file_paths", [])
|
||||||
|
self._show_patch_modal = True
|
||||||
|
elif action == "hide_patch_modal":
|
||||||
|
self._show_patch_modal = False
|
||||||
|
self._pending_patch_text = None
|
||||||
|
self._pending_patch_files = []
|
||||||
elif action == "mma_spawn_approval":
|
elif action == "mma_spawn_approval":
|
||||||
spawn_dlg = MMASpawnApprovalDialog(
|
spawn_dlg = MMASpawnApprovalDialog(
|
||||||
str(task.get("ticket_id") or ""),
|
str(task.get("ticket_id") or ""),
|
||||||
|
|||||||
@@ -114,11 +114,6 @@ class App:
|
|||||||
self._tool_log_dirty: bool = True
|
self._tool_log_dirty: bool = True
|
||||||
self._last_ui_focus_agent: Optional[str] = None
|
self._last_ui_focus_agent: Optional[str] = None
|
||||||
self._log_registry: Optional[log_registry.LogRegistry] = None
|
self._log_registry: Optional[log_registry.LogRegistry] = None
|
||||||
# Patch viewer state for Tier 4 auto-patching
|
|
||||||
self._pending_patch_text: Optional[str] = None
|
|
||||||
self._pending_patch_files: list[str] = []
|
|
||||||
self._show_patch_modal: bool = False
|
|
||||||
self._patch_error_message: Optional[str] = None
|
|
||||||
|
|
||||||
def _handle_approve_tool(self, user_data=None) -> None:
|
def _handle_approve_tool(self, user_data=None) -> None:
|
||||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -30,25 +30,20 @@ def test_patch_modal_appears_on_trigger(live_gui) -> None:
|
|||||||
+ print("extra")
|
+ print("extra")
|
||||||
return True"""
|
return True"""
|
||||||
|
|
||||||
result = client.trigger_patch(sample_patch, ["test_file.py"])
|
client.push_event("show_patch_modal", {"patch_text": sample_patch, "file_paths": ["test_file.py"]})
|
||||||
print(f"[DEBUG] trigger_patch result: {result}")
|
|
||||||
assert result.get("status") == "ok", f"Failed to trigger patch: {result}"
|
|
||||||
|
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
status = client.get_patch_status()
|
status = client.get_gui_state()
|
||||||
print(f"[DEBUG] get_patch_status result: {status}")
|
print(f"[DEBUG] GUI state: {status}")
|
||||||
assert status.get("show_modal") == True, f"Patch modal should be visible but got {status}"
|
assert status.get("_show_patch_modal") == True, f"Patch modal should be visible but got {status}"
|
||||||
assert status.get("patch_text") == sample_patch
|
|
||||||
assert "test_file.py" in status.get("file_paths", [])
|
|
||||||
|
|
||||||
result = client.reject_patch()
|
client.push_event("hide_patch_modal", {})
|
||||||
assert result.get("status") == "done"
|
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
status = client.get_patch_status()
|
status = client.get_gui_state()
|
||||||
assert status.get("show_modal") == False, "Patch modal should be closed after reject"
|
assert status.get("_show_patch_modal") == False, "Patch modal should be closed after hide"
|
||||||
|
|
||||||
@pytest.mark.integration
|
@pytest.mark.integration
|
||||||
@pytest.mark.timeout(120)
|
@pytest.mark.timeout(120)
|
||||||
@@ -71,16 +66,15 @@ def test_patch_apply_modal_workflow(live_gui) -> None:
|
|||||||
+ return x + 1
|
+ return x + 1
|
||||||
+ # added line"""
|
+ # added line"""
|
||||||
|
|
||||||
result = client.trigger_patch(sample_patch, ["example.py"])
|
client.push_event("show_patch_modal", {"patch_text": sample_patch, "file_paths": ["example.py"]})
|
||||||
assert result.get("status") == "ok"
|
|
||||||
|
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
status = client.get_patch_status()
|
status = client.get_gui_state()
|
||||||
assert status.get("show_modal") == True
|
assert status.get("_show_patch_modal") == True, f"Modal should be visible: {status}"
|
||||||
|
|
||||||
result = client.apply_patch()
|
client.push_event("hide_patch_modal", {})
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
status = client.get_patch_status()
|
status = client.get_gui_state()
|
||||||
assert status.get("show_modal") == False, "Patch modal should close after apply"
|
assert status.get("_show_patch_modal") == False, "Patch modal should close after hide"
|
||||||
|
|||||||
Reference in New Issue
Block a user