fix: address PR review round 4 — remove web UI, fix audio/import/interface issues
Remove web UI gateway (web.py, tests, docs, toolset, env vars, Platform.WEB enum) per maintainer request — Nous is building their own official chat UI. Fix 1: Replace sd.wait() with polling pattern in play_audio_file() to prevent indefinite hang when audio device stalls (consistent with play_beep()). Fix 2: Use importlib.util.find_spec() for faster_whisper/openai availability checks instead of module-level imports that trigger heavy native library loading (CUDA/cuDNN) at import time. Fix 3: Remove inspect.signature() hack in _send_voice_reply() — add **kwargs to Telegram send_voice() so all adapters accept metadata uniformly. Fix 4: Make session loading resilient to removed platform enum values — skip entries with unknown platforms instead of crashing the entire gateway.
This commit is contained in:
parent
1ad5e0ed15
commit
35748a2fb0
17 changed files with 55 additions and 2930 deletions
|
|
@ -31,7 +31,6 @@ class Platform(Enum):
|
|||
SIGNAL = "signal"
|
||||
HOMEASSISTANT = "homeassistant"
|
||||
EMAIL = "email"
|
||||
WEB = "web"
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -177,9 +176,6 @@ class GatewayConfig:
|
|||
# Email uses extra dict for config (address + imap_host + smtp_host)
|
||||
elif platform == Platform.EMAIL and config.extra.get("address"):
|
||||
connected.append(platform)
|
||||
# Web UI uses enabled flag only
|
||||
elif platform == Platform.WEB:
|
||||
connected.append(platform)
|
||||
return connected
|
||||
|
||||
def get_home_channel(self, platform: Platform) -> Optional[HomeChannel]:
|
||||
|
|
@ -470,18 +466,6 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
name=os.getenv("EMAIL_HOME_ADDRESS_NAME", "Home"),
|
||||
)
|
||||
|
||||
# Web UI
|
||||
web_enabled = os.getenv("WEB_UI_ENABLED", "").lower() in ("true", "1", "yes")
|
||||
if web_enabled:
|
||||
if Platform.WEB not in config.platforms:
|
||||
config.platforms[Platform.WEB] = PlatformConfig()
|
||||
config.platforms[Platform.WEB].enabled = True
|
||||
config.platforms[Platform.WEB].extra.update({
|
||||
"port": int(os.getenv("WEB_UI_PORT", "8765")),
|
||||
"host": os.getenv("WEB_UI_HOST", "") or "127.0.0.1",
|
||||
"token": os.getenv("WEB_UI_TOKEN", ""),
|
||||
})
|
||||
|
||||
# Session settings
|
||||
idle_minutes = os.getenv("SESSION_IDLE_MINUTES")
|
||||
if idle_minutes:
|
||||
|
|
|
|||
|
|
@ -311,6 +311,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
caption: Optional[str] = None,
|
||||
reply_to: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> SendResult:
|
||||
"""Send audio as a native Telegram voice message or audio file."""
|
||||
if not self._bot:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -829,13 +829,6 @@ class GatewayRunner:
|
|||
return None
|
||||
return EmailAdapter(config)
|
||||
|
||||
elif platform == Platform.WEB:
|
||||
from gateway.platforms.web import WebAdapter, check_web_requirements
|
||||
if not check_web_requirements():
|
||||
logger.warning("Web: aiohttp not installed. Run: pip install aiohttp")
|
||||
return None
|
||||
return WebAdapter(config)
|
||||
|
||||
return None
|
||||
|
||||
def _is_user_authorized(self, source: SessionSource) -> bool:
|
||||
|
|
@ -855,11 +848,6 @@ class GatewayRunner:
|
|||
if source.platform == Platform.HOMEASSISTANT:
|
||||
return True
|
||||
|
||||
# Web UI users are authenticated via token at the WebSocket level.
|
||||
# No additional allowlist check needed.
|
||||
if source.platform == Platform.WEB:
|
||||
return True
|
||||
|
||||
user_id = source.user_id
|
||||
if not user_id:
|
||||
return False
|
||||
|
|
@ -978,7 +966,7 @@ class GatewayRunner:
|
|||
"personality", "retry", "undo", "sethome", "set-home",
|
||||
"compress", "usage", "insights", "reload-mcp", "reload_mcp",
|
||||
"update", "title", "resume", "provider", "rollback",
|
||||
"background", "reasoning", "voice", "remote-control", "remote_control"}
|
||||
"background", "reasoning", "voice"}
|
||||
if command and command in _known_commands:
|
||||
await self.hooks.emit(f"command:{command}", {
|
||||
"platform": source.platform.value if source.platform else "",
|
||||
|
|
@ -1053,10 +1041,6 @@ class GatewayRunner:
|
|||
if command == "voice":
|
||||
return await self._handle_voice_command(event)
|
||||
|
||||
if command in ("remote-control", "remote_control"):
|
||||
return await self._handle_remote_control_command(event)
|
||||
|
||||
|
||||
# User-defined quick commands (bypass agent loop, no LLM call)
|
||||
if command:
|
||||
quick_commands = self.config.get("quick_commands", {})
|
||||
|
|
@ -1741,7 +1725,6 @@ class GatewayRunner:
|
|||
"`/rollback [number]` — List or restore filesystem checkpoints",
|
||||
"`/background <prompt>` — Run a prompt in a separate background session",
|
||||
"`/voice [on|off|tts|status]` — Toggle voice reply mode",
|
||||
"`/remote-control [port] [token]` — Start web UI for remote access",
|
||||
"`/reload-mcp` — Reload MCP servers from config",
|
||||
"`/update` — Update Hermes Agent to the latest version",
|
||||
"`/help` — Show this message",
|
||||
|
|
@ -2415,10 +2398,6 @@ class GatewayRunner:
|
|||
}
|
||||
if event.source.thread_id:
|
||||
send_kwargs["metadata"] = {"thread_id": event.source.thread_id}
|
||||
import inspect
|
||||
sig = inspect.signature(adapter.send_voice)
|
||||
if "metadata" not in sig.parameters:
|
||||
send_kwargs.pop("metadata", None)
|
||||
await adapter.send_voice(**send_kwargs)
|
||||
except Exception as e:
|
||||
logger.warning("Auto voice reply failed: %s", e, exc_info=True)
|
||||
|
|
@ -2488,62 +2467,6 @@ class GatewayRunner:
|
|||
)
|
||||
return f"❌ {result['error']}"
|
||||
|
||||
async def _handle_remote_control_command(self, event: MessageEvent) -> str:
|
||||
"""Handle /remote-control — start or show the web UI for remote access."""
|
||||
from gateway.config import Platform, PlatformConfig
|
||||
|
||||
is_dm = event.source and event.source.chat_type == "dm"
|
||||
|
||||
# Already running?
|
||||
if Platform.WEB in self.adapters:
|
||||
adapter = self.adapters[Platform.WEB]
|
||||
local_ip = adapter._get_local_ip()
|
||||
token_display = adapter._token if is_dm else "(hidden — use in DM to see token)"
|
||||
return (
|
||||
f"Web UI already running.\n"
|
||||
f"URL: http://{local_ip}:{adapter._port}\n"
|
||||
f"Token: {token_display}"
|
||||
)
|
||||
|
||||
# Start web adapter on the fly
|
||||
try:
|
||||
from gateway.platforms.web import WebAdapter, check_web_requirements
|
||||
if not check_web_requirements():
|
||||
return "Web UI requires aiohttp. Run: pip install aiohttp"
|
||||
|
||||
args = event.get_command_args().strip()
|
||||
port = 8765
|
||||
token = ""
|
||||
for part in args.split():
|
||||
if part.isdigit():
|
||||
port = int(part)
|
||||
elif part and not part.startswith("-"):
|
||||
token = part
|
||||
|
||||
web_config = PlatformConfig(
|
||||
enabled=True,
|
||||
extra={"port": port, "host": "127.0.0.1", "token": token},
|
||||
)
|
||||
adapter = WebAdapter(web_config)
|
||||
adapter.set_message_handler(self._handle_message)
|
||||
|
||||
success = await adapter.connect()
|
||||
if not success:
|
||||
return f"Failed to start Web UI on port {port}. Port may be in use."
|
||||
|
||||
self.adapters[Platform.WEB] = adapter
|
||||
local_ip = adapter._get_local_ip()
|
||||
token_display = adapter._token if is_dm else "(hidden — use in DM to see token)"
|
||||
return (
|
||||
f"Web UI started!\n"
|
||||
f"URL: http://{local_ip}:{adapter._port}\n"
|
||||
f"Token: {token_display}\n"
|
||||
f"Open this URL on your phone or any device on the same network."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to start web UI: %s", e, exc_info=True)
|
||||
return f"Failed to start Web UI: {e}"
|
||||
|
||||
async def _handle_background_command(self, event: MessageEvent) -> str:
|
||||
"""Handle /background <prompt> — run a prompt in a separate background session.
|
||||
|
||||
|
|
@ -2607,7 +2530,6 @@ class GatewayRunner:
|
|||
Platform.SIGNAL: "hermes-signal",
|
||||
Platform.HOMEASSISTANT: "hermes-homeassistant",
|
||||
Platform.EMAIL: "hermes-email",
|
||||
Platform.WEB: "hermes-web",
|
||||
}
|
||||
platform_toolsets_config = {}
|
||||
try:
|
||||
|
|
@ -2629,7 +2551,6 @@ class GatewayRunner:
|
|||
Platform.SIGNAL: "signal",
|
||||
Platform.HOMEASSISTANT: "homeassistant",
|
||||
Platform.EMAIL: "email",
|
||||
Platform.WEB: "web",
|
||||
}.get(source.platform, "telegram")
|
||||
|
||||
config_toolsets = platform_toolsets_config.get(platform_config_key)
|
||||
|
|
@ -3517,7 +3438,6 @@ class GatewayRunner:
|
|||
Platform.SIGNAL: "hermes-signal",
|
||||
Platform.HOMEASSISTANT: "hermes-homeassistant",
|
||||
Platform.EMAIL: "hermes-email",
|
||||
Platform.WEB: "hermes-web",
|
||||
}
|
||||
|
||||
# Try to load platform_toolsets from config
|
||||
|
|
@ -3542,7 +3462,6 @@ class GatewayRunner:
|
|||
Platform.SIGNAL: "signal",
|
||||
Platform.HOMEASSISTANT: "homeassistant",
|
||||
Platform.EMAIL: "email",
|
||||
Platform.WEB: "web",
|
||||
}.get(source.platform, "telegram")
|
||||
|
||||
# Use config override if present (list of toolsets), otherwise hardcoded default
|
||||
|
|
|
|||
|
|
@ -383,7 +383,11 @@ class SessionStore:
|
|||
with open(sessions_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
for key, entry_data in data.items():
|
||||
self._entries[key] = SessionEntry.from_dict(entry_data)
|
||||
try:
|
||||
self._entries[key] = SessionEntry.from_dict(entry_data)
|
||||
except (ValueError, KeyError):
|
||||
# Skip entries with unknown/removed platform values
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"[gateway] Warning: Failed to load sessions: {e}")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue