mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
* fix(gateway): scope reset banners' session info to the serving profile The auto-reset notice and the manual /reset //new banner both appended _format_session_info() outside any profile scope, so a multiplexed gateway advertised the base config's model/provider/context while the session actually ran on the profile's. Route both call sites through a new _reset_notice_session_info(source), which enters _profile_runtime_scope for the source's profile when gateway.multiplex_profiles is on (mirroring _run_agent's gating), so _load_gateway_config()/_resolve_gateway_model() resolve the profile's config.yaml via the existing context-local home override. Single-profile gateways never enter the scope — behavior unchanged. Both call sites invoke the helper via asyncio.to_thread: under the scope, resolution can do blocking work (credential refresh, context-length HTTP probes) that previously failed fast unscoped and must not run on the event loop. Fixes #59003 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(release): map irresi author email for PR #59048 salvage --------- Co-authored-by: irresi <blueirobin02@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
f1fde49e45
commit
088b989442
4 changed files with 78 additions and 3 deletions
|
|
@ -10678,7 +10678,9 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
f"Adjust reset timing in config.yaml under session_reset."
|
||||
)
|
||||
try:
|
||||
session_info = self._format_session_info()
|
||||
session_info = await asyncio.to_thread(
|
||||
self._reset_notice_session_info, source
|
||||
)
|
||||
if session_info:
|
||||
notice = f"{notice}\n\n{session_info}"
|
||||
except Exception:
|
||||
|
|
@ -11927,6 +11929,26 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
# Restore session context variables to their pre-handler state
|
||||
self._clear_session_env(_session_env_tokens)
|
||||
|
||||
def _reset_notice_session_info(self, source: SessionSource) -> str:
|
||||
"""Session-info block for the auto-reset notice, profile-scoped.
|
||||
|
||||
When multiplexing, resolve model/provider/context inside the profile
|
||||
serving ``source`` — otherwise the banner advertises the base config's
|
||||
model while the session actually runs on the profile's (#59003).
|
||||
Mirrors ``_run_agent``'s gating so single-profile gateways never
|
||||
enter the scope.
|
||||
|
||||
Call via ``asyncio.to_thread`` from async handlers: under the scope,
|
||||
resolution can do blocking work (credential refresh, context-length
|
||||
HTTP probes) that must not run on the event loop. The scope is entered
|
||||
inside this method, so contextvars behave correctly in the worker
|
||||
thread.
|
||||
"""
|
||||
if getattr(getattr(self, "config", None), "multiplex_profiles", False):
|
||||
with _profile_runtime_scope(self._resolve_profile_home_for_source(source)):
|
||||
return self._format_session_info()
|
||||
return self._format_session_info()
|
||||
|
||||
def _format_session_info(self) -> str:
|
||||
"""Resolve current model config and return a formatted info block.
|
||||
|
||||
|
|
|
|||
|
|
@ -231,9 +231,13 @@ class GatewaySlashCommandsMixin:
|
|||
"session_key": session_key,
|
||||
})
|
||||
|
||||
# Resolve session config info to surface to the user
|
||||
# Resolve session config info to surface to the user, scoped to the
|
||||
# profile serving this source so a multiplexed /reset //new banner
|
||||
# reports the profile's model, not the base config's (#59003).
|
||||
try:
|
||||
session_info = self._format_session_info()
|
||||
session_info = await asyncio.to_thread(
|
||||
self._reset_notice_session_info, source
|
||||
)
|
||||
except Exception:
|
||||
session_info = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ AUTHOR_MAP = {
|
|||
"AndreasHiltner@users.noreply.github.com": "AndreasHiltner", # PR #56854 salvage (gateway: route multiplex profile responses through the profile's own adapter — 53-site _adapter_for_source sweep)
|
||||
"allenliang2022@users.noreply.github.com": "allenliang2022", # PR #56932 test coverage folded into #56909 salvage (408 → retryable timeout)
|
||||
"m888.braun@hotmail.com": "ManniBr", # PR #57417 partial salvage (gateway: fail-closed adapter resolution for unregistered secondary profiles)
|
||||
"blueirobin02@gmail.com": "irresi", # PR #59048 salvage (gateway: scope reset banners' session info to the serving profile; #59003)
|
||||
"jashlee+microsoft@microsoft.com": "s905060", # PR #57943 salvage (photon: auto-reinstall stale sidecar node_modules when lockfile is newer than npm's install marker; #59169)
|
||||
"lohinth25@proton.me": "l0h1nth", # PR #32210 salvage (mattermost: accept leading-space slash commands from mobile clients; #25184)
|
||||
"perkintahmaz50@gmail.com": "devatnull", # PR #58704 salvage (whatsapp: native Baileys polls, clarify-as-poll, location pins, structured quoted replies, PTT/audio split, bridge_helpers extraction)
|
||||
|
|
|
|||
|
|
@ -107,3 +107,51 @@ class TestFormatSessionInfo:
|
|||
info = runner._format_session_info()
|
||||
assert "4K" in info
|
||||
assert "config" in info
|
||||
|
||||
|
||||
class TestResetNoticeSessionInfo:
|
||||
"""#59003: the auto-reset banner must report the serving profile's config,
|
||||
not the multiplexer's base config."""
|
||||
|
||||
_RUNTIME = {"provider": "", "base_url": "", "api_key": ""}
|
||||
|
||||
def _source(self):
|
||||
from gateway.config import Platform
|
||||
from gateway.session import SessionSource
|
||||
return SessionSource(
|
||||
platform=Platform.TELEGRAM, chat_id="123", user_id="u1",
|
||||
profile="planner",
|
||||
)
|
||||
|
||||
def _homes(self, tmp_path):
|
||||
base = tmp_path / "base"
|
||||
profile = tmp_path / "profiles" / "planner"
|
||||
profile.mkdir(parents=True)
|
||||
base.mkdir()
|
||||
base.joinpath("config.yaml").write_text(
|
||||
"model:\n default: base-model\n provider: custom\n context_length: 1000\n")
|
||||
profile.joinpath("config.yaml").write_text(
|
||||
"model:\n default: profile-model\n provider: anthropic\n context_length: 2000\n")
|
||||
return base, profile
|
||||
|
||||
def test_multiplex_uses_profile_config(self, runner, tmp_path):
|
||||
from types import SimpleNamespace
|
||||
base, profile = self._homes(tmp_path)
|
||||
runner.config = SimpleNamespace(multiplex_profiles=True)
|
||||
with patch("gateway.run._hermes_home", base), \
|
||||
patch.object(GatewayRunner, "_resolve_profile_home_for_source", return_value=profile), \
|
||||
patch("gateway.run._resolve_runtime_agent_kwargs", return_value=self._RUNTIME):
|
||||
info = runner._reset_notice_session_info(self._source())
|
||||
assert "profile-model" in info
|
||||
assert "anthropic" in info
|
||||
assert "base-model" not in info
|
||||
|
||||
def test_single_profile_uses_base_config(self, runner, tmp_path):
|
||||
from types import SimpleNamespace
|
||||
base, _profile = self._homes(tmp_path)
|
||||
runner.config = SimpleNamespace(multiplex_profiles=False)
|
||||
with patch("gateway.run._hermes_home", base), \
|
||||
patch("gateway.run._resolve_runtime_agent_kwargs", return_value=self._RUNTIME):
|
||||
info = runner._reset_notice_session_info(self._source())
|
||||
assert "base-model" in info
|
||||
assert "profile-model" not in info
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue