From 2db6b8c85b12aeefbb3d52450fd159b6ab074dbe Mon Sep 17 00:00:00 2001 From: kyssta-exe <25470058+kyssta-exe@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:10:50 +0000 Subject: [PATCH] fix(tui_gateway): scope secrets and MCP discovery to the active profile (#67605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard/desktop profile switch was partial — switching to profile X ran the launch profile's resources in two ways: 1. MCP discovery was gated on the launch profile's config having mcp_servers. If the launch profile had none, the background thread never started and zero MCP servers existed for every profile. Fix: always start discovery and let discover_mcp_tools() handle the empty-config case. 2. The profile secret scope (.env credentials) was never installed on the tui_gateway path. get_secret() fell through to os.environ, resolving secrets from the launch profile instead of the selected one. Fix: install set_secret_scope(build_profile_secret_scope(...)) alongside every set_hermes_home_override() call site: - compute_host.py:_ensure_server_session (build-time) - server.py:_build (lazy resume) - server.py:_handle_resume_session (_make_agent scope) - server.py:_handle_resume_session (_init_session scope) - server.py:_handle_submit_or_edit (per-turn handler) --- tui_gateway/compute_host.py | 5 +++++ tui_gateway/server.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/tui_gateway/compute_host.py b/tui_gateway/compute_host.py index 102d8e6d47b3..d1c46dd9c20e 100644 --- a/tui_gateway/compute_host.py +++ b/tui_gateway/compute_host.py @@ -436,12 +436,15 @@ class ComputeHost: profile_home = str(frame.get("profile_home") or "") session_db = None home_token = None + secret_token = None try: if profile_home: from hermes_constants import set_hermes_home_override + from agent.secret_scope import build_profile_secret_scope, set_secret_scope from hermes_state import SessionDB home_token = set_hermes_home_override(profile_home) + secret_token = set_secret_scope(build_profile_secret_scope(Path(profile_home))) session_db = SessionDB(db_path=Path(profile_home) / "state.db") agent = server._make_agent( sid, @@ -457,8 +460,10 @@ class ComputeHost: if home_token is not None: try: from hermes_constants import reset_hermes_home_override + from agent.secret_scope import reset_secret_scope reset_hermes_home_override(home_token) + reset_secret_scope(secret_token) except Exception: pass try: diff --git a/tui_gateway/server.py b/tui_gateway/server.py index bd39477aa6b9..f90350d2ec82 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -18,6 +18,11 @@ from datetime import datetime from pathlib import Path from typing import Any, NamedTuple, Optional +from agent.secret_scope import ( + build_profile_secret_scope, + reset_secret_scope, + set_secret_scope, +) from hermes_constants import ( get_hermes_home, get_hermes_home_override, @@ -1834,6 +1839,7 @@ def _start_agent_build(sid: str, session: dict) -> None: worker = None notify_registered = False home_token = None + secret_token = None profile_home = current.get("profile_home") try: tokens = _set_session_context(key) @@ -1843,6 +1849,12 @@ def _start_agent_build(sid: str, session: dict) -> None: session_db = None if profile_home: home_token = set_hermes_home_override(profile_home) + try: + from agent.secret_scope import build_profile_secret_scope, set_secret_scope + + secret_token = set_secret_scope(build_profile_secret_scope(Path(profile_home))) + except Exception: + pass try: from hermes_state import SessionDB @@ -1963,6 +1975,13 @@ def _start_agent_build(sid: str, session: dict) -> None: finally: if home_token is not None: reset_hermes_home_override(home_token) + if secret_token is not None: + try: + from agent.secret_scope import reset_secret_scope + + reset_secret_scope(secret_token) + except Exception: + pass # _attach_worker already closed the worker if this session was # reaped mid-build; only the late notify registration can still # leak (session.close unregistered before _build registered it). @@ -7474,6 +7493,11 @@ def _(rid, params: dict) -> dict: home_token = ( set_hermes_home_override(str(profile_home)) if profile_home is not None else None ) + secret_token = ( + set_secret_scope(build_profile_secret_scope(Path(str(profile_home)))) + if profile_home is not None + else None + ) try: db.reopen_session(target) # One lineage SELECT feeds both projections (see the interactive resume @@ -7515,6 +7539,8 @@ def _(rid, params: dict) -> dict: finally: if home_token is not None: reset_hermes_home_override(home_token) + if secret_token is not None: + reset_secret_scope(secret_token) # Double-checked locking: another concurrent resume may have created the # live session while we were building. Re-check under the lock; if it won, @@ -7545,6 +7571,11 @@ def _(rid, params: dict) -> dict: if profile_home is not None else None ) + init_secret_token = ( + set_secret_scope(build_profile_secret_scope(Path(str(profile_home)))) + if profile_home is not None + else None + ) try: _init_session( sid, @@ -7559,6 +7590,8 @@ def _(rid, params: dict) -> dict: finally: if init_home_token is not None: reset_hermes_home_override(init_home_token) + if init_secret_token is not None: + reset_secret_scope(init_secret_token) if sid in _sessions: if stored_runtime_overrides.get("model_override") is not None: _sessions[sid]["model_override"] = stored_runtime_overrides[ @@ -11609,6 +11642,7 @@ def _run_prompt_submit( approval_token = None session_tokens = [] home_token = None # per-turn HERMES_HOME override for a resumed remote profile + secret_token = None goal_followup = None # set by the post-turn goal hook below tts_queue = None # streaming-TTS feed for this turn (voice mode) one_turn_restore = session.pop("one_turn_model_restore", None) @@ -11641,6 +11675,7 @@ def _run_prompt_submit( _profile_home_str = session.get("profile_home") if _profile_home_str: home_token = set_hermes_home_override(_profile_home_str) + secret_token = set_secret_scope(build_profile_secret_scope(Path(_profile_home_str))) # The sudo password callback is thread-local (tools.terminal_tool # _callback_tls), so wiring it on the build thread doesn't reach this # turn thread — terminal sudo prompts would fall through to /dev/tty @@ -12164,6 +12199,8 @@ def _run_prompt_submit( pass if home_token is not None: reset_hermes_home_override(home_token) + if secret_token is not None: + reset_secret_scope(secret_token) _clear_session_context(session_tokens) # Clear the per-turn interim callback so a stale closure from # this turn can't fire during a later turn on the same agent.