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:
@@ -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])]
|
||||
}
|
||||
|
||||
@@ -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([])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user