From b30e563fc14336804ca9d980b944d0829369cf4d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 2 Mar 2026 16:26:41 -0500 Subject: [PATCH] =?UTF-8?q?feat(mma):=20Phase=203=20=E2=80=94=20Focus=20Ag?= =?UTF-8?q?ent=20UI=20+=20filter=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gui_2.__init__: add ui_focus_agent: str | None = None - _gui_func: Focus Agent combo (All/Tier2/3/4) + clear button above OperationsTabs - _render_comms_history_panel: filter by ui_focus_agent; show [source_tier] label per entry - _render_tool_calls_panel: pre-filter with tool_log_filtered; fix missing i=i_minus_one+1; remove stale tuple destructure - tests: 6 new Phase 3 tests, 18/18 total --- gui_2.py | 32 +++++++- tests/test_mma_agent_focus_phase3.py | 109 +++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 tests/test_mma_agent_focus_phase3.py diff --git a/gui_2.py b/gui_2.py index 8e2fbcc..1815888 100644 --- a/gui_2.py +++ b/gui_2.py @@ -260,6 +260,7 @@ class App: self.active_track: Track | None = None self.active_tickets: list[dict[str, Any]] = [] self.active_tier: str | None = None + self.ui_focus_agent: str | None = None self.mma_status = "idle" self._pending_mma_approval: dict[str, Any] | None = None self._mma_approval_open = False @@ -1746,6 +1747,21 @@ class App: if self.show_windows.get("Operations Hub", False): exp, self.show_windows["Operations Hub"] = imgui.begin("Operations Hub", self.show_windows["Operations Hub"]) if exp: + imgui.text("Focus Agent:") + imgui.same_line() + focus_label = self.ui_focus_agent or "All" + if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview): + if imgui.selectable("All", self.ui_focus_agent is None)[0]: + self.ui_focus_agent = None + for tier in ["Tier 2", "Tier 3", "Tier 4"]: + if imgui.selectable(tier, self.ui_focus_agent == tier)[0]: + self.ui_focus_agent = tier + imgui.end_combo() + imgui.same_line() + if self.ui_focus_agent: + if imgui.button("x##clear_focus"): + self.ui_focus_agent = None + imgui.separator() if imgui.begin_tab_bar("OperationsTabs"): if imgui.begin_tab_item("Tool Calls")[0]: self._render_tool_calls_panel() @@ -2968,14 +2984,17 @@ class App: imgui.separator() imgui.begin_child("scroll_area") clipper = imgui.ListClipper() - clipper.begin(len(self._tool_log)) + tool_log_filtered = self._tool_log if not self.ui_focus_agent else [ + e for e in self._tool_log if e.get("source_tier") == self.ui_focus_agent + ] + clipper = imgui.ListClipper() + clipper.begin(len(tool_log_filtered)) while clipper.step(): for i_minus_one in range(clipper.display_start, clipper.display_end): - entry = self._tool_log[i_minus_one] + i = i_minus_one + 1 + entry = tool_log_filtered[i_minus_one] script = entry["script"] result = entry["result"] - script, result, _ = self._tool_log[i_minus_one] - first_line = script.strip().splitlines()[0][:80] if script.strip() else "(empty)" imgui.text_colored(C_KEY, f"Call #{i}: {first_line}") # Script Display imgui.text_colored(C_LBL, "Script:") @@ -3411,6 +3430,8 @@ class App: imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 30, 20)) imgui.begin_child("comms_scroll", imgui.ImVec2(0, 0), False, imgui.WindowFlags_.horizontal_scrollbar) log_to_render = self.prior_session_entries if self.is_viewing_prior_session else list(self._comms_log) + if self.ui_focus_agent and not self.is_viewing_prior_session: + log_to_render = [e for e in log_to_render if e.get("source_tier") == self.ui_focus_agent] for idx_minus_one, entry in enumerate(log_to_render): idx = idx_minus_one + 1 local_ts = entry.get("local_ts", 0) @@ -3442,6 +3463,9 @@ class App: imgui.text_colored(KIND_COLORS.get(k, C_VAL), k) imgui.same_line() imgui.text_colored(C_LBL, f"{entry.get('provider', '?')}/{entry.get('model', '?')}") + imgui.same_line() + tier_label = entry.get("source_tier") or "main" + imgui.text_colored(C_SUB, f"[{tier_label}]") payload = entry.get("payload", {}) if k == "request": self._render_heavy_text("message", payload.get("message", "")) diff --git a/tests/test_mma_agent_focus_phase3.py b/tests/test_mma_agent_focus_phase3.py new file mode 100644 index 0000000..40c54a3 --- /dev/null +++ b/tests/test_mma_agent_focus_phase3.py @@ -0,0 +1,109 @@ +""" +Tests for mma_agent_focus_ux_20260302 — Phase 3: Focus Agent UI + Filter Logic. +""" +from typing import Generator +import pytest +from unittest.mock import patch +from gui_2 import App + + +@pytest.fixture +def app_instance() -> Generator[App, None, None]: + with ( + patch('gui_2.load_config', return_value={'gui': {'show_windows': {}}}), + patch('gui_2.save_config'), + patch('gui_2.project_manager'), + patch('gui_2.session_logger'), + patch('gui_2.immapp.run'), + patch.object(App, '_load_active_project'), + patch.object(App, '_fetch_models'), + patch.object(App, '_load_fonts'), + patch.object(App, '_post_init') + ): + yield App() + + +def test_ui_focus_agent_state_var_exists(app_instance): + """App.__init__ must expose ui_focus_agent: str | None = None.""" + assert hasattr(app_instance, 'ui_focus_agent'), "ui_focus_agent missing from App" + assert app_instance.ui_focus_agent is None + + +def test_tool_log_filter_all(app_instance): + """When ui_focus_agent is None, all tool log entries are visible.""" + app = app_instance + app._tool_log = [ + {"script": "a", "result": "r1", "ts": 1.0, "source_tier": "Tier 2"}, + {"script": "b", "result": "r2", "ts": 2.0, "source_tier": "Tier 3"}, + {"script": "c", "result": "r3", "ts": 3.0, "source_tier": None}, + ] + app.ui_focus_agent = None + filtered = app._tool_log if not app.ui_focus_agent else [ + e for e in app._tool_log if e.get("source_tier") == app.ui_focus_agent + ] + assert len(filtered) == 3 + + +def test_tool_log_filter_tier3_only(app_instance): + """When ui_focus_agent='Tier 3', only Tier 3 entries are shown.""" + app = app_instance + app._tool_log = [ + {"script": "a", "result": "r1", "ts": 1.0, "source_tier": "Tier 2"}, + {"script": "b", "result": "r2", "ts": 2.0, "source_tier": "Tier 3"}, + {"script": "c", "result": "r3", "ts": 3.0, "source_tier": "Tier 3"}, + {"script": "d", "result": "r4", "ts": 4.0, "source_tier": None}, + ] + app.ui_focus_agent = "Tier 3" + filtered = [e for e in app._tool_log if e.get("source_tier") == app.ui_focus_agent] + assert len(filtered) == 2 + assert all(e["source_tier"] == "Tier 3" for e in filtered) + + +def test_tool_log_filter_excludes_none_tier(app_instance): + """Filtering to Tier 2 excludes entries with source_tier=None.""" + app = app_instance + app._tool_log = [ + {"script": "a", "result": "r1", "ts": 1.0, "source_tier": "Tier 2"}, + {"script": "b", "result": "r2", "ts": 2.0, "source_tier": None}, + ] + app.ui_focus_agent = "Tier 2" + filtered = [e for e in app._tool_log if e.get("source_tier") == app.ui_focus_agent] + assert len(filtered) == 1 + assert filtered[0]["script"] == "a" + + +def test_comms_log_filter_tier3_only(app_instance): + """When ui_focus_agent='Tier 3', comms filter excludes other tiers.""" + app = app_instance + import time + app._comms_log = [ + {"ts": "10:00", "direction": "OUT", "kind": "request", "provider": "gemini", + "model": "m", "payload": {}, "source_tier": "Tier 2", "local_ts": time.time()}, + {"ts": "10:01", "direction": "IN", "kind": "response", "provider": "gemini", + "model": "m", "payload": {}, "source_tier": "Tier 3", "local_ts": time.time()}, + {"ts": "10:02", "direction": "OUT", "kind": "request", "provider": "gemini", + "model": "m", "payload": {}, "source_tier": None, "local_ts": time.time()}, + ] + app.ui_focus_agent = "Tier 3" + app.is_viewing_prior_session = False + log_to_render = list(app._comms_log) + if app.ui_focus_agent and not app.is_viewing_prior_session: + log_to_render = [e for e in log_to_render if e.get("source_tier") == app.ui_focus_agent] + assert len(log_to_render) == 1 + assert log_to_render[0]["source_tier"] == "Tier 3" + + +def test_comms_log_filter_not_applied_for_prior_session(app_instance): + """Focus filter must NOT apply when viewing prior session log.""" + app = app_instance + app.ui_focus_agent = "Tier 3" + app.is_viewing_prior_session = True + prior = [ + {"ts": "10:00", "source_tier": "Tier 2"}, + {"ts": "10:01", "source_tier": "Tier 3"}, + ] + app.prior_session_entries = prior + log_to_render = app.prior_session_entries if app.is_viewing_prior_session else list(app._comms_log) + if app.ui_focus_agent and not app.is_viewing_prior_session: + log_to_render = [e for e in log_to_render if e.get("source_tier") == app.ui_focus_agent] + assert len(log_to_render) == 2