feat(mma): Implement interception logic in GUI and Conductor

This commit is contained in:
2026-02-27 22:29:55 -05:00
parent 1017a4d807
commit 4c53ca11da
3 changed files with 133 additions and 17 deletions

109
gui_2.py
View File

@@ -131,6 +131,29 @@ class MMAApprovalDialog:
return self._approved, self._payload return self._approved, self._payload
class MMASpawnApprovalDialog:
def __init__(self, ticket_id: str, role: str, prompt: str, context_md: str):
self._ticket_id = ticket_id
self._role = role
self._prompt = prompt
self._context_md = context_md
self._condition = threading.Condition()
self._done = False
self._approved = False
self._abort = False
def wait(self) -> dict:
with self._condition:
while not self._done:
self._condition.wait(timeout=0.1)
return {
'approved': self._approved,
'abort': self._abort,
'prompt': self._prompt,
'context_md': self._context_md
}
class App: class App:
"""The main ImGui interface orchestrator for Manual Slop.""" """The main ImGui interface orchestrator for Manual Slop."""
@@ -246,6 +269,13 @@ class App:
self._mma_approval_edit_mode = False self._mma_approval_edit_mode = False
self._mma_approval_payload = "" self._mma_approval_payload = ""
# MMA Spawn approval state
self._pending_mma_spawn = None
self._mma_spawn_open = False
self._mma_spawn_edit_mode = False
self._mma_spawn_prompt = ''
self._mma_spawn_context = ''
# Orchestration State # Orchestration State
self.ui_epic_input = "" self.ui_epic_input = ""
self.proposed_tracks: list[dict] = [] self.proposed_tracks: list[dict] = []
@@ -989,6 +1019,21 @@ class App:
if "dialog_container" in task: if "dialog_container" in task:
task["dialog_container"][0] = dlg task["dialog_container"][0] = dlg
elif action == "mma_spawn_approval":
dlg = MMASpawnApprovalDialog(
task.get("ticket_id"),
task.get("role"),
task.get("prompt"),
task.get("context_md")
)
self._pending_mma_spawn = task
self._mma_spawn_prompt = task.get("prompt", "")
self._mma_spawn_context = task.get("context_md", "")
self._mma_spawn_open = True
self._mma_spawn_edit_mode = False
if "dialog_container" in task:
task["dialog_container"][0] = dlg
except Exception as e: except Exception as e:
print(f"Error executing GUI task: {e}") print(f"Error executing GUI task: {e}")
@@ -1020,7 +1065,7 @@ class App:
else: else:
print("[DEBUG] No pending dialog to reject") print("[DEBUG] No pending dialog to reject")
def _handle_mma_respond(self, approved: bool, payload: str = None): def _handle_mma_respond(self, approved: bool, payload: str = None, abort: bool = False, prompt: str = None, context_md: str = None):
if self._pending_mma_approval: if self._pending_mma_approval:
dlg = self._pending_mma_approval.get("dialog_container", [None])[0] dlg = self._pending_mma_approval.get("dialog_container", [None])[0]
if dlg: if dlg:
@@ -1032,6 +1077,20 @@ class App:
dlg._condition.notify_all() dlg._condition.notify_all()
self._pending_mma_approval = None self._pending_mma_approval = None
if self._pending_mma_spawn:
dlg = self._pending_mma_spawn.get("dialog_container", [None])[0]
if dlg:
with dlg._condition:
dlg._approved = approved
dlg._abort = abort
if prompt is not None:
dlg._prompt = prompt
if context_md is not None:
dlg._context_md = context_md
dlg._done = True
dlg._condition.notify_all()
self._pending_mma_spawn = None
def _handle_approve_ask(self): def _handle_approve_ask(self):
"""Responds with approval for a pending /api/ask request.""" """Responds with approval for a pending /api/ask request."""
if not self._ask_request_id: return if not self._ask_request_id: return
@@ -1821,6 +1880,54 @@ class App:
imgui.close_current_popup() imgui.close_current_popup()
imgui.end_popup() imgui.end_popup()
# MMA Spawn Approval Modal
if self._pending_mma_spawn:
if not self._mma_spawn_open:
imgui.open_popup("MMA Spawn Approval")
self._mma_spawn_open = True
self._mma_spawn_edit_mode = False
else:
self._mma_spawn_open = False
if imgui.begin_popup_modal("MMA Spawn Approval", None, imgui.WindowFlags_.always_auto_resize)[0]:
if not self._pending_mma_spawn:
imgui.close_current_popup()
else:
role = self._pending_mma_spawn.get("role", "??")
ticket_id = self._pending_mma_spawn.get("ticket_id", "??")
imgui.text(f"Spawning {role} for Ticket {ticket_id}")
imgui.separator()
if self._mma_spawn_edit_mode:
imgui.text("Edit Prompt:")
_, self._mma_spawn_prompt = imgui.input_text_multiline("##spawn_prompt", self._mma_spawn_prompt, imgui.ImVec2(800, 200))
imgui.text("Edit Context MD:")
_, self._mma_spawn_context = imgui.input_text_multiline("##spawn_context", self._mma_spawn_context, imgui.ImVec2(800, 300))
else:
imgui.text("Proposed Prompt:")
imgui.begin_child("spawn_prompt_preview", imgui.ImVec2(800, 150), True)
imgui.text_unformatted(self._mma_spawn_prompt)
imgui.end_child()
imgui.text("Proposed Context MD:")
imgui.begin_child("spawn_context_preview", imgui.ImVec2(800, 250), True)
imgui.text_unformatted(self._mma_spawn_context)
imgui.end_child()
imgui.separator()
if imgui.button("Approve", imgui.ImVec2(120, 0)):
self._handle_mma_respond(approved=True, prompt=self._mma_spawn_prompt, context_md=self._mma_spawn_context)
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Edit Mode" if not self._mma_spawn_edit_mode else "Preview Mode", imgui.ImVec2(120, 0)):
self._mma_spawn_edit_mode = not self._mma_spawn_edit_mode
imgui.same_line()
if imgui.button("Abort", imgui.ImVec2(120, 0)):
self._handle_mma_respond(approved=False, abort=True)
imgui.close_current_popup()
imgui.end_popup()
if self.show_script_output: if self.show_script_output:
if self._trigger_script_blink: if self._trigger_script_blink:
self._trigger_script_blink = False self._trigger_script_blink = False

