fix(desktop): list root-level personalities in the Settings dropdown (#123297)

The Desktop Settings → Chat → Personality dropdown and the config-driven
personality name list read only `agent.personalities`, so a persona
registered under the root-level `personalities` block — which the Python
runtime, CLI `/personality`, and gateway all honour via
`available_personalities()` — never appeared in the GUI and could not be
selected.

Read both blocks in `personalityOptions` (settings/helpers.ts) and
`personalityNamesFromConfig` (lib/chat-runtime.ts), mirroring the runtime's
merge order (root `personalities`, then `agent.personalities`), so the two
surfaces list the same personas.
This commit is contained in:
PRATHAMESH75
2026-09-26 08:35:01 +05:30
committed by brooklyn!
parent 9fa23760bc
commit 9992d70f0d
3 changed files with 46 additions and 8 deletions

View File

@@ -243,10 +243,17 @@ export function setNested(obj: HermesConfigRecord, path: string, value: unknown)
}
function personalityOptions(config: HermesConfigRecord): string[] {
const custom = getNested(config, 'agent.personalities')
const customNames =
custom && typeof custom === 'object' && !Array.isArray(custom) ? Object.keys(custom as Record<string, unknown>) : []
// The Python runtime (`hermes_cli.personality.available_personalities`) honours both
// the root-level `personalities` block and `agent.personalities` (agent wins on a name
// clash). Read both so a root-registered persona the CLI/gateway resolve also appears in
// the dropdown (#123297).
const customNames: string[] = []
for (const key of ['personalities', 'agent.personalities']) {
const block = getNested(config, key)
if (block && typeof block === 'object' && !Array.isArray(block)) {
customNames.push(...Object.keys(block as Record<string, unknown>))
}
}
return [...new Set(['', ...BUILTIN_PERSONALITIES, ...customNames])]
}

View File

@@ -12,6 +12,7 @@ import {
createToolMergeCache,
messageCreatedAt,
optimisticAttachmentRef,
personalityNamesFromConfig,
toRuntimeMessage
} from './chat-runtime'
@@ -250,3 +251,23 @@ describe('coalesceToolOnlyAssistants toolCallId uniqueness', () => {
expect(ids).toEqual(['call-a', 'call-b'])
})
})
describe('personalityNamesFromConfig', () => {
it('reads root-level personalities the runtime honours (#123297)', () => {
expect(personalityNamesFromConfig({ personalities: { root_persona: '...' } })).toEqual(['root_persona'])
})
it('merges root and agent blocks, deduping name clashes', () => {
const names = personalityNamesFromConfig({
personalities: { root_persona: 'r', shared: 'root' },
agent: { personalities: { agent_persona: 'a', shared: 'agent' } }
})
expect(new Set(names)).toEqual(new Set(['root_persona', 'shared', 'agent_persona']))
})
it('ignores non-object or array blocks', () => {
expect(personalityNamesFromConfig({ personalities: ['nope'], agent: { personalities: 'nope' } })).toEqual([])
expect(personalityNamesFromConfig(null)).toEqual([])
})
})

View File

@@ -281,11 +281,21 @@ export function optimisticAttachmentRef(attachment: ComposerAttachment): string
export function personalityNamesFromConfig(config: unknown): string[] {
const root = config && typeof config === 'object' ? (config as Record<string, unknown>) : {}
const agent = root.agent && typeof root.agent === 'object' ? (root.agent as Record<string, unknown>) : {}
const personalities = agent.personalities
return personalities && typeof personalities === 'object' && !Array.isArray(personalities)
? Object.keys(personalities as Record<string, unknown>)
: []
// The Python runtime (`hermes_cli.personality.available_personalities`) overlays
// built-ins with the root-level `personalities` block, then `agent.personalities`
// (agent wins on a name clash). Read both here so a root-registered persona the
// CLI/gateway honour also reaches the GUI (#123297).
const names = new Set<string>()
for (const block of [root.personalities, agent.personalities]) {
if (block && typeof block === 'object' && !Array.isArray(block)) {
for (const name of Object.keys(block as Record<string, unknown>)) {
names.add(name)
}
}
}
return [...names]
}
export function normalizePersonalityValue(value: string): string {