From a182ddbf9f8cb817be2c96238fb7e845488dfc22 Mon Sep 17 00:00:00 2001 From: Erosika Date: Tue, 7 Jul 2026 11:52:25 -0400 Subject: [PATCH] feat(memory): per-field info tooltips + name the profile in the full-config modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fields can declare a longer 'info' text rendered as an (i) tooltip next to the label in both the panel and the modal; honcho uses it to spell out the session strategy, write frequency, and recall mode semantics. The modal description claimed 'the active profile' without saying which — show the active gateway profile name. --- .../memory/provider-config-modal.test.tsx | 5 +++++ .../settings/memory/provider-config-modal.tsx | 12 ++++++++---- .../memory/provider-config-panel.test.tsx | 5 +++++ .../settings/memory/provider-config-panel.tsx | 4 ++-- apps/desktop/src/types/hermes.ts | 1 + hermes_cli/web_server.py | 1 + plugins/memory/config_schema.py | 2 ++ plugins/memory/honcho/config_schema.py | 16 ++++++++++++++++ 8 files changed, 40 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx b/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx index bfa0a54c5711..afa6a82e75da 100644 --- a/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx +++ b/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx @@ -9,6 +9,11 @@ vi.mock('@/hermes', () => ({ saveMemoryProviderConfig: (provider: string, values: unknown) => saveMemoryProviderConfig(provider, values) })) +vi.mock('@/store/profile', async () => { + const { atom } = await import('nanostores') + return { $activeGatewayProfile: atom('default') } +}) + vi.mock('@/store/notifications', () => ({ notify: vi.fn(), notifyError: vi.fn() diff --git a/apps/desktop/src/app/settings/memory/provider-config-modal.tsx b/apps/desktop/src/app/settings/memory/provider-config-modal.tsx index 3be76ea29240..1370722a63d0 100644 --- a/apps/desktop/src/app/settings/memory/provider-config-modal.tsx +++ b/apps/desktop/src/app/settings/memory/provider-config-modal.tsx @@ -1,3 +1,4 @@ +import { useStore } from '@nanostores/react' import { useEffect, useState } from 'react' import { Button } from '@/components/ui/button' @@ -15,7 +16,9 @@ import { ExternalLink, Loader2, Save, SlidersHorizontal } from '@/lib/icons' import { notify, notifyError } from '@/store/notifications' import type { MemoryProviderConfig, MemoryProviderField } from '@/types/hermes' -import { FieldControl } from './field-control' +import { $activeGatewayProfile } from '@/store/profile' + +import { FieldControl, FieldTitle } from './field-control' import { ListRow } from '../primitives' // Secrets seed blank: values are write-only and blank keeps the stored one. @@ -51,6 +54,7 @@ export function ProviderConfigModal({ onOpenChange: (open: boolean) => void onSaved: () => Promise | void }) { + const activeProfile = useStore($activeGatewayProfile) const [values, setValues] = useState>({}) const [seeded, setSeeded] = useState>({}) const [saving, setSaving] = useState(false) @@ -87,8 +91,8 @@ export function ProviderConfigModal({ {config.label} — full configuration - Every {config.label} option for the active profile. Blank fields fall back to the resolved host or - built-in default. + Every {config.label} option for the {activeProfile} profile. Blank + fields fall back to the resolved host or built-in default. {config.docs_url && ( } description={field.description} - title={field.label} + title={} /> ))} diff --git a/apps/desktop/src/app/settings/memory/provider-config-panel.test.tsx b/apps/desktop/src/app/settings/memory/provider-config-panel.test.tsx index 74d5e4b28110..672afe52289e 100644 --- a/apps/desktop/src/app/settings/memory/provider-config-panel.test.tsx +++ b/apps/desktop/src/app/settings/memory/provider-config-panel.test.tsx @@ -14,6 +14,11 @@ vi.mock('@/hermes', () => ({ runMemoryProviderAction(provider, action, values) })) +vi.mock('@/store/profile', async () => { + const { atom } = await import('nanostores') + return { $activeGatewayProfile: atom('default') } +}) + vi.mock('@/store/notifications', () => ({ notify: vi.fn(), notifyError: vi.fn() diff --git a/apps/desktop/src/app/settings/memory/provider-config-panel.tsx b/apps/desktop/src/app/settings/memory/provider-config-panel.tsx index 9b34e6ba65ea..01d6e3d8ac6a 100644 --- a/apps/desktop/src/app/settings/memory/provider-config-panel.tsx +++ b/apps/desktop/src/app/settings/memory/provider-config-panel.tsx @@ -7,7 +7,7 @@ import { Loader2, Save, SlidersHorizontal } from '@/lib/icons' import { notify, notifyError } from '@/store/notifications' import type { MemoryProviderConfig } from '@/types/hermes' -import { FieldControl } from './field-control' +import { FieldControl, FieldTitle } from './field-control' import { ListRow, LoadingState, Pill } from '../primitives' import { ProviderConfigModal } from './provider-config-modal' @@ -145,7 +145,7 @@ export function ProviderConfigPanel({ provider }: { provider: string }) { /> } description={field.description} - title={field.label} + title={} /> ))} diff --git a/apps/desktop/src/types/hermes.ts b/apps/desktop/src/types/hermes.ts index 90e34f934060..bf9bb588bf96 100644 --- a/apps/desktop/src/types/hermes.ts +++ b/apps/desktop/src/types/hermes.ts @@ -131,6 +131,7 @@ export interface MemoryProviderFieldOption { export interface MemoryProviderField { description: string group: string + info?: string inline: boolean is_set: boolean key: string diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 0004f966ccbd..8fdd1c37734d 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -3891,6 +3891,7 @@ def _provider_field_entry(field: ProviderField) -> Dict[str, Any]: "label": field.label, "kind": field.kind, "description": field.description, + "info": field.info, "placeholder": field.placeholder, "inline": field.inline, "group": field.group, diff --git a/plugins/memory/config_schema.py b/plugins/memory/config_schema.py index c1cbe2177ba0..a06121a69741 100644 --- a/plugins/memory/config_schema.py +++ b/plugins/memory/config_schema.py @@ -78,6 +78,8 @@ class ProviderField: env_fallbacks: tuple[str, ...] = () inline: bool = False group: str = "" + # Longer help text surfaced as an info tooltip next to the field label. + info: str = "" # Host-block placement: "host" (per-profile) or "root"; flat-json ignores it. scope: str = "host" diff --git a/plugins/memory/honcho/config_schema.py b/plugins/memory/honcho/config_schema.py index fdb096824442..b5298e20be9e 100644 --- a/plugins/memory/honcho/config_schema.py +++ b/plugins/memory/honcho/config_schema.py @@ -100,6 +100,12 @@ CONFIG_SCHEMA = ProviderConfigSchema( kind=KIND_SELECT, default="per-directory", description="How conversations map to Honcho sessions.", + info=( + "Per session: every conversation gets its own Honcho session. " + "Per directory: conversations from the same working directory share one. " + "Per repo: conversations from the same git repo share one. " + "Global: everything shares a single session." + ), options=( ProviderFieldOption("per-session", "Per session"), ProviderFieldOption("per-directory", "Per directory"), @@ -181,6 +187,11 @@ CONFIG_SCHEMA = ProviderConfigSchema( kind=KIND_TEXT, default="async", description="When to flush messages: async, turn, session, or every N turns.", + info=( + "async: write in the background as messages arrive. " + "turn: flush after each turn. session: flush when the session ends. " + "A number N flushes every N turns." + ), placeholder="async | turn | session | N", group="Message writing", ), @@ -259,6 +270,11 @@ CONFIG_SCHEMA = ProviderConfigSchema( kind=KIND_SELECT, default="hybrid", description="How memory retrieval works: hybrid, context-only, or tools-only.", + info=( + "Hybrid: auto-injected context plus on-demand memory tools. " + "Context only: injection without tools. " + "Tools only: the model queries memory explicitly, nothing is injected." + ), options=( ProviderFieldOption("hybrid", "Hybrid"), ProviderFieldOption("context", "Context only"),