mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-28 18:19:28 +00:00
fix(desktop): feed memory.provider dropdown from live discovery
The desktop Settings memory-provider dropdown read a hardcoded `ENUM_OPTIONS['memory.provider'] = ['', 'honcho', 'hindsight']` list, so user-installed and pip-installed providers never appeared even though the backend already discovers them (`GET /api/memory` -> `_discover_memory_provider_statuses()`) and the CLI (`hermes memory setup`) lists them. This was the one surface left where the memory config stack was not schema/discovery-driven. Fetch `getMemoryStatus()` on the settings page (mirroring the existing `elevenLabsVoiceOptions` pattern) and pass the discovered provider names to `enumOptionsFor` as `dynamicOptions` for the `memory.provider` key. The static `ENUM_OPTIONS` entry is demoted to a pre-load fallback; the current-value passthrough still keeps a selected-but-undiscovered provider visible. Completes the desktop half of the schema-driven memory-provider config surface (the CLI + backend + generic panel already landed via #51020 / #67206), superseding the stale #48675 which built the same feature against the pre-refactor layout. Co-authored-by: brooklyn! <770929+OutThisLife@users.noreply.github.com>
This commit is contained in:
parent
60ec6a3b8e
commit
3493a6c73c
3 changed files with 51 additions and 3 deletions
|
|
@ -5,7 +5,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
|||
import { useSearchParams } from 'react-router-dom'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { getElevenLabsVoices, getHermesConfigSchema, saveHermesConfig } from '@/hermes'
|
||||
import { getElevenLabsVoices, getHermesConfigSchema, getMemoryStatus, saveHermesConfig } from '@/hermes'
|
||||
import { useI18n } from '@/i18n'
|
||||
import { $keepAwake, setKeepAwake } from '@/store/keep-awake'
|
||||
import { notify, notifyError } from '@/store/notifications'
|
||||
|
|
@ -76,6 +76,10 @@ export function ConfigSettings({
|
|||
const schema = schemaResponse?.fields ?? null
|
||||
const [elevenLabsVoiceOptions, setElevenLabsVoiceOptions] = useState<string[] | null>(null)
|
||||
const [elevenLabsVoiceLabels, setElevenLabsVoiceLabels] = useState<Record<string, string>>({})
|
||||
// Discovered memory providers (bundled + user-installed + pip), so the
|
||||
// memory.provider dropdown reflects what the backend actually serves rather
|
||||
// than a hardcoded subset. null until the first fetch resolves.
|
||||
const [memoryProviderOptions, setMemoryProviderOptions] = useState<string[] | null>(null)
|
||||
const saveVersionRef = useRef(0)
|
||||
const savedDiscoverySignatureRef = useRef<string | undefined>(undefined)
|
||||
const [saveVersion, setSaveVersion] = useState(0)
|
||||
|
|
@ -126,6 +130,27 @@ export function ConfigSettings({
|
|||
return () => void (cancelled = true)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
getMemoryStatus()
|
||||
.then(result => {
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
// Empty sentinel first (built-in only), then every discovered plugin.
|
||||
setMemoryProviderOptions(['', ...result.providers.map(provider => provider.name)])
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setMemoryProviderOptions(null)
|
||||
}
|
||||
})
|
||||
|
||||
return () => void (cancelled = true)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!config || saveVersion === 0) {
|
||||
return
|
||||
|
|
@ -308,7 +333,9 @@ export function ConfigSettings({
|
|||
enumOptions={
|
||||
key === 'tts.elevenlabs.voice_id'
|
||||
? enumOptionsFor(key, getNested(config, key), config, elevenLabsVoiceOptions ?? undefined)
|
||||
: enumOptionsFor(key, getNested(config, key), config)
|
||||
: key === 'memory.provider'
|
||||
? enumOptionsFor(key, getNested(config, key), config, memoryProviderOptions ?? undefined)
|
||||
: enumOptionsFor(key, getNested(config, key), config)
|
||||
}
|
||||
onChange={value => updateConfig(setNested(config, key, value))}
|
||||
optionLabels={key === 'tts.elevenlabs.voice_id' ? elevenLabsVoiceLabels : undefined}
|
||||
|
|
|
|||
|
|
@ -249,7 +249,9 @@ export const ENUM_OPTIONS: Record<string, string[]> = {
|
|||
'delegation.reasoning_effort': ['', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
||||
// Built-in memory is not a provider plugin: the empty sentinel renders as
|
||||
// "Built-in only" and a legacy literal `builtin` value is only kept visible
|
||||
// via enumOptionsFor's current-value passthrough (#49513).
|
||||
// via enumOptionsFor's current-value passthrough (#49513). This static list
|
||||
// is only a pre-load fallback: config-settings.tsx feeds enumOptionsFor the
|
||||
// live discovered set (getMemoryStatus) so user-installed/pip providers show.
|
||||
'memory.provider': ['', 'honcho', 'hindsight'],
|
||||
// Terminal execution backends — kept in sync with the dispatch ladder in
|
||||
// tools/terminal_tool.py::_create_environment (local/docker/singularity/
|
||||
|
|
|
|||
|
|
@ -44,6 +44,25 @@ describe('settings helpers', () => {
|
|||
expect(options).toEqual(['', 'honcho', 'hindsight', 'builtin'])
|
||||
})
|
||||
|
||||
it('prefers the discovered provider set over the static fallback for memory.provider', () => {
|
||||
// config-settings.tsx passes the live getMemoryStatus() providers as
|
||||
// dynamicOptions; user-installed/pip providers must appear, not just the
|
||||
// hardcoded honcho/hindsight subset.
|
||||
const discovered = ['', 'honcho', 'hindsight', 'mem0', 'supermemory']
|
||||
const options = enumOptionsFor('memory.provider', '', {}, discovered)
|
||||
|
||||
expect(options).toEqual(discovered)
|
||||
})
|
||||
|
||||
it('keeps the current memory.provider value visible even if discovery omits it', () => {
|
||||
// A provider selected in config but not (yet) returned by discovery must
|
||||
// still render as the current selection rather than silently vanishing.
|
||||
const discovered = ['', 'honcho']
|
||||
const options = enumOptionsFor('memory.provider', 'hindsight', {}, discovered)
|
||||
|
||||
expect(options).toEqual(['', 'honcho', 'hindsight'])
|
||||
})
|
||||
|
||||
describe('isExternalMemoryProvider', () => {
|
||||
it('treats only real plugin names as external providers', () => {
|
||||
expect(isExternalMemoryProvider('honcho')).toBe(true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue