4 Commits

20 changed files with 583 additions and 26 deletions
+14 -3
View File
@@ -3,7 +3,7 @@ import json
import time
class ApiHookClient:
def __init__(self, base_url="http://127.0.0.1:8999", max_retries=2, retry_delay=0.1):
def __init__(self, base_url="http://127.0.0.1:8999", max_retries=5, retry_delay=0.2):
self.base_url = base_url
self.max_retries = max_retries
self.retry_delay = retry_delay
@@ -26,8 +26,8 @@ class ApiHookClient:
headers = {'Content-Type': 'application/json'}
last_exception = None
# Lower request timeout for local server by default
req_timeout = timeout if timeout is not None else 0.5
# Increase default request timeout for local server
req_timeout = timeout if timeout is not None else 2.0
for attempt in range(self.max_retries + 1):
try:
@@ -77,6 +77,17 @@ class ApiHookClient:
def get_session(self):
return self._make_request('GET', '/api/session')
def get_mma_status(self):
"""Retrieves current MMA status (track, tickets, tier, etc.)"""
return self._make_request('GET', '/api/gui/mma_status')
def push_event(self, event_type, payload):
"""Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint."""
return self.post_gui({
"action": event_type,
"payload": payload
})
def get_performance(self):
"""Retrieves UI performance metrics."""
return self._make_request('GET', '/api/performance')
+29
View File
@@ -111,6 +111,35 @@ class HookHandler(BaseHTTPRequestHandler):
"callback": get_val
})
if event.wait(timeout=2):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(result).encode('utf-8'))
else:
self.send_response(504)
self.end_headers()
elif self.path == '/api/gui/mma_status':
event = threading.Event()
result = {}
def get_mma():
try:
result["mma_status"] = getattr(app, "mma_status", "idle")
result["active_tier"] = getattr(app, "active_tier", None)
result["active_track"] = getattr(app, "active_track", None)
result["active_tickets"] = getattr(app, "active_tickets", [])
result["mma_step_mode"] = getattr(app, "mma_step_mode", False)
result["pending_approval"] = app._pending_mma_approval is not None
finally:
event.set()
with app._pending_gui_tasks_lock:
app._pending_gui_tasks.append({
"action": "custom_callback",
"callback": get_mma
})
if event.wait(timeout=2):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
@@ -70,3 +70,16 @@
- [x] Task: Tiered Metrics & Log Links
- [x] Add visual indicators for the active model Tier.
- [x] Provide clickable links to sub-agent logs.
## Phase 8: Visual Verification & Interaction Tests
- [x] Task: Visual Verification Script
- [x] Create `tests/visual_mma_verification.py` to drive the GUI into various MMA states.
- [x] Verify MMA Dashboard visibility and progress bar.
- [x] Verify Ticket Queue rendering with correct status colors.
- [x] Task: HITL Interaction Verification
- [x] Drive a simulated HITL pause through the verification script.
- [x] Manually verify the "MMA Step Approval" modal appearance.
- [x] Manually verify "Edit Payload" (Memory Mutator) functionality.
- [~] Task: Final Polish & Fixes
- [ ] Fix any visual glitches or layout issues discovered during manual testing.
- [ ] Fix any visual glitches or layout issues discovered during manual testing.
+3 -4
View File
@@ -20,8 +20,8 @@ This file tracks all major tracks for the project. Each track has its own detail
---
- [ ] **Track: Update ./docs/* & ./Readme.md, review ./MainContext.md significance (should we keep it..).**
*Link: [./tracks/documentation_refresh_20260224/](./tracks/documentation_refresh_20260224/)*
- [~] **Track: MMA Orchestrator Integration**
*Link: [./tracks/mma_orchestrator_integration_20260226/](./tracks/mma_orchestrator_integration_20260226/)*
---
@@ -30,8 +30,7 @@ This file tracks all major tracks for the project. Each track has its own detail
---
- [x] **Track: MMA Core Engine Implementation**
*Link: [./tracks/mma_core_engine_20260224/](./tracks/mma_core_engine_20260224/)*
- [~] **Track: MMA Core Engine Implementation**
---
@@ -0,0 +1,8 @@
# MMA Orchestrator Integration
This track implements the full hierarchical orchestration loop, connecting Tier 1 (PM) strategic planning with Tier 2 (Tech Lead) tactical ticket generation.
### Navigation
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Previous Track: MMA Core Engine Implementation (Archived)](../../archive/mma_core_engine_20260224/index.md)
@@ -0,0 +1,6 @@
{
"id": "mma_orchestrator_integration_20260226",
"title": "MMA Orchestrator Integration",
"status": "in-progress",
"created_at": "2026-02-26T22:04:00.000000"
}
@@ -0,0 +1,42 @@
# Implementation Plan: MMA Orchestrator Integration
## Phase 1: Tier 1 Strategic PM Implementation
- [ ] Task: PM Planning Hook
- [ ] Create `orchestrator_pm.py` to handle the Tier 1 Strategic prompt.
- [ ] Implement the `generate_tracks(user_request, repo_map)` function.
- [ ] Task: Project History Aggregation
- [ ] Summarize past track results to provide context for new epics.
## Phase 2: Tier 2 Tactical Dispatcher Implementation
- [ ] Task: Tech Lead Dispatcher Hook
- [ ] Create `conductor_tech_lead.py` to handle the Tier 2 Dispatcher prompt.
- [ ] Implement the `generate_tickets(track_brief, module_skeletons)` function.
- [ ] Task: DAG Construction
- [ ] Build the topological dependency graph from the Tech Lead's ticket list.
## Phase 3: Guided Planning UX & Interaction
- [ ] Task: Strategic Planning View
- [ ] Implement a "Track Proposal" modal in `gui_2.py` for reviewing Tier 1's plans.
- [ ] Allow manual editing of track goals and acceptance criteria (Manual Curation).
- [ ] Task: Tactical Dispatcher View
- [ ] Implement a "Ticket DAG" visualization or interactive list in the MMA Dashboard.
- [ ] Allow manual "Skip", "Retry", or "Re-assign" actions on individual tickets.
- [ ] Task: The Orchestrator Main Loop
- [ ] Implement the async state machine in `gui_2.py` that moves from Planning -> Dispatching -> Execution.
- [ ] Task: Project Metadata Serialization
- [ ] Persist the active epic, tracks, and tickets to `manual_slop.toml`.
## Phase 4: Product Alignment & Refinement
- [ ] Task: UX Differentiator Audit
- [ ] Ensure the UX prioritizes "Expert Oversight" over "Full Autonomy" (Manual Slop vs. Gemini CLI).
- [ ] Add detailed token metrics and Tier-specific latency indicators to the Dashboard.
## Phase 5: Exhaustive Testing & Regression
- [ ] Task: Headless Engine Verification
- [ ] Create `tests/test_orchestration_logic.py` to verify Tier 1 -> Tier 2 -> Tier 3 flow without a GUI.
- [ ] Verify DAG resolution and error handling (e.g., blocked tickets).
- [ ] Task: Visual Verification Suite
- [ ] Create `tests/visual_orchestration_verification.py` using `ApiHookClient`.
- [ ] Simulate a full "Epic" lifecycle: User Prompt -> Track Review -> Ticket Generation -> Execution.
- [ ] Task: Core Regression Suite
- [ ] Run all existing MMA, Conductor, and GUI tests to ensure no regressions.
@@ -0,0 +1,21 @@
# Track Specification: MMA Orchestrator Integration
## Overview
Implement the full hierarchical orchestration loop, connecting Tier 1 (PM) strategic planning with Tier 2 (Tech Lead) tactical ticket generation. This track will enable the GUI to autonomously break down high-level user 'Epics' into actionable tracks and tickets, and manage their execution through the multi-agent system.
## Goals
1. **Tier 1 Epic Planning:** Implement the logic for the Orchestrator to analyze project state and user requests to generate Implementation Tracks.
2. **Tier 2 Ticket Dispatcher:** Enable the Tech Lead to take a Track Brief and generate a Directed Acyclic Graph (DAG) of worker tickets.
3. **GUI Orchestrator Loop:** Create the 'main thinking loop' in gui_2.py that manages the transition between planning and execution.
4. **Project History Integration:** Ensure that completed tracks and tickets are properly summarized and stored in the project's persistent memory.
## Technical Scope
- **Prompt Engineering:** Finalize and integrate the Tier 1 and Tier 2 system prompts from mma_prompts.py.
- **Backend Plumbing:** Extend multi_agent_conductor.py to handle track generation and ticket dispatching.
- **UI Interaction:** Connect the 'New Project/Epic' button to the Tier 1 generator and the 'Start Track' button to the Tier 2 dispatcher.
- **State Management:** Implement the transition logic between 'Strategic Planning', 'Tactical Dispatching', and 'Worker Execution' states.
## Constraints
- **Hierarchy Enforcement:** All planning must flow from Tier 1 to Tier 2, and all execution from Tier 2 to Tiers 3/4.
- **Token Firewalling:** Use scripts/mma_exec.py for all tiered sub-agent calls.
- **HITL Verification:** Maintain Step Mode capabilities for each transition (Planning -> Dispatching -> Execution).
+1
View File
@@ -28,6 +28,7 @@ active = "C:\\projects\\manual_slop\\tests\\temp_project.toml"
"Context Hub" = true
"Files & Media" = true
"AI Settings" = true
"MMA Dashboard" = true
"Discussion Hub" = true
"Operations Hub" = true
Theme = true
+9 -3
View File
@@ -962,13 +962,16 @@ class App:
def _handle_approve_ask(self):
"""Responds with approval for a pending /api/ask request."""
if not self._ask_request_id: return
request_id = self._ask_request_id
def do_post():
try:
requests.post(
"http://127.0.0.1:8999/api/ask/respond",
json={"request_id": self._ask_request_id, "response": {"approved": True}},
json={"request_id": request_id, "response": {"approved": True}},
timeout=2
)
except Exception as e: print(f"Error responding to ask: {e}")
threading.Thread(target=do_post, daemon=True).start()
self._pending_ask_dialog = False
self._ask_request_id = None
self._ask_tool_data = None
@@ -976,13 +979,16 @@ class App:
def _handle_reject_ask(self):
"""Responds with rejection for a pending /api/ask request."""
if not self._ask_request_id: return
request_id = self._ask_request_id
def do_post():
try:
requests.post(
"http://127.0.0.1:8999/api/ask/respond",
json={"request_id": self._ask_request_id, "response": {"approved": False}},
json={"request_id": request_id, "response": {"approved": False}},
timeout=2
)
except Exception as e: print(f"Error responding to ask: {e}")
threading.Thread(target=do_post, daemon=True).start()
self._pending_ask_dialog = False
self._ask_request_id = None
self._ask_tool_data = None
@@ -1696,7 +1702,7 @@ class App:
else:
imgui.text("Proposed Tool Call:")
imgui.begin_child("mma_preview", imgui.ImVec2(600, 300), True)
imgui.text_unformatted(self._pending_mma_approval.get("payload", ""))
imgui.text_unformatted(str(self._pending_mma_approval.get("payload", "")))
imgui.end_child()
imgui.separator()
View File
+65
View File
@@ -0,0 +1,65 @@
import json
import ai_client
import mma_prompts
import aggregate
import summarize
from pathlib import Path
def generate_tracks(user_request: str, project_config: dict, file_items: list[dict]) -> list[dict]:
"""
Tier 1 (Strategic PM) call.
Analyzes the project state and user request to generate a list of Tracks.
"""
# 1. Build Repository Map (Summary View)
repo_map = summarize.build_summary_markdown(file_items)
# 2. Construct Prompt
system_prompt = mma_prompts.PROMPTS.get("tier1_epic_init")
user_message = (
f"### USER REQUEST:
{user_request}
"
f"### REPOSITORY MAP:
{repo_map}
"
"Please generate the implementation tracks for this request."
)
# 3. Call Tier 1 Model (Strategic - Pro)
# Note: We use gemini-1.5-pro or similar high-reasoning model for Tier 1
response = ai_client.send(
md_content="", # We pass everything in user_message for clarity
user_message=user_message,
system_prompt=system_prompt,
model_name="gemini-1.5-pro" # Strategic Tier
)
# 4. Parse JSON Output
try:
# The prompt asks for a JSON array. We need to extract it if the AI added markdown blocks.
json_match = response.strip()
if "```json" in json_match:
json_match = json_match.split("```json")[1].split("```")[0].strip()
elif "```" in json_match:
json_match = json_match.split("```")[1].split("```")[0].strip()
tracks = json.loads(json_match)
return tracks
except Exception as e:
print(f"Error parsing Tier 1 response: {e}")
print(f"Raw response: {response}")
return []
if __name__ == "__main__":
# Quick CLI test
import project_manager
proj = project_manager.load_project("manual_slop.toml")
flat = project_manager.flat_config(proj)
file_items = aggregate.build_file_items(Path("."), flat.get("files", {}).get("paths", []))
print("Testing Tier 1 Track Generation...")
tracks = generate_tracks("Implement a basic unit test for the ai_client.py module.", flat, file_items)
print(json.dumps(tracks, indent=2))
+1 -1
View File
@@ -70,7 +70,7 @@ def get_model_for_role(role: str) -> str:
elif role == 'tier2-tech-lead' or role == 'tier2':
return 'gemini-3-flash-preview'
elif role == 'tier3-worker' or role == 'tier3':
return 'gemini-2.5-flash-lite'
return 'gemini-3-flash-preview'
elif role == 'tier4-qa' or role == 'tier4':
return 'gemini-2.5-flash-lite'
else:
+9 -2
View File
@@ -5,10 +5,17 @@ roles = [
"System",
"Reasoning",
]
active = "main"
active = "mma_human veriffication"
auto_add = true
[discussions.main]
git_commit = ""
last_updated = "2026-02-26T21:34:07"
last_updated = "2026-02-26T22:00:42"
history = [
"@2026-02-26T22:00:02\nSystem:\n[PERFORMANCE ALERT] CPU usage high: 92.2%. Please consider optimizing recent changes or reducing load.",
]
[discussions."mma_human veriffication"]
git_commit = ""
last_updated = "2026-02-26T22:06:01"
history = []
+96
View File
@@ -0,0 +1,96 @@
import subprocess
import time
import sys
import os
import unittest
# Calculate project root
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from api_hook_client import ApiHookClient
class TestMMAGUIRobust(unittest.TestCase):
@classmethod
def setUpClass(cls):
# 1. Launch gui_2.py with --enable-test-hooks
cls.gui_command = [sys.executable, "gui_2.py", "--enable-test-hooks"]
print(f"Launching GUI: {' '.join(cls.gui_command)}")
cls.gui_process = subprocess.Popen(
cls.gui_command,
cwd=PROJECT_ROOT,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
cls.client = ApiHookClient()
print("Waiting for GUI to start...")
if not cls.client.wait_for_server(timeout=10):
cls.gui_process.terminate()
raise RuntimeError("GUI failed to start or hook server not responsive.")
print("GUI started.")
@classmethod
def tearDownClass(cls):
if cls.gui_process:
cls.gui_process.terminate()
cls.gui_process.wait(timeout=5)
def test_mma_state_ingestion(self):
"""Verify that mma_state_update event correctly updates GUI state."""
track_data = {
"id": "robust_test_track",
"title": "Robust Verification Track",
"description": "Verifying internal state ingestion"
}
tickets_data = [
{"id": "T1", "target_file": "file1.py", "status": "todo"},
{"id": "T2", "target_file": "file2.py", "status": "running"},
{"id": "T3", "target_file": "file3.py", "status": "complete"},
]
payload = {
"status": "active",
"active_tier": "Tier 2",
"track": track_data,
"tickets": tickets_data
}
print("Pushing mma_state_update...")
self.client.push_event("mma_state_update", payload)
# Give GUI a moment to process the async task
time.sleep(1.0)
print("Querying mma_status...")
status = self.client.get_mma_status()
self.assertEqual(status["mma_status"], "active")
self.assertEqual(status["active_tier"], "Tier 2")
self.assertEqual(status["active_track"]["id"], "robust_test_track")
self.assertEqual(len(status["active_tickets"]), 3)
self.assertEqual(status["active_tickets"][2]["status"], "complete")
print("MMA state ingestion verified successfully.")
def test_mma_step_approval_trigger(self):
"""Verify that mma_step_approval event sets the pending approval flag."""
payload = {
"ticket_id": "T2",
"payload": "echo 'Robust Test'"
}
print("Pushing mma_step_approval...")
self.client.push_event("mma_step_approval", payload)
time.sleep(1.0)
print("Querying mma_status for pending approval...")
status = self.client.get_mma_status()
self.assertTrue(status["pending_approval"], "GUI did not register pending MMA approval")
print("MMA step approval trigger verified successfully.")
if __name__ == "__main__":
unittest.main()
+69
View File
@@ -0,0 +1,69 @@
import subprocess
import time
import sys
import os
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from api_hook_client import ApiHookClient
def diag_run():
print("Launching GUI for manual inspection + automated hooks...")
# Use a log file for GUI output
with open("gui_diag.log", "w") as log_file:
gui_process = subprocess.Popen(
[sys.executable, "gui_2.py", "--enable-test-hooks"],
cwd=PROJECT_ROOT,
stdout=log_file,
stderr=log_file,
text=True
)
client = ApiHookClient()
print("Waiting for GUI...")
if not client.wait_for_server(timeout=10):
print("GUI failed to start.")
gui_process.terminate()
return
# Pushing state
track_data = {"id": "diag_track", "title": "Diagnostic Track"}
tickets_data = [{"id": f"T{i}", "status": "todo"} for i in range(3)]
print("Pushing state update...")
client.push_event("mma_state_update", {
"status": "active",
"active_tier": "Tier 1",
"track": track_data,
"tickets": tickets_data
})
time.sleep(2)
print("Pushing approval request...")
client.push_event("mma_step_approval", {
"ticket_id": "T0",
"payload": "Get-ChildItem"
})
print("\nGUI is running. Check 'gui_diag.log' for output.")
print("I will now poll mma_status every 2 seconds. Ctrl+C to stop.")
try:
start_poll = time.time()
while time.time() - start_poll < 30:
try:
status = client.get_mma_status()
print(f"[{time.strftime('%H:%M:%S')}] Status: {status.get('mma_status')}, Pending Approval: {status.get('pending_approval')}")
except Exception as e:
print(f"[{time.strftime('%H:%M:%S')}] Error querying status: {e}")
time.sleep(2)
except KeyboardInterrupt:
print("Stopping...")
finally:
gui_process.terminate()
if __name__ == "__main__":
diag_run()
+184
View File
@@ -0,0 +1,184 @@
import subprocess
import time
import sys
import os
import glob # Will be used to find api_hook_client.py path if needed, though sys.path modification is better.
# --- Configuration ---
GUI_SCRIPT = 'gui_2.py'
TEST_HOOKS_FLAG = '--enable-test-hooks'
API_HOOK_CLIENT_MODULE = 'api_hook_client'
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) # Calculate project root
# Ensure project root is in sys.path to import modules like api_hook_client
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
print(f"Added '{PROJECT_ROOT}' to sys.path for imports.")
try:
from api_hook_client import ApiHookClient
except ImportError as e:
print(f"Error: Could not import ApiHookClient from '{API_HOOK_CLIENT_MODULE}'.")
print(f"Please ensure '{API_HOOK_CLIENT_MODULE}.py' is accessible and '{PROJECT_ROOT}' is correctly added to sys.path.")
print(f"Import error: {e}")
sys.exit(1)
def run_visual_mma_verification():
print("Starting visual MMA verification test...")
# Change current directory to project root to ensure relative paths are correct for gui_2.py
original_dir = os.getcwd()
if original_dir != PROJECT_ROOT:
try:
os.chdir(PROJECT_ROOT)
print(f"Changed current directory to: {PROJECT_ROOT}")
except FileNotFoundError:
print(f"Error: Project root directory '{PROJECT_ROOT}' not found.")
return
# 1. Launch gui_2.py with --enable-test-hooks
gui_command = [sys.executable, GUI_SCRIPT, TEST_HOOKS_FLAG]
print(f"Launching GUI with command: {' '.join(gui_command)}")
try:
gui_process = subprocess.Popen(
gui_command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
cwd=PROJECT_ROOT # Explicitly set working directory
)
print(f"GUI process started with PID: {gui_process.pid}")
except FileNotFoundError:
print(f"Error: Could not find Python executable '{sys.executable}' or script '{GUI_SCRIPT}'.")
return
except Exception as e:
print(f"Error starting GUI process: {e}")
return
# Give the GUI a moment to start and open the hook server.
print("Waiting for GUI to initialize and hook server to start (5 seconds)...")
time.sleep(5)
# Check if the GUI process exited prematurely
if gui_process.poll() is not None:
print(f"Error: GUI process exited prematurely with return code {gui_process.returncode}.")
stderr_output = gui_process.stderr.read()
if stderr_output:
print("--- GUI Stderr ---")
print(stderr_output)
print("------------------")
return
# 2. Use api_hook_client.ApiHookClient to push events
try:
client = ApiHookClient()
print("ApiHookClient initialized successfully.")
except Exception as e:
print(f"Failed to initialize ApiHookClient. Ensure the hook server is running on port 8999. Error: {e}")
if gui_process:
gui_process.terminate()
gui_process.wait()
return
# 3. Include at least 5 tickets in different states
track_data = {
"id": "visual_test_track",
"title": "Visual Verification Track",
"description": "A track to verify MMA UI components"
}
tickets_data = [
{"id": "TICKET-001", "target_file": "core.py", "status": "todo"},
{"id": "TICKET-002", "target_file": "utils.py", "status": "running"},
{"id": "TICKET-003", "target_file": "tests.py", "status": "complete"},
{"id": "TICKET-004", "target_file": "api.py", "status": "blocked"},
{"id": "TICKET-005", "target_file": "gui.py", "status": "paused"},
]
print("\nPushing MMA state update...")
try:
payload = {
"status": "running",
"active_tier": "Tier 3",
"track": track_data,
"tickets": tickets_data
}
client.push_event("mma_state_update", payload)
print(" - MMA state update pushed.")
except Exception as e:
print(f" - Warning: Failed to push mma_state_update: {e}")
# 4. After a short delay, push an 'mma_step_approval' event
print("\nWaiting for GUI to render ticket queue and progress bar (3 seconds)...")
time.sleep(3)
print("Pushing 'mma_step_approval' event to trigger HITL modal...")
try:
approval_payload = {
"ticket_id": "TICKET-002",
"payload": "powershell -Command \"Write-Host 'Hello from Tier 3'\""
}
client.push_event("mma_step_approval", approval_payload)
print("mma_step_approval event pushed successfully.")
except Exception as e:
print(f"Error pushing mma_step_approval event: {e}")
# 5. Provide clear print statements for manual verification
print("
--- Manual Verification Instructions ---")
print("Please visually inspect the running GUI application:")
print("1. MMA Dashboard: Ensure the 'MMA Dashboard' panel is visible and active.")
print("2. Ticket Queue: Verify the 'Ticket Queue' section displays all 5 tickets with correct statuses:")
print(" - TICKET-001: Should be 'todo'")
print(" - TICKET-002: Should be 'running'")
print(" - TICKET-003: Should be 'complete'")
print(" - TICKET-004: Should be 'blocked'")
print(" - TICKET-005: Should be 'paused'")
print(" Observe the distinct status colors for each ticket.")
print("3. Progress Bar: Check that the progress bar correctly reflects the completed/total tickets (e.g., 1/5).")
print("4. Approval Modal: Confirm that an 'MMA Step Approval' modal has appeared.")
print(" - Verify it contains a 'Proposed Tool Call' section (e.g., showing 'powershell -Command...').")
print(" - Ensure there is an option/button to 'Edit Payload'.")
print("
--------------------------------------")
print("The test script has finished its automated actions.")
print("The GUI application is still running. Please perform manual verification.")
print("Press Enter in this terminal to stop the GUI process and exit the test script.")
try:
input()
except EOFError:
print("EOF received, exiting.")
pass
print("
Stopping GUI process...")
if gui_process:
try:
gui_process.terminate()
gui_process.wait(timeout=10)
print("GUI process terminated gracefully.")
except subprocess.TimeoutExpired:
print("GUI process did not terminate within the timeout. Killing it forcefully.")
gui_process.kill()
gui_process.wait()
except Exception as e:
print(f"Error during GUI process termination: {e}")
else:
print("GUI process was not started or already terminated.")
print("Visual MMA verification test script finished.")
# Restore original directory
if original_dir != PROJECT_ROOT:
try:
os.chdir(original_dir)
print(f"Restored original working directory: {original_dir}")
except FileNotFoundError:
print(f"Warning: Could not restore original working directory '{original_dir}'.")
# --- Main execution ---
if __name__ == "__main__":
# When the script is executed directly, ensure it runs from the correct context.
run_visual_mma_verification()