refactor(gateway/slash): unify context-breakdown transcript load; tidy module headers and import order

This commit is contained in:
Teknium
2026-09-02 16:30:01 -07:00
parent 5ab334c6e7
commit 59acca1850
5 changed files with 42 additions and 52 deletions

View File

@@ -1,9 +1,13 @@
"""Gateway slash-command handlers for GatewayRunner.
The in-session slash commands (/model, /reset, /usage, ...) dispatched from ``_handle_message``,
lifted out of ``gateway/run.py`` into a mixin so every ``self._handle_*_command`` reference keeps
working via the MRO. run.py helpers (``_hermes_home``, ``_load_gateway_config``, ...) are imported
lazily inside handler bodies — a deferred ``from gateway.run import ...`` avoids the import cycle.
The in-session slash commands dispatched from ``_handle_message``, lifted out of ``gateway/run.py``
into a mixin so every ``self._handle_*_command`` reference keeps working via the MRO. Cohesive
clusters live in sibling mixins this class inherits: ``slash_commands_model`` (/model, /reasoning,
/fast, ...), ``slash_commands_session`` (/new, /resume, /branch, /compress, ...),
``slash_commands_status`` (/status, /context, /usage, ...) and ``slash_commands_goals`` (/goal,
/loop, /heartbeat, ...). This module keeps the shared helpers plus the remaining one-off commands.
run.py helpers (``_hermes_home``, ``_load_gateway_config``, ...) are imported lazily inside handler
bodies — a deferred ``from gateway.run import ...`` avoids the import cycle.
"""
from __future__ import annotations
@@ -24,25 +28,16 @@ from typing import Optional, Union
from agent.i18n import t
from gateway.config import HomeChannel, Platform, PlatformConfig, persist_home_channel
from gateway.platforms.base import EphemeralReply, MessageEvent
from gateway.session import (
AsyncSessionStore,
)
from hermes_cli.config import atomic_config_write, cfg_get
from utils import (
atomic_json_write,
is_truthy_value,
)
from gateway.slash_commands_model import ( # noqa: F401 — _model_switch_skew_guard re-exported for callers/tests
from gateway.session import AsyncSessionStore
from gateway.slash_commands_goals import GatewayGoalCommandsMixin
from gateway.slash_commands_model import ( # noqa: F401 — _model_switch_skew_guard re-exported for tests
GatewayModelCommandsMixin,
_model_switch_skew_guard,
)
from gateway.slash_commands_session import GatewaySessionCommandsMixin
from gateway.slash_commands_status import GatewayStatusCommandsMixin
from gateway.slash_commands_goals import GatewayGoalCommandsMixin
from hermes_cli.config import atomic_config_write, cfg_get
from utils import atomic_json_write, is_truthy_value
logger = logging.getLogger("gateway.run")
@@ -190,8 +185,13 @@ def _home_thread_from_source(source) -> Optional[str]:
return str(thread_id)
class GatewaySlashCommandsMixin(GatewayModelCommandsMixin, GatewaySessionCommandsMixin, GatewayStatusCommandsMixin, GatewayGoalCommandsMixin):
"""In-session slash-command handlers for GatewayRunner."""
class GatewaySlashCommandsMixin(
GatewayModelCommandsMixin,
GatewaySessionCommandsMixin,
GatewayStatusCommandsMixin,
GatewayGoalCommandsMixin,
):
"""In-session slash-command handlers for GatewayRunner (plus the helpers the sibling mixins share)."""
async_session_store: AsyncSessionStore
@@ -320,7 +320,6 @@ class GatewaySlashCommandsMixin(GatewayModelCommandsMixin, GatewaySessionCommand
adapter = self.adapters.get(platform) if getattr(self, "adapters", None) else None
return getattr(adapter, "typed_command_prefix", "/") if adapter is not None else "/"
async def _handle_profile_command(self, event: MessageEvent) -> str:
"""Handle /profile — show the profile serving this source and its home.
@@ -523,7 +522,6 @@ class GatewaySlashCommandsMixin(GatewayModelCommandsMixin, GatewaySessionCommand
await asyncio.to_thread(_sub)
return True
async def _handle_stop_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:
"""Handle /stop command - interrupt a running agent.
@@ -791,7 +789,6 @@ class GatewaySlashCommandsMixin(GatewayModelCommandsMixin, GatewaySessionCommand
)
return self._telegramized_command_reply(event, reply.text)
async def _handle_set_home_command(self, event: MessageEvent) -> str:
"""Handle /sethome command -- set the current chat as the platform's home channel."""
from gateway.run import _home_target_env_var, _home_thread_env_var
@@ -1183,7 +1180,6 @@ class GatewaySlashCommandsMixin(GatewayModelCommandsMixin, GatewaySessionCommand
return t("gateway.btw.started", preview=preview)
async def _handle_memory_command(self, event: MessageEvent) -> str:
"""Handle /memory — review pending memory writes + toggle the approval gate.
@@ -1247,7 +1243,6 @@ class GatewaySlashCommandsMixin(GatewayModelCommandsMixin, GatewaySessionCommand
f"~/.hermes/pending/skills/{pending_id}.json)")
return out
async def _handle_approvals_command(self, event: MessageEvent) -> str:
"""Show or persist the profile-wide dangerous-command approval mode."""
from gateway.slash_access import policy_for_source
@@ -1441,7 +1436,6 @@ class GatewaySlashCommandsMixin(GatewayModelCommandsMixin, GatewaySessionCommand
example = t("gateway.footer.example_line", preview=preview)
return t("gateway.footer.saved", state=state, example=example)
async def _handle_reload_mcp_command(self, event: MessageEvent) -> Optional[str]:
"""Handle /reload-mcp — reconnect MCP servers and rebuild the cached agent.

View File

@@ -8,6 +8,7 @@ import ...``) inside the bodies to avoid the import cycle.
from __future__ import annotations
import logging
from agent.i18n import t
from gateway.platforms.base import MessageEvent, MessageType

View File

@@ -9,10 +9,11 @@ from __future__ import annotations
import logging
import asyncio
from typing import Optional
from agent.i18n import t
from gateway.platforms.base import MessageEvent
from hermes_cli.config import atomic_config_write, clear_model_endpoint_credentials
from typing import Optional
from utils import base_url_host_matches
# Log-record parity with gateway/run.py and the origin module.

View File

@@ -13,12 +13,13 @@ import contextlib
import dataclasses
import os
import shlex
from typing import Optional, Union
from agent.i18n import t
from agent.turn_context import extract_api_content_sidecar
from gateway.config import Platform
from gateway.platforms.base import EphemeralReply, MessageEvent, MessageType
from gateway.session import SessionSource, build_session_key, is_shared_multi_user_session
from typing import Optional, Union
# Log-record parity with gateway/run.py and the origin module.
logger = logging.getLogger("gateway.run")

View File

@@ -13,11 +13,12 @@ import hashlib
import os
import re
import time
from typing import Any
from agent.account_usage import fetch_account_usage, render_account_usage_lines
from agent.i18n import t
from gateway.config import Platform
from gateway.platforms.base import MessageEvent
from typing import Any
# Log-record parity with gateway/run.py and the origin module.
logger = logging.getLogger("gateway.run")
@@ -610,20 +611,9 @@ class GatewayStatusCommandsMixin:
Estimated (chars/4), same engine as /usage. Runs in a thread; returns [] and never raises.
"""
try:
from agent.context_breakdown import (
compute_context_details,
compute_session_context_breakdown,
render_context_breakdown_lines,
)
from agent.context_breakdown import compute_context_details, render_context_breakdown_lines
history: list[dict] = []
try:
entry = self.session_store.get_or_create_session(source)
history = self.session_store.load_transcript(entry.session_id) or []
except Exception:
history = []
payload = compute_session_context_breakdown(agent, history)
payload = self._session_context_breakdown(agent, source)
if not (payload.get("categories") or []):
return []
@@ -638,22 +628,25 @@ class GatewayStatusCommandsMixin:
except Exception:
return []
def _session_context_breakdown(self, agent, source) -> dict:
"""Per-category context estimate (chars/4) for *agent* over the session transcript (sync)."""
from agent.context_breakdown import compute_session_context_breakdown
history: list[dict] = []
try:
entry = self.session_store.get_or_create_session(source)
history = self.session_store.load_transcript(entry.session_id) or []
except Exception:
history = []
return compute_session_context_breakdown(agent, history)
def _context_breakdown_lines(self, agent, source) -> list[str]:
"""Render the per-category context breakdown for /usage.
Estimated (chars/4). Returns [] and never raises so /usage stays robust.
"""
try:
from agent.context_breakdown import compute_session_context_breakdown
history: list[dict] = []
try:
entry = self.session_store.get_or_create_session(source)
history = self.session_store.load_transcript(entry.session_id) or []
except Exception:
history = []
payload = compute_session_context_breakdown(agent, history)
payload = self._session_context_breakdown(agent, source)
categories = payload.get("categories") or []
if not categories:
return []