mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
refactor: remove remaining redundant local imports (comprehensive sweep)
Full AST-based scan of all .py files to find every case where a module
or name is imported locally inside a function body but is already
available at module level. This is the second pass — the first commit
handled the known cases from the lint report; this one catches
everything else.
Files changed (19):
cli.py — 16 removals: time as _time/_t/_tmod (×10),
re / re as _re (×2), os as _os, sys,
partial os from combo import,
from model_tools import get_tool_definitions
gateway/run.py — 8 removals: MessageEvent as _ME /
MessageType as _MT (×3), os as _os2,
MessageEvent+MessageType (×2), Platform,
BasePlatformAdapter as _BaseAdapter
run_agent.py — 6 removals: get_hermes_home as _ghh,
partial (contextlib, os as _os),
cleanup_vm, cleanup_browser,
set_interrupt as _sif (×2),
partial get_toolset_for_tool
hermes_cli/main.py — 4 removals: get_hermes_home, time as _time,
logging as _log, shutil
hermes_cli/config.py — 1 removal: get_hermes_home as _ghome
hermes_cli/runtime_provider.py
— 1 removal: load_config as _load_bedrock_config
hermes_cli/setup.py — 2 removals: importlib.util (×2)
hermes_cli/nous_subscription.py
— 1 removal: from hermes_cli.config import load_config
hermes_cli/tools_config.py
— 1 removal: from hermes_cli.config import load_config, save_config
cron/scheduler.py — 3 removals: concurrent.futures, json as _json,
from hermes_cli.config import load_config
batch_runner.py — 1 removal: list_distributions as get_all_dists
(kept print_distribution_info, not at top level)
tools/send_message_tool.py
— 2 removals: import os (×2)
tools/skills_tool.py — 1 removal: logging as _logging
tools/browser_camofox.py
— 1 removal: from hermes_cli.config import load_config
tools/image_generation_tool.py
— 1 removal: import fal_client
environments/tool_context.py
— 1 removal: concurrent.futures
gateway/platforms/bluebubbles.py
— 1 removal: httpx as _httpx
gateway/platforms/whatsapp.py
— 1 removal: import asyncio
tui_gateway/server.py — 2 removals: from datetime import datetime,
import time
All alias references (_time, _t, _tmod, _re, _os, _os2, _json, _ghh,
_ghome, _sif, _ME, _MT, _BaseAdapter, _load_bedrock_config, _httpx,
_logging, _log, get_all_dists) updated to use the top-level names.
This commit is contained in:
parent
1010e5fa3c
commit
28b3f49aaa
19 changed files with 43 additions and 101 deletions
|
|
@ -1190,12 +1190,12 @@ def main(
|
||||||
"""
|
"""
|
||||||
# Handle list distributions
|
# Handle list distributions
|
||||||
if list_distributions:
|
if list_distributions:
|
||||||
from toolset_distributions import list_distributions as get_all_dists, print_distribution_info
|
from toolset_distributions import print_distribution_info
|
||||||
|
|
||||||
print("📊 Available Toolset Distributions")
|
print("📊 Available Toolset Distributions")
|
||||||
print("=" * 70)
|
print("=" * 70)
|
||||||
|
|
||||||
all_dists = get_all_dists()
|
all_dists = list_distributions()
|
||||||
for dist_name in sorted(all_dists.keys()):
|
for dist_name in sorted(all_dists.keys()):
|
||||||
print_distribution_info(dist_name)
|
print_distribution_info(dist_name)
|
||||||
|
|
||||||
|
|
|
||||||
53
cli.py
53
cli.py
|
|
@ -7159,7 +7159,6 @@ class HermesCLI:
|
||||||
|
|
||||||
# Refresh the agent's tool list so the model can call new tools
|
# Refresh the agent's tool list so the model can call new tools
|
||||||
if self.agent is not None:
|
if self.agent is not None:
|
||||||
from model_tools import get_tool_definitions
|
|
||||||
self.agent.tools = get_tool_definitions(
|
self.agent.tools = get_tool_definitions(
|
||||||
enabled_toolsets=self.agent.enabled_toolsets
|
enabled_toolsets=self.agent.enabled_toolsets
|
||||||
if hasattr(self.agent, "enabled_toolsets") else None,
|
if hasattr(self.agent, "enabled_toolsets") else None,
|
||||||
|
|
@ -7242,7 +7241,6 @@ class HermesCLI:
|
||||||
full history of tool calls (not just the current one in the spinner).
|
full history of tool calls (not just the current one in the spinner).
|
||||||
"""
|
"""
|
||||||
if event_type == "tool.completed":
|
if event_type == "tool.completed":
|
||||||
import time as _time
|
|
||||||
self._tool_start_time = 0.0
|
self._tool_start_time = 0.0
|
||||||
# Print stacked scrollback line for "all" / "new" modes
|
# Print stacked scrollback line for "all" / "new" modes
|
||||||
if function_name and self.tool_progress_mode in ("all", "new"):
|
if function_name and self.tool_progress_mode in ("all", "new"):
|
||||||
|
|
@ -7271,7 +7269,6 @@ class HermesCLI:
|
||||||
if event_type != "tool.started":
|
if event_type != "tool.started":
|
||||||
return
|
return
|
||||||
if function_name and not function_name.startswith("_"):
|
if function_name and not function_name.startswith("_"):
|
||||||
import time as _time
|
|
||||||
from agent.display import get_tool_emoji
|
from agent.display import get_tool_emoji
|
||||||
emoji = get_tool_emoji(function_name)
|
emoji = get_tool_emoji(function_name)
|
||||||
label = preview or function_name
|
label = preview or function_name
|
||||||
|
|
@ -7280,7 +7277,7 @@ class HermesCLI:
|
||||||
if _pl > 0 and len(label) > _pl:
|
if _pl > 0 and len(label) > _pl:
|
||||||
label = label[:_pl - 3] + "..."
|
label = label[:_pl - 3] + "..."
|
||||||
self._spinner_text = f"{emoji} {label}"
|
self._spinner_text = f"{emoji} {label}"
|
||||||
self._tool_start_time = _time.monotonic()
|
self._tool_start_time = time.monotonic()
|
||||||
# Store args for stacked scrollback line on completion
|
# Store args for stacked scrollback line on completion
|
||||||
self._pending_tool_info.setdefault(function_name, []).append(
|
self._pending_tool_info.setdefault(function_name, []).append(
|
||||||
function_args if function_args is not None else {}
|
function_args if function_args is not None else {}
|
||||||
|
|
@ -7538,7 +7535,6 @@ class HermesCLI:
|
||||||
try:
|
try:
|
||||||
from tools.tts_tool import text_to_speech_tool
|
from tools.tts_tool import text_to_speech_tool
|
||||||
from tools.voice_mode import play_audio_file
|
from tools.voice_mode import play_audio_file
|
||||||
import re
|
|
||||||
|
|
||||||
# Strip markdown and non-speech content for cleaner TTS
|
# Strip markdown and non-speech content for cleaner TTS
|
||||||
tts_text = text[:4000] if len(text) > 4000 else text
|
tts_text = text[:4000] if len(text) > 4000 else text
|
||||||
|
|
@ -8374,8 +8370,7 @@ class HermesCLI:
|
||||||
try:
|
try:
|
||||||
_dbg = _hermes_home / "interrupt_debug.log"
|
_dbg = _hermes_home / "interrupt_debug.log"
|
||||||
with open(_dbg, "a") as _f:
|
with open(_dbg, "a") as _f:
|
||||||
import time as _t
|
_f.write(f"{time.strftime('%H:%M:%S')} interrupt fired: msg={str(interrupt_msg)[:60]!r}, "
|
||||||
_f.write(f"{_t.strftime('%H:%M:%S')} interrupt fired: msg={str(interrupt_msg)[:60]!r}, "
|
|
||||||
f"children={len(self.agent._active_children)}, "
|
f"children={len(self.agent._active_children)}, "
|
||||||
f"parent._interrupt={self.agent._interrupt_requested}\n")
|
f"parent._interrupt={self.agent._interrupt_requested}\n")
|
||||||
for _ci, _ch in enumerate(self.agent._active_children):
|
for _ci, _ch in enumerate(self.agent._active_children):
|
||||||
|
|
@ -8451,9 +8446,8 @@ class HermesCLI:
|
||||||
# buffer so tool/status lines render ABOVE our response box.
|
# buffer so tool/status lines render ABOVE our response box.
|
||||||
# The flush pushes data into the renderer queue; the short
|
# The flush pushes data into the renderer queue; the short
|
||||||
# sleep lets the renderer actually paint it before we draw.
|
# sleep lets the renderer actually paint it before we draw.
|
||||||
import time as _time
|
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
_time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
|
|
||||||
# Update history with full conversation
|
# Update history with full conversation
|
||||||
self.conversation_history = result.get("messages", self.conversation_history) if result else self.conversation_history
|
self.conversation_history = result.get("messages", self.conversation_history) if result else self.conversation_history
|
||||||
|
|
@ -9119,8 +9113,7 @@ class HermesCLI:
|
||||||
try:
|
try:
|
||||||
_dbg = _hermes_home / "interrupt_debug.log"
|
_dbg = _hermes_home / "interrupt_debug.log"
|
||||||
with open(_dbg, "a") as _f:
|
with open(_dbg, "a") as _f:
|
||||||
import time as _t
|
_f.write(f"{time.strftime('%H:%M:%S')} ENTER: queued interrupt msg={str(payload)[:60]!r}, "
|
||||||
_f.write(f"{_t.strftime('%H:%M:%S')} ENTER: queued interrupt msg={str(payload)[:60]!r}, "
|
|
||||||
f"agent_running={self._agent_running}\n")
|
f"agent_running={self._agent_running}\n")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -9268,8 +9261,7 @@ class HermesCLI:
|
||||||
2. Interrupt the running agent (first press)
|
2. Interrupt the running agent (first press)
|
||||||
3. Force exit (second press within 2s, or when idle)
|
3. Force exit (second press within 2s, or when idle)
|
||||||
"""
|
"""
|
||||||
import time as _time
|
now = time.time()
|
||||||
now = _time.time()
|
|
||||||
|
|
||||||
# Cancel active voice recording.
|
# Cancel active voice recording.
|
||||||
# Run cancel() in a background thread to prevent blocking the
|
# Run cancel() in a background thread to prevent blocking the
|
||||||
|
|
@ -9377,12 +9369,11 @@ class HermesCLI:
|
||||||
@kb.add('c-z')
|
@kb.add('c-z')
|
||||||
def handle_ctrl_z(event):
|
def handle_ctrl_z(event):
|
||||||
"""Handle Ctrl+Z - suspend process to background (Unix only)."""
|
"""Handle Ctrl+Z - suspend process to background (Unix only)."""
|
||||||
import sys
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
_cprint(f"\n{_DIM}Suspend (Ctrl+Z) is not supported on Windows.{_RST}")
|
_cprint(f"\n{_DIM}Suspend (Ctrl+Z) is not supported on Windows.{_RST}")
|
||||||
event.app.invalidate()
|
event.app.invalidate()
|
||||||
return
|
return
|
||||||
import os, signal as _sig
|
import signal as _sig
|
||||||
from prompt_toolkit.application import run_in_terminal
|
from prompt_toolkit.application import run_in_terminal
|
||||||
from hermes_cli.skin_engine import get_active_skin
|
from hermes_cli.skin_engine import get_active_skin
|
||||||
agent_name = get_active_skin().get_branding("agent_name", "Hermes Agent")
|
agent_name = get_active_skin().get_branding("agent_name", "Hermes Agent")
|
||||||
|
|
@ -9696,31 +9687,29 @@ class HermesCLI:
|
||||||
# extra instructions (sudo countdown, approval navigation, clarify).
|
# extra instructions (sudo countdown, approval navigation, clarify).
|
||||||
# The agent-running interrupt hint is now an inline placeholder above.
|
# The agent-running interrupt hint is now an inline placeholder above.
|
||||||
def get_hint_text():
|
def get_hint_text():
|
||||||
import time as _time
|
|
||||||
|
|
||||||
if cli_ref._sudo_state:
|
if cli_ref._sudo_state:
|
||||||
remaining = max(0, int(cli_ref._sudo_deadline - _time.monotonic()))
|
remaining = max(0, int(cli_ref._sudo_deadline - time.monotonic()))
|
||||||
return [
|
return [
|
||||||
('class:hint', ' password hidden · Enter to skip'),
|
('class:hint', ' password hidden · Enter to skip'),
|
||||||
('class:clarify-countdown', f' ({remaining}s)'),
|
('class:clarify-countdown', f' ({remaining}s)'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if cli_ref._secret_state:
|
if cli_ref._secret_state:
|
||||||
remaining = max(0, int(cli_ref._secret_deadline - _time.monotonic()))
|
remaining = max(0, int(cli_ref._secret_deadline - time.monotonic()))
|
||||||
return [
|
return [
|
||||||
('class:hint', ' secret hidden · Enter to skip'),
|
('class:hint', ' secret hidden · Enter to skip'),
|
||||||
('class:clarify-countdown', f' ({remaining}s)'),
|
('class:clarify-countdown', f' ({remaining}s)'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if cli_ref._approval_state:
|
if cli_ref._approval_state:
|
||||||
remaining = max(0, int(cli_ref._approval_deadline - _time.monotonic()))
|
remaining = max(0, int(cli_ref._approval_deadline - time.monotonic()))
|
||||||
return [
|
return [
|
||||||
('class:hint', ' ↑/↓ to select, Enter to confirm'),
|
('class:hint', ' ↑/↓ to select, Enter to confirm'),
|
||||||
('class:clarify-countdown', f' ({remaining}s)'),
|
('class:clarify-countdown', f' ({remaining}s)'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if cli_ref._clarify_state:
|
if cli_ref._clarify_state:
|
||||||
remaining = max(0, int(cli_ref._clarify_deadline - _time.monotonic()))
|
remaining = max(0, int(cli_ref._clarify_deadline - time.monotonic()))
|
||||||
countdown = f' ({remaining}s)' if cli_ref._clarify_deadline else ''
|
countdown = f' ({remaining}s)' if cli_ref._clarify_deadline else ''
|
||||||
if cli_ref._clarify_freetext:
|
if cli_ref._clarify_freetext:
|
||||||
return [
|
return [
|
||||||
|
|
@ -10268,22 +10257,20 @@ class HermesCLI:
|
||||||
app._on_resize = _resize_clear_ghosts
|
app._on_resize = _resize_clear_ghosts
|
||||||
|
|
||||||
def spinner_loop():
|
def spinner_loop():
|
||||||
import time as _time
|
|
||||||
|
|
||||||
last_idle_refresh = 0.0
|
last_idle_refresh = 0.0
|
||||||
while not self._should_exit:
|
while not self._should_exit:
|
||||||
if not self._app:
|
if not self._app:
|
||||||
_time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
continue
|
continue
|
||||||
if self._command_running:
|
if self._command_running:
|
||||||
self._invalidate(min_interval=0.1)
|
self._invalidate(min_interval=0.1)
|
||||||
_time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
else:
|
else:
|
||||||
now = _time.monotonic()
|
now = time.monotonic()
|
||||||
if now - last_idle_refresh >= 1.0:
|
if now - last_idle_refresh >= 1.0:
|
||||||
last_idle_refresh = now
|
last_idle_refresh = now
|
||||||
self._invalidate(min_interval=1.0)
|
self._invalidate(min_interval=1.0)
|
||||||
_time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
spinner_thread = threading.Thread(target=spinner_loop, daemon=True)
|
spinner_thread = threading.Thread(target=spinner_loop, daemon=True)
|
||||||
spinner_thread.start()
|
spinner_thread.start()
|
||||||
|
|
@ -10352,8 +10339,7 @@ class HermesCLI:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Expand paste references back to full content
|
# Expand paste references back to full content
|
||||||
import re as _re
|
_paste_ref_re = re.compile(r'\[Pasted text #\d+: \d+ lines \u2192 (.+?)\]')
|
||||||
_paste_ref_re = _re.compile(r'\[Pasted text #\d+: \d+ lines \u2192 (.+?)\]')
|
|
||||||
paste_refs = list(_paste_ref_re.finditer(user_input)) if isinstance(user_input, str) else []
|
paste_refs = list(_paste_ref_re.finditer(user_input)) if isinstance(user_input, str) else []
|
||||||
if paste_refs:
|
if paste_refs:
|
||||||
user_input = self._expand_paste_references(user_input)
|
user_input = self._expand_paste_references(user_input)
|
||||||
|
|
@ -10445,13 +10431,12 @@ class HermesCLI:
|
||||||
try:
|
try:
|
||||||
if getattr(self, "agent", None) and getattr(self, "_agent_running", False):
|
if getattr(self, "agent", None) and getattr(self, "_agent_running", False):
|
||||||
self.agent.interrupt(f"received signal {signum}")
|
self.agent.interrupt(f"received signal {signum}")
|
||||||
import time as _t
|
|
||||||
try:
|
try:
|
||||||
_grace = float(os.getenv("HERMES_SIGTERM_GRACE", "1.5"))
|
_grace = float(os.getenv("HERMES_SIGTERM_GRACE", "1.5"))
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
_grace = 1.5
|
_grace = 1.5
|
||||||
if _grace > 0:
|
if _grace > 0:
|
||||||
_t.sleep(_grace)
|
time.sleep(_grace)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # never block signal handling
|
pass # never block signal handling
|
||||||
raise KeyboardInterrupt()
|
raise KeyboardInterrupt()
|
||||||
|
|
@ -10484,8 +10469,7 @@ class HermesCLI:
|
||||||
# uv-managed Python, fd 0 can be invalid or unregisterable with the
|
# uv-managed Python, fd 0 can be invalid or unregisterable with the
|
||||||
# asyncio selector, causing "KeyError: '0 is not registered'" (#6393).
|
# asyncio selector, causing "KeyError: '0 is not registered'" (#6393).
|
||||||
try:
|
try:
|
||||||
import os as _os
|
os.fstat(0)
|
||||||
_os.fstat(0)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
print(
|
print(
|
||||||
"Error: stdin (fd 0) is not available.\n"
|
"Error: stdin (fd 0) is not available.\n"
|
||||||
|
|
@ -10778,13 +10762,12 @@ def main(
|
||||||
_agent = getattr(cli, "agent", None)
|
_agent = getattr(cli, "agent", None)
|
||||||
if _agent is not None:
|
if _agent is not None:
|
||||||
_agent.interrupt(f"received signal {signum}")
|
_agent.interrupt(f"received signal {signum}")
|
||||||
import time as _t
|
|
||||||
try:
|
try:
|
||||||
_grace = float(os.getenv("HERMES_SIGTERM_GRACE", "1.5"))
|
_grace = float(os.getenv("HERMES_SIGTERM_GRACE", "1.5"))
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
_grace = 1.5
|
_grace = 1.5
|
||||||
if _grace > 0:
|
if _grace > 0:
|
||||||
_t.sleep(_grace)
|
time.sleep(_grace)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # never block signal handling
|
pass # never block signal handling
|
||||||
raise KeyboardInterrupt()
|
raise KeyboardInterrupt()
|
||||||
|
|
|
||||||
|
|
@ -422,7 +422,6 @@ def _deliver_result(job: dict, content: str, adapters=None, loop=None) -> Option
|
||||||
# prevent "coroutine was never awaited" RuntimeWarning, then retry in a
|
# prevent "coroutine was never awaited" RuntimeWarning, then retry in a
|
||||||
# fresh thread that has no running loop.
|
# fresh thread that has no running loop.
|
||||||
coro.close()
|
coro.close()
|
||||||
import concurrent.futures
|
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
||||||
future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, cleaned_delivery_content, thread_id=thread_id, media_files=media_files))
|
future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, cleaned_delivery_content, thread_id=thread_id, media_files=media_files))
|
||||||
result = future.result(timeout=30)
|
result = future.result(timeout=30)
|
||||||
|
|
@ -810,14 +809,13 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
||||||
prefill_messages = None
|
prefill_messages = None
|
||||||
prefill_file = os.getenv("HERMES_PREFILL_MESSAGES_FILE", "") or _cfg.get("prefill_messages_file", "")
|
prefill_file = os.getenv("HERMES_PREFILL_MESSAGES_FILE", "") or _cfg.get("prefill_messages_file", "")
|
||||||
if prefill_file:
|
if prefill_file:
|
||||||
import json as _json
|
|
||||||
pfpath = Path(prefill_file).expanduser()
|
pfpath = Path(prefill_file).expanduser()
|
||||||
if not pfpath.is_absolute():
|
if not pfpath.is_absolute():
|
||||||
pfpath = _hermes_home / pfpath
|
pfpath = _hermes_home / pfpath
|
||||||
if pfpath.exists():
|
if pfpath.exists():
|
||||||
try:
|
try:
|
||||||
with open(pfpath, "r", encoding="utf-8") as _pf:
|
with open(pfpath, "r", encoding="utf-8") as _pf:
|
||||||
prefill_messages = _json.load(_pf)
|
prefill_messages = json.load(_pf)
|
||||||
if not isinstance(prefill_messages, list):
|
if not isinstance(prefill_messages, list):
|
||||||
prefill_messages = None
|
prefill_messages = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1085,7 +1083,6 @@ def tick(verbose: bool = True, adapters=None, loop=None) -> int:
|
||||||
logger.warning("Invalid HERMES_CRON_MAX_PARALLEL value; defaulting to unbounded")
|
logger.warning("Invalid HERMES_CRON_MAX_PARALLEL value; defaulting to unbounded")
|
||||||
if _max_workers is None:
|
if _max_workers is None:
|
||||||
try:
|
try:
|
||||||
from hermes_cli.config import load_config
|
|
||||||
_ucfg = load_config() or {}
|
_ucfg = load_config() or {}
|
||||||
_cfg_par = (
|
_cfg_par = (
|
||||||
_ucfg.get("cron", {}) if isinstance(_ucfg, dict) else {}
|
_ucfg.get("cron", {}) if isinstance(_ucfg, dict) else {}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ def _run_tool_in_thread(tool_name: str, arguments: Dict[str, Any], task_id: str)
|
||||||
try:
|
try:
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
# We're in an async context -- need to run in thread
|
# We're in an async context -- need to run in thread
|
||||||
import concurrent.futures
|
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
||||||
future = pool.submit(
|
future = pool.submit(
|
||||||
handle_function_call, tool_name, arguments, task_id
|
handle_function_call, tool_name, arguments, task_id
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ def _redact(text: str) -> str:
|
||||||
def check_bluebubbles_requirements() -> bool:
|
def check_bluebubbles_requirements() -> bool:
|
||||||
try:
|
try:
|
||||||
import aiohttp # noqa: F401
|
import aiohttp # noqa: F401
|
||||||
import httpx as _httpx # noqa: F401
|
import httpx # noqa: F401
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -399,7 +399,6 @@ class WhatsAppAdapter(BasePlatformAdapter):
|
||||||
|
|
||||||
# Check if bridge is already running and connected
|
# Check if bridge is already running and connected
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import asyncio
|
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
|
|
|
||||||
|
|
@ -3274,10 +3274,9 @@ class GatewayRunner:
|
||||||
return "Usage: /queue <prompt>"
|
return "Usage: /queue <prompt>"
|
||||||
adapter = self.adapters.get(source.platform)
|
adapter = self.adapters.get(source.platform)
|
||||||
if adapter:
|
if adapter:
|
||||||
from gateway.platforms.base import MessageEvent as _ME, MessageType as _MT
|
queued_event = MessageEvent(
|
||||||
queued_event = _ME(
|
|
||||||
text=queued_text,
|
text=queued_text,
|
||||||
message_type=_MT.TEXT,
|
message_type=MessageType.TEXT,
|
||||||
source=event.source,
|
source=event.source,
|
||||||
message_id=event.message_id,
|
message_id=event.message_id,
|
||||||
channel_prompt=event.channel_prompt,
|
channel_prompt=event.channel_prompt,
|
||||||
|
|
@ -3299,10 +3298,9 @@ class GatewayRunner:
|
||||||
# Agent hasn't started yet — queue as turn-boundary fallback.
|
# Agent hasn't started yet — queue as turn-boundary fallback.
|
||||||
adapter = self.adapters.get(source.platform)
|
adapter = self.adapters.get(source.platform)
|
||||||
if adapter:
|
if adapter:
|
||||||
from gateway.platforms.base import MessageEvent as _ME, MessageType as _MT
|
queued_event = MessageEvent(
|
||||||
queued_event = _ME(
|
|
||||||
text=steer_text,
|
text=steer_text,
|
||||||
message_type=_MT.TEXT,
|
message_type=MessageType.TEXT,
|
||||||
source=event.source,
|
source=event.source,
|
||||||
message_id=event.message_id,
|
message_id=event.message_id,
|
||||||
channel_prompt=event.channel_prompt,
|
channel_prompt=event.channel_prompt,
|
||||||
|
|
@ -3322,10 +3320,9 @@ class GatewayRunner:
|
||||||
# Running agent is missing or lacks steer() — fall back to queue.
|
# Running agent is missing or lacks steer() — fall back to queue.
|
||||||
adapter = self.adapters.get(source.platform)
|
adapter = self.adapters.get(source.platform)
|
||||||
if adapter:
|
if adapter:
|
||||||
from gateway.platforms.base import MessageEvent as _ME, MessageType as _MT
|
queued_event = MessageEvent(
|
||||||
queued_event = _ME(
|
|
||||||
text=steer_text,
|
text=steer_text,
|
||||||
message_type=_MT.TEXT,
|
message_type=MessageType.TEXT,
|
||||||
source=event.source,
|
source=event.source,
|
||||||
message_id=event.message_id,
|
message_id=event.message_id,
|
||||||
channel_prompt=event.channel_prompt,
|
channel_prompt=event.channel_prompt,
|
||||||
|
|
@ -3857,9 +3854,7 @@ class GatewayRunner:
|
||||||
for i, path in enumerate(event.media_urls):
|
for i, path in enumerate(event.media_urls):
|
||||||
mtype = event.media_types[i] if i < len(event.media_types) else ""
|
mtype = event.media_types[i] if i < len(event.media_types) else ""
|
||||||
if mtype in ("", "application/octet-stream"):
|
if mtype in ("", "application/octet-stream"):
|
||||||
import os as _os2
|
_ext = os.path.splitext(path)[1].lower()
|
||||||
|
|
||||||
_ext = _os2.path.splitext(path)[1].lower()
|
|
||||||
if _ext in _TEXT_EXTENSIONS:
|
if _ext in _TEXT_EXTENSIONS:
|
||||||
mtype = "text/plain"
|
mtype = "text/plain"
|
||||||
else:
|
else:
|
||||||
|
|
@ -8302,7 +8297,6 @@ class GatewayRunner:
|
||||||
if not adapter:
|
if not adapter:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
from gateway.platforms.base import MessageEvent, MessageType
|
|
||||||
synth_event = MessageEvent(
|
synth_event = MessageEvent(
|
||||||
text=synth_text,
|
text=synth_text,
|
||||||
message_type=MessageType.TEXT,
|
message_type=MessageType.TEXT,
|
||||||
|
|
@ -8407,7 +8401,6 @@ class GatewayRunner:
|
||||||
break
|
break
|
||||||
if adapter and source.chat_id:
|
if adapter and source.chat_id:
|
||||||
try:
|
try:
|
||||||
from gateway.platforms.base import MessageEvent, MessageType
|
|
||||||
synth_event = MessageEvent(
|
synth_event = MessageEvent(
|
||||||
text=synth_text,
|
text=synth_text,
|
||||||
message_type=MessageType.TEXT,
|
message_type=MessageType.TEXT,
|
||||||
|
|
@ -8929,7 +8922,6 @@ class GatewayRunner:
|
||||||
if _streaming_enabled:
|
if _streaming_enabled:
|
||||||
try:
|
try:
|
||||||
from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig
|
from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig
|
||||||
from gateway.config import Platform
|
|
||||||
_adapter = self.adapters.get(source.platform)
|
_adapter = self.adapters.get(source.platform)
|
||||||
if _adapter:
|
if _adapter:
|
||||||
_adapter_supports_edit = getattr(_adapter, "SUPPORTS_MESSAGE_EDITING", True)
|
_adapter_supports_edit = getattr(_adapter, "SUPPORTS_MESSAGE_EDITING", True)
|
||||||
|
|
@ -9279,8 +9271,7 @@ class GatewayRunner:
|
||||||
# Skip tool progress for platforms that don't support message
|
# Skip tool progress for platforms that don't support message
|
||||||
# editing (e.g. iMessage/BlueBubbles) — each progress update
|
# editing (e.g. iMessage/BlueBubbles) — each progress update
|
||||||
# would become a separate message bubble, which is noisy.
|
# would become a separate message bubble, which is noisy.
|
||||||
from gateway.platforms.base import BasePlatformAdapter as _BaseAdapter
|
if type(adapter).edit_message is BasePlatformAdapter.edit_message:
|
||||||
if type(adapter).edit_message is _BaseAdapter.edit_message:
|
|
||||||
while not progress_queue.empty():
|
while not progress_queue.empty():
|
||||||
try:
|
try:
|
||||||
progress_queue.get_nowait()
|
progress_queue.get_nowait()
|
||||||
|
|
|
||||||
|
|
@ -2605,8 +2605,7 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
|
||||||
# Scan ``$HERMES_HOME/plugins/`` for currently installed user plugins.
|
# Scan ``$HERMES_HOME/plugins/`` for currently installed user plugins.
|
||||||
grandfathered: List[str] = []
|
grandfathered: List[str] = []
|
||||||
try:
|
try:
|
||||||
from hermes_constants import get_hermes_home as _ghome
|
user_plugins_dir = get_hermes_home() / "plugins"
|
||||||
user_plugins_dir = _ghome() / "plugins"
|
|
||||||
if user_plugins_dir.is_dir():
|
if user_plugins_dir.is_dir():
|
||||||
for child in sorted(user_plugins_dir.iterdir()):
|
for child in sorted(user_plugins_dir.iterdir()):
|
||||||
if not child.is_dir():
|
if not child.is_dir():
|
||||||
|
|
|
||||||
|
|
@ -4319,8 +4319,6 @@ def _gateway_prompt(prompt_text: str, default: str = "", timeout: float = 300.0)
|
||||||
tmp.replace(prompt_path)
|
tmp.replace(prompt_path)
|
||||||
|
|
||||||
# Poll for response
|
# Poll for response
|
||||||
import time as _time
|
|
||||||
|
|
||||||
deadline = _time.monotonic() + timeout
|
deadline = _time.monotonic() + timeout
|
||||||
while _time.monotonic() < deadline:
|
while _time.monotonic() < deadline:
|
||||||
if response_path.exists():
|
if response_path.exists():
|
||||||
|
|
@ -5214,8 +5212,6 @@ def _install_hangup_protection(gateway_mode: bool = False):
|
||||||
# (2) Mirror output to update.log and wrap stdio for broken-pipe
|
# (2) Mirror output to update.log and wrap stdio for broken-pipe
|
||||||
# tolerance. Any failure here is non-fatal; we just skip the wrap.
|
# tolerance. Any failure here is non-fatal; we just skip the wrap.
|
||||||
try:
|
try:
|
||||||
from hermes_cli.config import get_hermes_home
|
|
||||||
|
|
||||||
logs_dir = get_hermes_home() / "logs"
|
logs_dir = get_hermes_home() / "logs"
|
||||||
logs_dir.mkdir(parents=True, exist_ok=True)
|
logs_dir.mkdir(parents=True, exist_ok=True)
|
||||||
log_path = logs_dir / "update.log"
|
log_path = logs_dir / "update.log"
|
||||||
|
|
@ -5791,8 +5787,6 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
||||||
# Verify the service actually survived the
|
# Verify the service actually survived the
|
||||||
# restart. systemctl restart returns 0 even
|
# restart. systemctl restart returns 0 even
|
||||||
# if the new process crashes immediately.
|
# if the new process crashes immediately.
|
||||||
import time as _time
|
|
||||||
|
|
||||||
_time.sleep(3)
|
_time.sleep(3)
|
||||||
verify = subprocess.run(
|
verify = subprocess.run(
|
||||||
scope_cmd + ["is-active", svc_name],
|
scope_cmd + ["is-active", svc_name],
|
||||||
|
|
@ -7679,9 +7673,7 @@ Examples:
|
||||||
)
|
)
|
||||||
cmd_info["setup_fn"](plugin_parser)
|
cmd_info["setup_fn"](plugin_parser)
|
||||||
except Exception as _exc:
|
except Exception as _exc:
|
||||||
import logging as _log
|
logging.getLogger(__name__).debug("Plugin CLI discovery failed: %s", _exc)
|
||||||
|
|
||||||
_log.getLogger(__name__).debug("Plugin CLI discovery failed: %s", _exc)
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# memory command
|
# memory command
|
||||||
|
|
@ -8145,8 +8137,6 @@ Examples:
|
||||||
|
|
||||||
# Launch hermes --resume <id> by replacing the current process
|
# Launch hermes --resume <id> by replacing the current process
|
||||||
print(f"Resuming session: {selected_id}")
|
print(f"Resuming session: {selected_id}")
|
||||||
import shutil
|
|
||||||
|
|
||||||
hermes_bin = shutil.which("hermes")
|
hermes_bin = shutil.which("hermes")
|
||||||
if hermes_bin:
|
if hermes_bin:
|
||||||
os.execvp(hermes_bin, ["hermes", "--resume", selected_id])
|
os.execvp(hermes_bin, ["hermes", "--resume", selected_id])
|
||||||
|
|
|
||||||
|
|
@ -586,7 +586,6 @@ def get_gateway_eligible_tools(
|
||||||
return [], [], []
|
return [], [], []
|
||||||
|
|
||||||
if config is None:
|
if config is None:
|
||||||
from hermes_cli.config import load_config
|
|
||||||
config = load_config() or {}
|
config = load_config() or {}
|
||||||
|
|
||||||
# Quick provider check without the heavy get_nous_subscription_features call
|
# Quick provider check without the heavy get_nous_subscription_features call
|
||||||
|
|
|
||||||
|
|
@ -906,8 +906,7 @@ def resolve_runtime_provider(
|
||||||
code="no_aws_credentials",
|
code="no_aws_credentials",
|
||||||
)
|
)
|
||||||
# Read bedrock-specific config from config.yaml
|
# Read bedrock-specific config from config.yaml
|
||||||
from hermes_cli.config import load_config as _load_bedrock_config
|
_bedrock_cfg = load_config().get("bedrock", {})
|
||||||
_bedrock_cfg = _load_bedrock_config().get("bedrock", {})
|
|
||||||
# Region priority: config.yaml bedrock.region → env var → us-east-1
|
# Region priority: config.yaml bedrock.region → env var → us-east-1
|
||||||
region = (_bedrock_cfg.get("region") or "").strip() or resolve_bedrock_region()
|
region = (_bedrock_cfg.get("region") or "").strip() or resolve_bedrock_region()
|
||||||
auth_source = resolve_aws_auth_env_var() or "aws-sdk-default-chain"
|
auth_source = resolve_aws_auth_env_var() or "aws-sdk-default-chain"
|
||||||
|
|
|
||||||
|
|
@ -434,7 +434,6 @@ def _print_setup_summary(config: dict, hermes_home):
|
||||||
tool_status.append(("Text-to-Speech (Google Gemini)", True, None))
|
tool_status.append(("Text-to-Speech (Google Gemini)", True, None))
|
||||||
elif tts_provider == "neutts":
|
elif tts_provider == "neutts":
|
||||||
try:
|
try:
|
||||||
import importlib.util
|
|
||||||
neutts_ok = importlib.util.find_spec("neutts") is not None
|
neutts_ok = importlib.util.find_spec("neutts") is not None
|
||||||
except Exception:
|
except Exception:
|
||||||
neutts_ok = False
|
neutts_ok = False
|
||||||
|
|
@ -963,7 +962,6 @@ def _setup_tts_provider(config: dict):
|
||||||
if selected == "neutts":
|
if selected == "neutts":
|
||||||
# Check if already installed
|
# Check if already installed
|
||||||
try:
|
try:
|
||||||
import importlib.util
|
|
||||||
already_installed = importlib.util.find_spec("neutts") is not None
|
already_installed = importlib.util.find_spec("neutts") is not None
|
||||||
except Exception:
|
except Exception:
|
||||||
already_installed = False
|
already_installed = False
|
||||||
|
|
|
||||||
|
|
@ -1186,7 +1186,6 @@ def _configure_simple_requirements(ts_key: str):
|
||||||
if api_key and api_key.strip():
|
if api_key and api_key.strip():
|
||||||
save_env_value("OPENAI_API_KEY", api_key.strip())
|
save_env_value("OPENAI_API_KEY", api_key.strip())
|
||||||
# Save vision base URL to config (not .env — only secrets go there)
|
# Save vision base URL to config (not .env — only secrets go there)
|
||||||
from hermes_cli.config import load_config, save_config
|
|
||||||
_cfg = load_config()
|
_cfg = load_config()
|
||||||
_aux = _cfg.setdefault("auxiliary", {}).setdefault("vision", {})
|
_aux = _cfg.setdefault("auxiliary", {}).setdefault("vision", {})
|
||||||
_aux["base_url"] = base_url
|
_aux["base_url"] = base_url
|
||||||
|
|
|
||||||
17
run_agent.py
17
run_agent.py
|
|
@ -1453,11 +1453,10 @@ class AIAgent:
|
||||||
if _mp and _mp.is_available():
|
if _mp and _mp.is_available():
|
||||||
self._memory_manager.add_provider(_mp)
|
self._memory_manager.add_provider(_mp)
|
||||||
if self._memory_manager.providers:
|
if self._memory_manager.providers:
|
||||||
from hermes_constants import get_hermes_home as _ghh
|
|
||||||
_init_kwargs = {
|
_init_kwargs = {
|
||||||
"session_id": self.session_id,
|
"session_id": self.session_id,
|
||||||
"platform": platform or "cli",
|
"platform": platform or "cli",
|
||||||
"hermes_home": str(_ghh()),
|
"hermes_home": str(get_hermes_home()),
|
||||||
"agent_context": "primary",
|
"agent_context": "primary",
|
||||||
}
|
}
|
||||||
# Thread session title for memory provider scoping
|
# Thread session title for memory provider scoping
|
||||||
|
|
@ -2777,10 +2776,10 @@ class AIAgent:
|
||||||
prompt = self._SKILL_REVIEW_PROMPT
|
prompt = self._SKILL_REVIEW_PROMPT
|
||||||
|
|
||||||
def _run_review():
|
def _run_review():
|
||||||
import contextlib, os as _os
|
import contextlib
|
||||||
review_agent = None
|
review_agent = None
|
||||||
try:
|
try:
|
||||||
with open(_os.devnull, "w") as _devnull, \
|
with open(os.devnull, "w") as _devnull, \
|
||||||
contextlib.redirect_stdout(_devnull), \
|
contextlib.redirect_stdout(_devnull), \
|
||||||
contextlib.redirect_stderr(_devnull):
|
contextlib.redirect_stderr(_devnull):
|
||||||
review_agent = AIAgent(
|
review_agent = AIAgent(
|
||||||
|
|
@ -3852,14 +3851,12 @@ class AIAgent:
|
||||||
|
|
||||||
# 2. Clean terminal sandbox environments
|
# 2. Clean terminal sandbox environments
|
||||||
try:
|
try:
|
||||||
from tools.terminal_tool import cleanup_vm
|
|
||||||
cleanup_vm(task_id)
|
cleanup_vm(task_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 3. Clean browser daemon sessions
|
# 3. Clean browser daemon sessions
|
||||||
try:
|
try:
|
||||||
from tools.browser_tool import cleanup_browser
|
|
||||||
cleanup_browser(task_id)
|
cleanup_browser(task_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -7777,8 +7774,7 @@ class AIAgent:
|
||||||
# the tool returns True on the next poll.
|
# the tool returns True on the next poll.
|
||||||
if self._interrupt_requested:
|
if self._interrupt_requested:
|
||||||
try:
|
try:
|
||||||
from tools.interrupt import set_interrupt as _sif
|
_set_interrupt(True, _worker_tid)
|
||||||
_sif(True, _worker_tid)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# Set the activity callback on THIS worker thread so
|
# Set the activity callback on THIS worker thread so
|
||||||
|
|
@ -7809,8 +7805,7 @@ class AIAgent:
|
||||||
with self._tool_worker_threads_lock:
|
with self._tool_worker_threads_lock:
|
||||||
self._tool_worker_threads.discard(_worker_tid)
|
self._tool_worker_threads.discard(_worker_tid)
|
||||||
try:
|
try:
|
||||||
from tools.interrupt import set_interrupt as _sif
|
_set_interrupt(False, _worker_tid)
|
||||||
_sif(False, _worker_tid)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -11864,7 +11859,7 @@ def main(
|
||||||
|
|
||||||
# Handle tool listing
|
# Handle tool listing
|
||||||
if list_tools:
|
if list_tools:
|
||||||
from model_tools import get_all_tool_names, get_toolset_for_tool, get_available_toolsets
|
from model_tools import get_all_tool_names, get_available_toolsets
|
||||||
from toolsets import get_all_toolsets, get_toolset_info
|
from toolsets import get_all_toolsets, get_toolset_info
|
||||||
|
|
||||||
print("📋 Available Tools & Toolsets:")
|
print("📋 Available Tools & Toolsets:")
|
||||||
|
|
|
||||||
|
|
@ -543,7 +543,6 @@ def camofox_vision(question: str, annotate: bool = False,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from hermes_cli.config import load_config
|
|
||||||
_cfg = load_config()
|
_cfg = load_config()
|
||||||
_vision_cfg = _cfg.get("auxiliary", {}).get("vision", {})
|
_vision_cfg = _cfg.get("auxiliary", {}).get("vision", {})
|
||||||
_vision_timeout = float(_vision_cfg.get("timeout", 120))
|
_vision_timeout = float(_vision_cfg.get("timeout", 120))
|
||||||
|
|
|
||||||
|
|
@ -742,7 +742,7 @@ def check_image_generation_requirements() -> bool:
|
||||||
try:
|
try:
|
||||||
if not check_fal_api_key():
|
if not check_fal_api_key():
|
||||||
return False
|
return False
|
||||||
import fal_client # noqa: F401 — SDK presence check
|
fal_client # noqa: F401 — SDK presence check
|
||||||
return True
|
return True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,6 @@ def _handle_send(args):
|
||||||
# Weixin can be configured purely via .env; synthesize a pconfig so
|
# Weixin can be configured purely via .env; synthesize a pconfig so
|
||||||
# send_message and cron delivery work without a gateway.yaml entry.
|
# send_message and cron delivery work without a gateway.yaml entry.
|
||||||
if platform_name == "weixin":
|
if platform_name == "weixin":
|
||||||
import os
|
|
||||||
wx_token = os.getenv("WEIXIN_TOKEN", "").strip()
|
wx_token = os.getenv("WEIXIN_TOKEN", "").strip()
|
||||||
wx_account = os.getenv("WEIXIN_ACCOUNT_ID", "").strip()
|
wx_account = os.getenv("WEIXIN_ACCOUNT_ID", "").strip()
|
||||||
if wx_token and wx_account:
|
if wx_token and wx_account:
|
||||||
|
|
@ -254,7 +253,6 @@ def _handle_send(args):
|
||||||
if not chat_id:
|
if not chat_id:
|
||||||
home = config.get_home_channel(platform)
|
home = config.get_home_channel(platform)
|
||||||
if not home and platform_name == "weixin":
|
if not home and platform_name == "weixin":
|
||||||
import os
|
|
||||||
wx_home = os.getenv("WEIXIN_HOME_CHANNEL", "").strip()
|
wx_home = os.getenv("WEIXIN_HOME_CHANNEL", "").strip()
|
||||||
if wx_home:
|
if wx_home:
|
||||||
from gateway.config import HomeChannel
|
from gateway.config import HomeChannel
|
||||||
|
|
|
||||||
|
|
@ -975,8 +975,7 @@ def skill_view(name: str, file_path: str = None, task_id: str = None) -> str:
|
||||||
_warnings.append(f"skill file is outside the trusted skills directory (~/.hermes/skills/): {skill_md}")
|
_warnings.append(f"skill file is outside the trusted skills directory (~/.hermes/skills/): {skill_md}")
|
||||||
if _injection_detected:
|
if _injection_detected:
|
||||||
_warnings.append("skill content contains patterns that may indicate prompt injection")
|
_warnings.append("skill content contains patterns that may indicate prompt injection")
|
||||||
import logging as _logging
|
logging.getLogger(__name__).warning("Skill security warning for '%s': %s", name, "; ".join(_warnings))
|
||||||
_logging.getLogger(__name__).warning("Skill security warning for '%s': %s", name, "; ".join(_warnings))
|
|
||||||
|
|
||||||
parsed_frontmatter: Dict[str, Any] = {}
|
parsed_frontmatter: Dict[str, Any] = {}
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1608,7 +1608,6 @@ def _(rid, params: dict) -> dict:
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
try:
|
try:
|
||||||
from datetime import datetime
|
|
||||||
from hermes_cli.clipboard import has_clipboard_image, save_clipboard_image
|
from hermes_cli.clipboard import has_clipboard_image, save_clipboard_image
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return _err(rid, 5027, f"clipboard unavailable: {e}")
|
return _err(rid, 5027, f"clipboard unavailable: {e}")
|
||||||
|
|
@ -2687,7 +2686,6 @@ def _(rid, params: dict) -> dict:
|
||||||
def _(rid, params: dict) -> dict:
|
def _(rid, params: dict) -> dict:
|
||||||
days = params.get("days", 30)
|
days = params.get("days", 30)
|
||||||
try:
|
try:
|
||||||
import time
|
|
||||||
cutoff = time.time() - days * 86400
|
cutoff = time.time() - days * 86400
|
||||||
rows = [s for s in _get_db().list_sessions_rich(limit=500) if (s.get("started_at") or 0) >= cutoff]
|
rows = [s for s in _get_db().list_sessions_rich(limit=500) if (s.get("started_at") or 0) >= cutoff]
|
||||||
return _ok(rid, {"days": days, "sessions": len(rows), "messages": sum(s.get("message_count", 0) for s in rows)})
|
return _ok(rid, {"days": days, "sessions": len(rows), "messages": sum(s.get("message_count", 0) for s in rows)})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue