refactor(web-profiles/status): drop dead sidebar cache_clear hook, compact literals and ladders

This commit is contained in:
Teknium
2026-09-02 22:28:11 -07:00
parent ba1df7dab1
commit a29a970054
3 changed files with 64 additions and 118 deletions
+23 -42
View File
@@ -177,10 +177,8 @@ def _profile_targets(log_label: str, *, lightweight: bool) -> List[Tuple[str, Pa
config/meta and probes gateways/skills per profile — too heavy per sidebar refresh."""
from hermes_cli import profiles as profiles_mod
try:
if lightweight:
targets = list(profiles_mod.profiles_to_serve(multiplex=True))
else:
targets = [(info.name, info.path) for info in profiles_mod.list_profiles()]
targets = (list(profiles_mod.profiles_to_serve(multiplex=True)) if lightweight
else [(info.name, info.path) for info in profiles_mod.list_profiles()])
except Exception:
_log.exception("%s: list_profiles failed", log_label)
targets = []
@@ -309,11 +307,6 @@ def _sidebar_singleflight_cache(func):
refresh_lock = threading.Lock()
miss = object()
def _key(args, kwargs):
bound = signature.bind(*args, **kwargs)
bound.apply_defaults()
return tuple(bound.arguments.items())
def _lookup(key):
now = time.monotonic()
with cache_lock:
@@ -330,7 +323,9 @@ def _sidebar_singleflight_cache(func):
if ttl <= 0:
return func(*args, **kwargs)
key = _key(args, kwargs)
bound = signature.bind(*args, **kwargs)
bound.apply_defaults()
key = tuple(bound.arguments.items())
cached = _lookup(key)
if cached is not miss:
return cached
@@ -359,11 +354,6 @@ def _sidebar_singleflight_cache(func):
cache.popitem(last=False)
return result
def cache_clear():
with cache_lock:
cache.clear()
wrapped.cache_clear = cache_clear
return wrapped
@@ -388,10 +378,8 @@ def get_profiles_sessions(
if order not in ("created", "recent"):
raise HTTPException(status_code=400, detail="order must be one of: created, recent")
if profile and profile != "all":
targets = [_cron_profile_home(profile)]
else:
targets = _profile_targets("GET /api/profiles/sessions", lightweight=True)
targets = ([_cron_profile_home(profile)] if profile and profile != "all"
else _profile_targets("GET /api/profiles/sessions", lightweight=True))
# Source scoping (see /api/sessions): recents pass exclude_sources=cron, the cron-jobs
# section source=cron — two independent lists so cron sessions can't starve recents.
@@ -594,11 +582,10 @@ def get_profiles_projects_tree(preview_limit: int = 3, session_limit: int = 2000
scoped_session_ids.extend(tree["scoped_session_ids"])
_read_profile_db(name, home, errors, _read)
return {
"projects": sorted(merged.values(), key=lambda p: p.get("lastActive") or 0, reverse=True),
# Ownership is per profile, so no project is "the active one" here; the desktop only
# reads active_id to bias its overview sort.
"active_id": None, "scoped_session_ids": scoped_session_ids, "errors": errors}
# active_id is None: ownership is per profile, so no project is "the active one" here.
projects = sorted(merged.values(), key=lambda p: p.get("lastActive") or 0, reverse=True)
return {"projects": projects, "active_id": None, "scoped_session_ids": scoped_session_ids,
"errors": errors}
# `gh pr create` prints the PR url and nothing else, so a tool result whose whole output IS a
@@ -690,14 +677,12 @@ async def create_profile_endpoint(body: ProfileCreate):
"Setting model for new profile %s failed", body.name,
fn=lambda: (_write_profile_model(path, provider, model), True)[1], default=False)
mcp_written = _best_effort(
"Writing MCP servers for new profile %s failed", body.name,
fn=lambda: _write_profile_mcp_servers(path, body.mcp_servers), default=0
) if body.mcp_servers else 0
"Writing MCP servers for new profile %s failed", body.name, default=0,
fn=lambda: _write_profile_mcp_servers(path, body.mcp_servers)) if body.mcp_servers else 0
# "keep" has replace semantics; skipped when empty (legacy: keep the bundle).
skills_disabled = _best_effort(
"Applying skill selection for new profile %s failed", body.name,
fn=lambda: _disable_unselected_skills(path, body.keep_skills), default=0
) if body.keep_skills else 0
"Applying skill selection for new profile %s failed", body.name, default=0,
fn=lambda: _disable_unselected_skills(path, body.keep_skills)) if body.keep_skills else 0
# Hub installs spawn async, scoped via `-p <name>` (a fresh subprocess re-binds
# skills_hub.SKILLS_DIR at import). PIDs go back for the UI to poll.
@@ -874,8 +859,8 @@ async def update_profile_description_endpoint(name: str, body: ProfileDescriptio
@router.put("/api/profiles/{name}/model")
async def update_profile_model_endpoint(name: str, body: ProfileModelUpdate):
"""Set the main model for a specific profile's config.yaml without touching the
dashboard's own profile — ``POST /api/model/set`` (main scope) via the HERMES_HOME override."""
"""Set the main model for a specific profile's config.yaml without touching the dashboard's
own profile — ``POST /api/model/set`` (main scope) via the HERMES_HOME override."""
profile_dir = _resolve_profile_dir(name)
provider = (body.provider or "").strip()
model = (body.model or "").strip()
@@ -903,12 +888,9 @@ async def describe_profile_auto_endpoint(name: str, body: ProfileDescribeAuto):
not_found=(), bad_request=()):
# A synchronous LLM round-trip with a 60 s ceiling; on the loop it stalls everything.
outcome = await run_in_threadpool(_run)
return {
"ok": bool(outcome.ok),
"reason": outcome.reason,
"description": outcome.description,
# A failed sweep leaves any existing description untouched: not auto-authored.
"description_auto": bool(outcome.ok)}
# description_auto mirrors ok: a failed sweep leaves any existing description untouched.
return {"ok": bool(outcome.ok), "reason": outcome.reason, "description": outcome.description,
"description_auto": bool(outcome.ok)}
# ── Export / Import ── wraps hermes_cli.profiles.export_profile / import_profile. Paths are
@@ -954,10 +936,9 @@ async def import_profile_endpoint(body: ProfileImport):
imported = profile_dir.name
# Match the CLI import flow: create the wrapper alias when it's safe.
def _wrapper():
if not profiles_mod.check_alias_collision(imported):
profiles_mod.create_wrapper_script(imported)
_best_effort("Creating wrapper for imported profile %s failed", imported, fn=_wrapper)
_best_effort("Creating wrapper for imported profile %s failed", imported,
fn=lambda: (profiles_mod.check_alias_collision(imported)
or profiles_mod.create_wrapper_script(imported)))
# Bundled desktop appearance overlay, so the desktop needn't make another round-trip.
desktop_overlay = None
+34 -65
View File
@@ -58,6 +58,14 @@ _GATEWAY_HEALTH_ROUTE_TIMEOUT = 1.0
_HEALTHY_PLATFORM_STATES = {"connected", "running", "ok"}
def _safe_call(mod, fn_name: str, default):
try:
fn = getattr(mod, fn_name, None)
return fn() if callable(fn) else default
except Exception:
return default
def _count_status_active_sessions() -> int:
"""Best-effort status garnish. Opens read-only (via the shared stale-schema heal) so
/api/status never routinely writes to state.db while another Hermes process uses it."""
@@ -95,20 +103,15 @@ async def get_ssh_ownership(request: Request):
_require_token(request)
if not _SSH_OWNER_NONCE:
raise HTTPException(status_code=404, detail="SSH ownership is not active")
return {
"ok": True,
"sshOwnerNonce": _SSH_OWNER_NONCE,
"protocolVersion": 1,
"runtimeIntact": _ssh_runtime_intact()}
return {"ok": True, "sshOwnerNonce": _SSH_OWNER_NONCE, "protocolVersion": 1,
"runtimeIntact": _ssh_runtime_intact()}
@router.get("/api/health")
async def get_health():
"""Lightweight process liveness for desktop/backend readiness probes."""
return {
"ok": True,
"version": __version__,
"auth_required": bool(getattr(app.state, "auth_required", False))}
return {"ok": True, "version": __version__,
"auth_required": bool(getattr(app.state, "auth_required", False))}
# Profile segment mirrors hermes_cli.profiles._PROFILE_ID_RE. Platform segment mirrors the
@@ -197,10 +200,8 @@ def _project_gateway_platforms(gateway_platforms: dict, configured: "set[str] |
entries are the diagnosis (credential collisions, auth failures) that the single
exit_reason string can't express, so they are kept — upstream writer-identity/freshness
filtering already dropped other processes' entries."""
platforms = {
key: _public_platform_entry(value)
for key, value in gateway_platforms.items()
if _status_platform_key_allowed(key, configured)}
platforms = {key: _public_platform_entry(value) for key, value in gateway_platforms.items()
if _status_platform_key_allowed(key, configured)}
if gateway_running:
return platforms
if gateway_state == "startup_failed":
@@ -326,15 +327,11 @@ async def _component_health(gateway: Dict[str, Any]) -> Dict[str, Any]:
components["storage"] = {"status": storage_check.get("status", "degraded")}
except Exception:
components["storage"] = {"status": "degraded"}
platform_states = [
str(value.get("state") or value.get("status") or "").lower()
for value in gateway_platforms.values()
if isinstance(value, dict)]
platform_states = [str(value.get("state") or value.get("status") or "").lower()
for value in gateway_platforms.values() if isinstance(value, dict)]
connected = sum(1 for state in platform_states if state in _HEALTHY_PLATFORM_STATES)
components["platforms"] = {
"status": "ok" if connected == len(platform_states) else "degraded",
"configured": len(gateway_platforms),
"connected": connected}
components["platforms"] = {"status": "ok" if connected == len(platform_states) else "degraded",
"configured": len(gateway_platforms), "connected": connected}
return components
@@ -411,13 +408,10 @@ async def get_status(profile: Optional[str] = None):
auth = _auth_gate_status()
status = {
"version": __version__,
"release_date": __release_date__,
"config_version": current_ver,
"latest_config_version": latest_ver,
"version": __version__, "release_date": __release_date__,
"config_version": current_ver, "latest_config_version": latest_ver,
"can_update_hermes": not _dashboard_local_update_managed_externally(),
"gateway_running": gateway_running,
"gateway_state": gateway_state,
"gateway_running": gateway_running, "gateway_state": gateway_state,
"gateway_platforms": gateway["gateway_platforms"],
"gateway_exit_reason": gateway["gateway_exit_reason"],
"gateway_updated_at": gateway["gateway_updated_at"],
@@ -427,10 +421,8 @@ async def get_status(profile: Optional[str] = None):
active_agents=active_agents),
"gateway_drainable": derive_gateway_drainable(
gateway_running=gateway_running, gateway_state=gateway_state),
"restart_drain_timeout": restart_drain_timeout,
"active_sessions": active_sessions,
**auth,
"nous_session_valid": _nous_session_validity()}
"restart_drain_timeout": restart_drain_timeout, "active_sessions": active_sessions,
**auth, "nous_session_valid": _nous_session_validity()}
# Stable per-install identity (first call may touch disk). Omitted (not null) when
# unpersistable so older-client behavior and the no-identity fallback stay identical.
@@ -455,12 +447,9 @@ async def get_status(profile: Optional[str] = None):
# reaches this endpoint — surface them only on a loopback / ``--insecure`` bind.
if not auth["auth_required"]:
status.update({
"hermes_home": str(get_hermes_home()),
"config_path": str(get_config_path()),
"env_path": str(get_env_path()),
"gateway_pid": gateway["gateway_pid"],
"gateway_health_url": _GATEWAY_HEALTH_URL,
"gateways": topology["gateways"]})
"hermes_home": str(get_hermes_home()), "config_path": str(get_config_path()),
"env_path": str(get_env_path()), "gateway_pid": gateway["gateway_pid"],
"gateway_health_url": _GATEWAY_HEALTH_URL, "gateways": topology["gateways"]})
return status
finally:
@@ -478,12 +467,10 @@ async def get_system_stats():
**_display_system_platform(
system=_platform.system(), release=_platform.release(), version=_platform.version(),
platform_label=_platform.platform()),
"arch": _platform.machine(),
"hostname": _platform.node(),
"arch": _platform.machine(), "hostname": _platform.node(),
"python_version": _platform.python_version(),
"python_impl": _platform.python_implementation(),
"hermes_version": __version__,
"cpu_count": os.cpu_count()}
"hermes_version": __version__, "cpu_count": os.cpu_count()}
def _disk():
du = psutil.disk_usage(str(get_hermes_home()))
@@ -536,10 +523,7 @@ async def get_curator_status():
from agent import curator
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Curator unavailable: {exc}")
try:
state = curator.load_state()
except Exception:
state = {}
state = _safe_call(curator, "load_state", {})
return {
"enabled": _safe_call(curator, "is_enabled", True),
"paused": _safe_call(curator, "is_paused", False),
@@ -619,14 +603,6 @@ async def update_learning_node(body: LearningNodeEdit):
body.profile, lambda: edit_node(body.id, body.content), 400, "edit failed")
def _safe_call(mod, fn_name: str, default):
try:
fn = getattr(mod, fn_name, None)
return fn() if callable(fn) else default
except Exception:
return default
# Portal — Nous Portal auth + Tool Gateway routing status (read-only).
@@ -667,8 +643,7 @@ def _get_portal_status_sync():
model_cfg = cfg.get("model") if isinstance(cfg.get("model"), dict) else {}
return {
"logged_in": bool(auth.get("logged_in")),
"portal_url": auth.get("portal_base_url"),
"logged_in": bool(auth.get("logged_in")), "portal_url": auth.get("portal_base_url"),
"inference_url": auth.get("inference_base_url"),
"provider": str((model_cfg or {}).get("provider") or ""),
"subscription_url": "https://portal.nousresearch.com/manage-subscription",
@@ -710,12 +685,8 @@ async def run_debug_share_endpoint(body: DebugShareRequest | None = None):
_log.exception("debug share failed")
raise HTTPException(status_code=500, detail=f"Failed: {exc}")
return {
"ok": True,
"urls": result.urls,
"failures": result.failures,
"redacted": result.redacted,
"auto_delete_seconds": result.auto_delete_seconds}
return {"ok": True, "urls": result.urls, "failures": result.failures,
"redacted": result.redacted, "auto_delete_seconds": result.auto_delete_seconds}
@logs_router.get("/api/logs")
@@ -741,10 +712,8 @@ async def get_logs(
if component and component.lower() != "all":
comp_prefixes = COMPONENT_PREFIXES.get(component)
if comp_prefixes is None:
raise HTTPException(
status_code=400,
detail=f"Unknown component: {component}. "
f"Available: {', '.join(sorted(COMPONENT_PREFIXES))}")
raise HTTPException(status_code=400, detail=f"Unknown component: {component}. "
f"Available: {', '.join(sorted(COMPONENT_PREFIXES))}")
result = _read_tail(
log_path, min(lines, 500) if not search else 2000,
has_filters=bool(min_level or comp_prefixes or search),
+7 -11
View File
@@ -106,13 +106,11 @@ def _parse_model_ids(resp: "Any") -> List[str]:
def _fallback_profile_entry(profiles_mod, name: str, home: Path, *, is_default: bool,
has_env: bool, gateway_running: Callable[[], bool]) -> Dict[str, Any]:
model, provider = _safe(lambda: profiles_mod._read_config_model(home), (None, None))
def meta(key, default):
return _safe(lambda: profiles_mod.read_profile_meta(home).get(key, default), default)
meta = lambda key, default: _safe( # noqa: E731
lambda: profiles_mod.read_profile_meta(home).get(key, default), default)
return {
"name": name, "path": str(home), "is_default": is_default,
"model": model, "provider": provider, "has_env": has_env,
"name": name, "path": str(home), "is_default": is_default, "model": model,
"provider": provider, "has_env": has_env,
"skill_count": _safe(lambda: profiles_mod._count_skills(home), 0),
"gateway_running": _safe(gateway_running, False),
"description": meta("description", ""), "description_auto": meta("description_auto", False),
@@ -386,10 +384,8 @@ def _installed_hub_identifiers(profile: Optional[str] = None) -> dict:
else:
profile_dir = _resolve_profile_dir(profile.strip())
lock = HubLockFile(profile_dir / "skills" / ".hub" / "lock.json")
return {
entry["identifier"]: {"name": entry.get("name"),
"trust_level": entry.get("trust_level"),
"scan_verdict": entry.get("scan_verdict")}
for entry in lock.list_installed() if entry.get("identifier")}
keys = ("name", "trust_level", "scan_verdict")
return {entry["identifier"]: {k: entry.get(k) for k in keys}
for entry in lock.list_installed() if entry.get("identifier")}
except Exception:
return {}