NousResearch/hermes-agent

Model visibility dialog hide-all preservation bug fixes

Cluster 43485. Data as of

Nothing open on GitHub. Fix merged in #43496 on 11 Jun 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #43496. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #43496. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and pull requests concern a bug in the desktop model visibility dialog where toggling off the last model in a provider group incorrectly re-enables all models, along with multiple fix attempts to preserve explicit hide-all states for providers.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #43496 fix(desktop): preserve explicit hide-all choice in model visibility dialog

    merged to main

    files: apps/desktop/src/components/model-visibility-dialog.tsx, apps/desktop/src/store/model-visibility.test.ts, apps/desktop/src/store/model-visibility.ts

    Show the diff
    • apps/desktop/src/components/model-visibility-dialog.tsx
      @@ -14,6 +14,8 @@ import {
         $visibleModels,
         collapseModelFamilies,
         effectiveVisibleKeys,
      +  emptyProviderSentinelKey,
      +  isProviderSentinel,
         modelVisibilityKey,
         setVisibleModels
       } from '@/store/model-visibility'
      @@ -61,10 +63,21 @@ export function ModelVisibilityDialog({
         const toggle = (provider: ModelOptionProvider, model: string) => {
           const next = new Set(effectiveVisibleKeys($visibleModels.get(), providers))
           const key = modelVisibilityKey(provider.slug, model)
      +    const sentinel = emptyProviderSentinelKey(provider.slug)
       
           if (next.has(key)) {
             next.delete(key)
      +
      +      // Check if this was the last real model for this provider.
      +      const remainingForProvider = [...next].some(
      +        k => k.startsWith(`${provider.slug}::`) && !isProviderSentinel(k)
      +      )
      +
      +      if (!remainingForProvider) {
      +        next.add(sentinel)
      +      }
           } else {
      +      next.delete(sentinel)
             next.add(key)
           }
       
    • apps/desktop/src/store/model-visibility.test.ts
      @@ -2,7 +2,12 @@ import { describe, expect, it } from 'vitest'
       
       import type { ModelOptionProvider } from '@/types/hermes'
       
      -import { effectiveVisibleKeys, modelVisibilityKey } from './model-visibility'
      +import {
      +  effectiveVisibleKeys,
      +  emptyProviderSentinelKey,
      +  isProviderSentinel,
      +  modelVisibilityKey
      +} from './model-visibility'
       
       const provider = (slug: string, models: string[]): ModelOptionProvider => ({
         models,
      @@ -34,4 +39,48 @@ describe('model visibility', () => {
           expect(visible.has(modelVisibilityKey('local-ollama', 'qwen3:latest'))).toBe(true)
           expect(visible.has(modelVisibilityKey('local-ollama', 'llama3.2:latest'))).toBe(false)
         })
      +
      +  it('preserves hidden-provider sentinel without re-adding defaults', () => {
      +    // User explicitly hid all models for "nous" — sentinel marks this choice.
      +    const stored = new Set([emptyProviderSentinelKey('nous')])
      +
      +    const visible = effectiveVisibleKeys(stored, [
      +      provider('nous', ['hermes-3-llama-3.1-70b', 'hermes-3-llama-3.1-8b']),
      +      provider('ollama', ['qwen3:latest'])
      +    ])
      +
      +    expect(visible.has(modelVisibilityKey('nous', 'hermes-3-llama-3.1-70b'))).toBe(false)
      +    expect(visible.has(modelVisibilityKey('nous', 'hermes-3-llama-3.1-8b'))).toBe(false)
      +    // Sentinel itself is stripped from the result.
      +    expect(visible.has(emptyProviderSentinelKey('nous'))).toBe(false)
      +    // Other providers still get defaults.
      +    expect(visible.has(modelVisibilityKey('ollama', 'qwen3:latest'))).toBe(true)
      +  })
      +
      +  it('restores model when toggling on after hiding all', () => {
      +    // Simulates: user hid all "nous" models, then toggles one back on.
      +    const stored = new Set([
      +      emptyProviderSentinelKey('nous'),
      +      modelVisibilityKey('ollama', 'qwen3:latest')
      +    ])
      +
      +    // After toggle: sentinel removed, one model added.
      +    const afterToggle = new Set(stored)
      +    afterToggle.delete(emptyProviderSentinelKey('nous'))
      +    afterToggle.add(modelVisibilityKey('nous', 'hermes-3-llama-3.1-70b'))
      +
      +    const visible = effectiveVisibleKeys(afterToggle, [
      +      provider('nous', ['hermes-3-llama-3.1-70b', 'hermes-3-llama-3.1-8b']),
      +      provider('ollama', ['qwen3:latest'])
      +    ])
      +
      +    expect(visible.has(modelVisibilityKey('nous', 'hermes-3-llama-3.1-70b'))).toBe(true)
      +    expect(visible.has(modelVisibilityKey('nous', 'hermes-3-llama-3.1-8b'))).toBe(false)
      +  })
      +
      +  it('sentinel key helper produces correct format', () => {
      +    expect(emptyProviderSentinelKey('openai')).toBe('openai::')
      +    expect(isProviderSentinel('openai::')).toBe(true)
      +    expect(isProviderSentinel('openai::gpt-4o')).toBe(false)
      +  })
       })
    • apps/desktop/src/store/model-visibility.ts
      @@ -13,6 +13,19 @@ export const DEFAULT_VISIBLE_PER_PROVIDER = 50
        *  that contain a single colon, e.g. `model:tag`). */
       export const modelVisibilityKey = (provider: string, model: string): string => `${provider}::${model}`
       
      +/** Sentinel key suffix stored when the user explicitly hides ALL models for a
      + *  provider.  Distinguishes "user hid everything" from "never customized" so
      + *  `effectiveVisibleKeys` does not re-add defaults for that provider. */
      +export const EMPTY_PROVIDER_SENTINEL = ''
      +
      +/** Build the sentinel key for a provider whose last model was toggled off. */
      +export const emptyProviderSentinelKey = (provider: string): string =>
      +  modelVisibilityKey(provider, EMPTY_PROVIDER_SENTINEL)
      +
      +/** Check whether a stored key is a provider-hidden sentinel. */
      +export const isProviderSentinel = (key: string): boolean =>
      +  key.endsWith('::')
      +
       /** A model and its optional `…-fast` sibling, collapsed into one logical row.
        *  `id` is the canonical (base) model; `fastId` is the fast variant if present. */
       export interface ModelFamily {
      @@ -116,9 +129,12 @@ export function effectiveVisibleKeys(
       
         for (const provider of providers) {
           const providerPrefix = `${provider.slug}::`
      -    const hasStoredProvider = [...stored].some(key => key.startsWith(providerPrefix))
      +    const hasStoredProvider = [...stored].some(
      +      key => key.startsWith(providerPrefix) && !isProviderSentinel(key)
      +    )
      +    const hasSentinel = stored.has(emptyProviderSentinelKey(provider.slug))
       
      -    if (hasStoredProvider) {
      +    if (hasStoredProvider || hasSentinel) {
             continue
           }
       
      @@ -129,5 +145,12 @@ export function effectiveVisibleKeys(
           }
         }
       
      +  // Strip sentinel keys — they are bookkeeping, not real visibility entries.
      +  for (const key of [...next]) {
      +    if (isProviderSentinel(key)) {
      +      next.delete(key)
      +    }
      +  }
      +
         return next
       }

1 PR closed without merging

From the close comments: superseded by another PR 1

  • #43542 fix(desktop): preserve empty model visibility providers · superseded by another PR → #43496, #43485
Closed issues · 1
  • Closed#43485[Bug] Model visibility: toggling off last model in a provider group re-enables all models
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#43496
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
11 Jun 2026

For your agent

The same facts, typed.