View File

@@ -219,20 +219,25 @@ def confirm_spawn(role: str, prompt: str, context_md: str, event_queue: events.A
time.sleep(0.1) time.sleep(0.1)
if dialog_container[0]: if dialog_container[0]:
approved, final_payload = dialog_container[0].wait() res = dialog_container[0].wait()
# Extract modifications from final_payload if it's a dict if isinstance(res, dict):
approved = res.get("approved", False)
abort = res.get("abort", False)
modified_prompt = res.get("prompt", prompt)
modified_context = res.get("context_md", context_md)
return approved and not abort, modified_prompt, modified_context
else:
# Fallback for old tuple style if any
approved, final_payload = res
modified_prompt = prompt modified_prompt = prompt
modified_context = context_md modified_context = context_md
if isinstance(final_payload, dict): if isinstance(final_payload, dict):
modified_prompt = final_payload.get("prompt", prompt) modified_prompt = final_payload.get("prompt", prompt)
modified_context = final_payload.get("context_md", context_md) modified_context = final_payload.get("context_md", context_md)
return approved, modified_prompt, modified_context return approved, modified_prompt, modified_context
return False, prompt, context_md return False, prompt, context_md
def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files: List[str] = None, event_queue: events.AsyncEventQueue = None, engine: Optional['ConductorEngine'] = None, md_content: str = ""): def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files: List[str] = None, event_queue: events.AsyncEventQueue = None, engine: Optional['ConductorEngine'] = None, md_content: str = ""):
""" """
Simulates the lifecycle of a single agent working on a ticket. Simulates the lifecycle of a single agent working on a ticket.

View File

@@ -11,7 +11,11 @@ class MockDialog:
self.approved = approved self.approved = approved
self.final_payload = final_payload self.final_payload = final_payload
def wait(self): def wait(self):
return self.approved, self.final_payload # Match the new return format: a dictionary
res = {'approved': self.approved, 'abort': False}
if self.final_payload:
res.update(self.final_payload)
return res
@pytest.fixture @pytest.fixture
def mock_ai_client(): def mock_ai_client():