merge: upgrade to upstream v0.50.95 + keep custom additions
Upstream v0.50.95 features merged (Russian localization, slash commands, mic toggle fix, gateway sync fix, KaTeX/Prism.js, etc.) Custom additions preserved: - Tier-2 agent switching commands in commands.js - MC panel in index.html + MC CSS - _resolve_cli_toolsets() in config.py - Custom routes.py, server.py, boot.js, i18n.js, messages.js, workspace.js Files with conflict resolution (took upstream, custom code in other files): - CHANGELOG.md, config.py, commands.js, index.html, panels.js, style.css, ui.js
This commit is contained in:
173
api/routes.py
173
api/routes.py
@@ -53,6 +53,7 @@ from api.helpers import (
|
||||
redact_session_data,
|
||||
_redact_text,
|
||||
)
|
||||
from api import mc as _mc
|
||||
|
||||
# ── CSRF: validate Origin/Referer on POST ────────────────────────────────────
|
||||
import re as _re
|
||||
@@ -755,6 +756,20 @@ def handle_get(handler, parsed) -> bool:
|
||||
if parsed.path == "/api/memory":
|
||||
return _handle_memory_read(handler)
|
||||
|
||||
# ── Mission Control (GET) ──
|
||||
if parsed.path == "/api/mc/status":
|
||||
return j(handler, _mc.get_dashboard_status())
|
||||
|
||||
if parsed.path == "/api/mc/priorities":
|
||||
return j(handler, {"priorities": _mc.get_priorities()})
|
||||
|
||||
if parsed.path == "/api/mc/tasks":
|
||||
return j(handler, {"tasks": _mc.get_tasks()})
|
||||
|
||||
if parsed.path == "/api/mc/feed":
|
||||
limit = int(parse_qs(parsed.query).get("limit", ["50"])[0])
|
||||
return j(handler, {"feed": _mc.get_feed(limit=limit)})
|
||||
|
||||
# ── Profile API (GET) ──
|
||||
if parsed.path == "/api/profiles":
|
||||
from api.profiles import list_profiles_api, get_active_profile_name
|
||||
@@ -772,6 +787,52 @@ def handle_get(handler, parsed) -> bool:
|
||||
{"name": get_active_profile_name(), "path": str(get_active_hermes_home())},
|
||||
)
|
||||
|
||||
# ── Commands API (dynamic from Hermes COMMAND_REGISTRY) ──
|
||||
if parsed.path == "/api/commands":
|
||||
import sys as _sys
|
||||
from pathlib import Path as _P
|
||||
|
||||
# Add hermes-agent to path so we can import the registry
|
||||
_agent_path = (_P(__file__).parent.parent / "hermes-agent").resolve()
|
||||
if str(_agent_path) not in _sys.path:
|
||||
_sys.path.insert(0, str(_agent_path))
|
||||
|
||||
try:
|
||||
from hermes_cli.commands import COMMAND_REGISTRY
|
||||
|
||||
# Map Hermes category names to WebUI category labels
|
||||
_cat_map = {
|
||||
"Session": "Session",
|
||||
"Config": "Configuration",
|
||||
"Tool": "Tools & Skills",
|
||||
"Skill": "Tools & Skills",
|
||||
"Info": "Info",
|
||||
"Agent": "Agents",
|
||||
"System": "System",
|
||||
}
|
||||
|
||||
_by_cat: dict[str, list] = {}
|
||||
for cmd in COMMAND_REGISTRY:
|
||||
if getattr(cmd, "cli_only", False):
|
||||
continue # skip CLI-only commands
|
||||
cat = _cat_map.get(cmd.category, "Info")
|
||||
if cat not in _by_cat:
|
||||
_by_cat[cat] = []
|
||||
_by_cat[cat].append({
|
||||
"name": cmd.name,
|
||||
"desc": cmd.description,
|
||||
"arg": getattr(cmd, "args_hint", None) or "(none)",
|
||||
"aliases": list(getattr(cmd, "aliases", []) or []),
|
||||
})
|
||||
|
||||
# Always include Agents section even if empty (Tier-2 agents are added client-side)
|
||||
if "Agents" not in _by_cat:
|
||||
_by_cat["Agents"] = []
|
||||
|
||||
return j(handler, {"categories": _by_cat})
|
||||
except Exception as e:
|
||||
return j(handler, {"error": str(e)}, status=500)
|
||||
|
||||
return False # 404
|
||||
|
||||
|
||||
@@ -1047,6 +1108,67 @@ def handle_post(handler, parsed) -> bool:
|
||||
if parsed.path == "/api/memory/write":
|
||||
return _handle_memory_write(handler, body)
|
||||
|
||||
# ── Mission Control (POST) ──
|
||||
if parsed.path == "/api/mc/priority/create":
|
||||
name = body.get("name", "").strip()
|
||||
if not name:
|
||||
return bad(handler, "name is required")
|
||||
priority = _mc.create_priority(name, body.get("color", "#808080"))
|
||||
return j(handler, {"ok": True, "priority": priority})
|
||||
|
||||
if parsed.path == "/api/mc/priority/update":
|
||||
pid = body.get("id")
|
||||
if not pid:
|
||||
return bad(handler, "id is required")
|
||||
updated = _mc.update_priority(
|
||||
pid,
|
||||
name=body.get("name"),
|
||||
color=body.get("color"),
|
||||
done=body.get("done"),
|
||||
)
|
||||
if not updated:
|
||||
return bad(handler, "priority not found", 404)
|
||||
return j(handler, {"ok": True, "priority": updated})
|
||||
|
||||
if parsed.path == "/api/mc/priority/delete":
|
||||
pid = body.get("id")
|
||||
if not pid:
|
||||
return bad(handler, "id is required")
|
||||
deleted = _mc.delete_priority(pid)
|
||||
return j(handler, {"ok": deleted})
|
||||
|
||||
if parsed.path == "/api/mc/task/create":
|
||||
title = body.get("title", "").strip()
|
||||
if not title:
|
||||
return bad(handler, "title is required")
|
||||
task = _mc.create_task(
|
||||
title,
|
||||
priority=body.get("priority", 1),
|
||||
status=body.get("status", "backlog"),
|
||||
)
|
||||
return j(handler, {"ok": True, "task": task})
|
||||
|
||||
if parsed.path == "/api/mc/task/update":
|
||||
tid = body.get("id")
|
||||
if not tid:
|
||||
return bad(handler, "id is required")
|
||||
updated = _mc.update_task(
|
||||
tid,
|
||||
title=body.get("title"),
|
||||
priority=body.get("priority"),
|
||||
status=body.get("status"),
|
||||
)
|
||||
if not updated:
|
||||
return bad(handler, "task not found", 404)
|
||||
return j(handler, {"ok": True, "task": updated})
|
||||
|
||||
if parsed.path == "/api/mc/task/delete":
|
||||
tid = body.get("id")
|
||||
if not tid:
|
||||
return bad(handler, "id is required")
|
||||
deleted = _mc.delete_task(tid)
|
||||
return j(handler, {"ok": deleted})
|
||||
|
||||
# ── Profile API (POST) ──
|
||||
if parsed.path == "/api/profile/switch":
|
||||
name = body.get("name", "").strip()
|
||||
@@ -1113,6 +1235,57 @@ def handle_post(handler, parsed) -> bool:
|
||||
except RuntimeError as e:
|
||||
return bad(handler, str(e), 409)
|
||||
|
||||
# ── Gateway API ──
|
||||
if parsed.path == "/api/gateways":
|
||||
# GET - list all gateways
|
||||
from api.gateways import list_gateways_api
|
||||
return j(handler, {"gateways": list_gateways_api()})
|
||||
|
||||
if parsed.path == "/api/gateway/start":
|
||||
name = body.get("name", "").strip()
|
||||
if not name:
|
||||
return bad(handler, "name is required")
|
||||
from api.gateways import start_gateway_api
|
||||
try:
|
||||
result = start_gateway_api(name)
|
||||
return j(handler, result)
|
||||
except (ValueError, RuntimeError) as e:
|
||||
return bad(handler, str(e))
|
||||
|
||||
if parsed.path == "/api/gateway/stop":
|
||||
name = body.get("name", "").strip()
|
||||
if not name:
|
||||
return bad(handler, "name is required")
|
||||
from api.gateways import stop_gateway_api
|
||||
try:
|
||||
result = stop_gateway_api(name)
|
||||
return j(handler, result)
|
||||
except (ValueError, RuntimeError) as e:
|
||||
return bad(handler, str(e))
|
||||
|
||||
if parsed.path == "/api/gateway/restart":
|
||||
name = body.get("name", "").strip()
|
||||
if not name:
|
||||
return bad(handler, "name is required")
|
||||
from api.gateways import restart_gateway_api
|
||||
try:
|
||||
result = restart_gateway_api(name)
|
||||
return j(handler, result)
|
||||
except (ValueError, RuntimeError) as e:
|
||||
return bad(handler, str(e))
|
||||
|
||||
if parsed.path == "/api/gateway/add":
|
||||
name = body.get("name", "").strip()
|
||||
if not name:
|
||||
return bad(handler, "name is required")
|
||||
gw_type = body.get("type", "telegram").strip()
|
||||
from api.gateways import add_gateway_api
|
||||
try:
|
||||
result = add_gateway_api(name, gw_type)
|
||||
return j(handler, result)
|
||||
except (ValueError, RuntimeError, FileExistsError) as e:
|
||||
return bad(handler, str(e))
|
||||
|
||||
# ── Settings (POST) ──
|
||||
if parsed.path == "/api/settings":
|
||||
from api.auth import (
|
||||
|
||||
Reference in New Issue
Block a user