mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
fix(dashboard): keep memory.provider in the config schema so Desktop's dropdown survives (#63886)
The dashboard's dedicated memory-provider UI (4b184cbe5) excluded
memory.provider from /api/config/schema server-side. Desktop's settings
page builds its field list from that schema, so the Memory Provider
dropdown silently vanished from Desktop after v0.18.1.
- web_server.py: restore memory.provider as a select in _SCHEMA_OVERRIDES,
with options built from plugins.memory discovery (was a stale hardcoded
[builtin, honcho] list before the removal)
- plugins/memory: add list_memory_provider_names() — directory-scan-only
name listing, safe at module import time (no provider imports)
- web ConfigPage: hide memory.provider client-side instead — the Plugins
page owns the dedicated provider-switching UI there
- tests: schema contract (select present, category memory, builtin
sentinel) + invariant that every discoverable provider is selectable
This commit is contained in:
parent
b663d50a6a
commit
861d69c7bb
4 changed files with 73 additions and 2 deletions
|
|
@ -610,7 +610,30 @@ async def _token_auth_seam(request: Request, call_next):
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Manual overrides for fields that need select options or custom types
|
||||
def _memory_provider_options() -> List[str]:
|
||||
"""Discovered memory providers for the ``memory.provider`` select.
|
||||
|
||||
Directory-scan only (no provider imports), so it's safe at module import
|
||||
time. ``""`` (built-in) is always first; discovery failures degrade to the
|
||||
bundled defaults rather than dropping the field.
|
||||
"""
|
||||
options = ["", "builtin"]
|
||||
try:
|
||||
from plugins.memory import list_memory_provider_names
|
||||
|
||||
options.extend(list_memory_provider_names())
|
||||
except Exception:
|
||||
options.extend(["honcho"])
|
||||
# Dedupe, preserve order
|
||||
return list(dict.fromkeys(options))
|
||||
|
||||
|
||||
_SCHEMA_OVERRIDES: Dict[str, Dict[str, Any]] = {
|
||||
"memory.provider": {
|
||||
"type": "select",
|
||||
"description": "Memory provider plugin",
|
||||
"options": _memory_provider_options(),
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Default model (e.g. anthropic/claude-sonnet-4.6)",
|
||||
|
|
@ -780,7 +803,7 @@ def _build_schema_from_config(
|
|||
full_key = f"{prefix}.{key}" if prefix else key
|
||||
|
||||
# Skip internal / version keys
|
||||
if full_key in {"_config_version", "memory.provider"}:
|
||||
if full_key in {"_config_version"}:
|
||||
continue
|
||||
|
||||
# Category is the first path component for nested keys, or "general"
|
||||
|
|
|
|||
|
|
@ -143,6 +143,17 @@ def find_provider_dir(name: str) -> Optional[Path]:
|
|||
# Public API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def list_memory_provider_names() -> List[str]:
|
||||
"""Cheap name-only listing of discoverable memory providers.
|
||||
|
||||
Unlike :func:`discover_memory_providers`, this does NOT import provider
|
||||
modules or run availability checks — it's a directory scan only, safe to
|
||||
call at module-import time (e.g. when building the dashboard config
|
||||
schema).
|
||||
"""
|
||||
return sorted({name for name, _ in _iter_provider_dirs()})
|
||||
|
||||
|
||||
def discover_memory_providers() -> List[Tuple[str, str, bool]]:
|
||||
"""Scan bundled and user-installed directories for available providers.
|
||||
|
||||
|
|
|
|||
|
|
@ -3645,6 +3645,34 @@ class TestBuildSchemaFromConfig:
|
|||
assert "options" in entry
|
||||
assert "local" in entry["options"]
|
||||
|
||||
def test_memory_provider_field_present_as_select(self):
|
||||
"""memory.provider must stay in the config schema.
|
||||
|
||||
Desktop's settings page builds its field list from /api/config/schema —
|
||||
a key excluded here silently vanishes from Desktop's Memory section
|
||||
(regression: the dashboard's dedicated memory-provider UI excluded the
|
||||
key server-side, breaking Desktop's dropdown). The dashboard hides the
|
||||
field client-side instead.
|
||||
"""
|
||||
from hermes_cli.web_server import CONFIG_SCHEMA
|
||||
entry = CONFIG_SCHEMA["memory.provider"]
|
||||
assert entry["type"] == "select"
|
||||
assert entry["category"] == "memory"
|
||||
options = entry["options"]
|
||||
# Built-in sentinel first, plus at least one discovered provider.
|
||||
assert options[0] == ""
|
||||
assert "builtin" in options
|
||||
assert len(options) >= 3
|
||||
|
||||
def test_memory_provider_options_cover_discovered_providers(self):
|
||||
"""Every provider the /api/memory endpoint can activate is selectable."""
|
||||
from hermes_cli.web_server import CONFIG_SCHEMA
|
||||
from plugins.memory import list_memory_provider_names
|
||||
|
||||
options = set(CONFIG_SCHEMA["memory.provider"]["options"])
|
||||
missing = set(list_memory_provider_names()) - options
|
||||
assert missing == set(), f"discovered providers missing from schema options: {missing}"
|
||||
|
||||
def test_approvals_mode_options_match_config_values(self):
|
||||
"""approvals.mode select options must match the values accepted by config.py.
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,16 @@ export default function ConfigPage() {
|
|||
api
|
||||
.getSchema()
|
||||
.then((resp) => {
|
||||
setSchema(resp.fields as Record<string, Record<string, unknown>>);
|
||||
// memory.provider has a dedicated management UI on the Plugins page
|
||||
// (provider cards + guided setup/switch flow). Hide it from the
|
||||
// generic config form so the two surfaces don't fight; the schema
|
||||
// keeps the field for other consumers (Desktop settings).
|
||||
const fields = { ...resp.fields } as Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
>;
|
||||
delete fields["memory.provider"];
|
||||
setSchema(fields);
|
||||
setCategoryOrder(resp.category_order ?? []);
|
||||
})
|
||||
.catch(() => {});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue