diff --git a/apps/desktop/src/app/chat/composer/model-pill.test.tsx b/apps/desktop/src/app/chat/composer/model-pill.test.tsx index d81afb7ed521..4f9d56874e71 100644 --- a/apps/desktop/src/app/chat/composer/model-pill.test.tsx +++ b/apps/desktop/src/app/chat/composer/model-pill.test.tsx @@ -64,6 +64,7 @@ describe('ModelPill pinned-override badge', () => { const { unmount } = render( ) + expect(screen.getByTestId('model-pinned-dot')).toBeTruthy() unmount() diff --git a/apps/desktop/src/app/shell/model-menu-panel.tsx b/apps/desktop/src/app/shell/model-menu-panel.tsx index 100b9b04eea9..21eb9a6a7f1c 100644 --- a/apps/desktop/src/app/shell/model-menu-panel.tsx +++ b/apps/desktop/src/app/shell/model-menu-panel.tsx @@ -247,115 +247,115 @@ export function ModelMenuPanel({ gateway, onSelectModel, requestGateway }: Model const slug = group.provider.slug // Collapsed when stored + no active search + not the current provider. - const collapsed = - collapsedProviders.includes(slug) && !search && slug !== optionsProvider + const collapsed = collapsedProviders.includes(slug) && !search && slug !== optionsProvider return ( { event.preventDefault() toggleCollapsedProvider(slug) }} textValue="" > - {collapsed ? - : + {collapsed ? ( + + ) : ( - } + )} {group.provider.name} {!collapsed && group.families.map(family => { - // The active id may be the base or its -fast sibling; either - // way this one family row represents both. - const activeId = - group.provider.slug === optionsProvider && - (optionsModel === family.id || optionsModel === family.fastId) - ? optionsModel - : null + // The active id may be the base or its -fast sibling; either + // way this one family row represents both. + const activeId = + group.provider.slug === optionsProvider && + (optionsModel === family.id || optionsModel === family.fastId) + ? optionsModel + : null - const isCurrent = activeId !== null - const name = modelDisplayParts(family.id).name - // Capabilities are looked up against the active/base id; the - // -fast variant carries the same param support as its base. - const caps = group.provider.capabilities?.[family.id] + const isCurrent = activeId !== null + const name = modelDisplayParts(family.id).name + // Capabilities are looked up against the active/base id; the + // -fast variant carries the same param support as its base. + const caps = group.provider.capabilities?.[family.id] - // Effective settings for this row: live session state when it's - // the active model, otherwise its remembered preset (Hermes - // defaults when unset). Row label AND submenu read from these so - // they never disagree. - const preset = modelPresets[modelPresetKey(group.provider.slug, family.id)] ?? {} - const effEffort = isCurrent ? currentReasoningEffort : (preset.effort ?? '') - const effFast = isCurrent ? currentFastMode : (preset.fast ?? false) + // Effective settings for this row: live session state when it's + // the active model, otherwise its remembered preset (Hermes + // defaults when unset). Row label AND submenu read from these so + // they never disagree. + const preset = modelPresets[modelPresetKey(group.provider.slug, family.id)] ?? {} + const effEffort = isCurrent ? currentReasoningEffort : (preset.effort ?? '') + const effFast = isCurrent ? currentFastMode : (preset.fast ?? false) - const fastControl = resolveFastControl( - activeId ?? family.id, - group.provider.models ?? [], - caps?.fast ?? false, - effFast - ) + const fastControl = resolveFastControl( + activeId ?? family.id, + group.provider.models ?? [], + caps?.fast ?? false, + effFast + ) - const meta = [ - fastControl.kind !== 'none' && fastControl.on ? copy.fast : null, - (caps?.reasoning ?? true) ? reasoningEffortLabel(effEffort) || copy.medium : null - ] - .filter(Boolean) - .join(' ') + const meta = [ + fastControl.kind !== 'none' && fastControl.on ? copy.fast : null, + (caps?.reasoning ?? true) ? reasoningEffortLabel(effEffort) || copy.medium : null + ] + .filter(Boolean) + .join(' ') - // Every row is a hover-Edit submenu trigger. Activating it - // (pointer or keyboard) switches to the family's base model and - // restores its preset; the Fast toggle inside swaps to the -fast - // sibling (or flips the speed param). The sub-trigger has no - // `onSelect`, so wire both click and Enter/Space for keyboard parity. - // Clicking the row commits the model and closes the picker; the - // edit submenu (reasoning/fast) is reached by HOVER, so you can - // still tweak those without the click dismissing everything. - const activate = () => { - if (!isCurrent) { - void selectFamily(family, group.provider) - } + // Every row is a hover-Edit submenu trigger. Activating it + // (pointer or keyboard) switches to the family's base model and + // restores its preset; the Fast toggle inside swaps to the -fast + // sibling (or flips the speed param). The sub-trigger has no + // `onSelect`, so wire both click and Enter/Space for keyboard parity. + // Clicking the row commits the model and closes the picker; the + // edit submenu (reasoning/fast) is reached by HOVER, so you can + // still tweak those without the click dismissing everything. + const activate = () => { + if (!isCurrent) { + void selectFamily(family, group.provider) + } - closeMenu() - } + closeMenu() + } - return ( - - { - if (event.key === 'Enter' || event.key === ' ') { - activate() - } - }} - > - - {name} - {meta ? {meta} : null} - - {isCurrent ? : null} - - switchTo(nextModel, group.provider.slug)} - provider={group.provider.slug} - reasoning={caps?.reasoning ?? true} - requestGateway={requestGateway} - /> - - ) - })} - - )})} + return ( + + { + if (event.key === 'Enter' || event.key === ' ') { + activate() + } + }} + > + + {name} + {meta ? {meta} : null} + + {isCurrent ? ( + + ) : null} + + switchTo(nextModel, group.provider.slug)} + provider={group.provider.slug} + reasoning={caps?.reasoning ?? true} + requestGateway={requestGateway} + /> + + ) + })} + + ) + })} )} diff --git a/apps/desktop/src/components/assistant-ui/tool/fallback.tsx b/apps/desktop/src/components/assistant-ui/tool/fallback.tsx index 901f0f234bcb..bf4cfaed6d0e 100644 --- a/apps/desktop/src/components/assistant-ui/tool/fallback.tsx +++ b/apps/desktop/src/components/assistant-ui/tool/fallback.tsx @@ -381,7 +381,10 @@ function ToolEntry({ part }: ToolEntryProps) { ) const showRawSearchDrilldown = - part.toolName === 'web_search' && part.result !== undefined && toolViewMode !== 'technical' && Boolean(rawResult.trim()) + part.toolName === 'web_search' && + part.result !== undefined && + toolViewMode !== 'technical' && + Boolean(rawResult.trim()) const hasExpandableContent = Boolean( view.imageUrl || view.inlineDiff || showDetail || hasSearchHits || toolViewMode === 'technical' @@ -596,9 +599,7 @@ function ToolEntry({ part }: ToolEntryProps) { {showRawSearchDrilldown && (
{copy.rawResponse} -
-                {rawResult}
-              
+
{rawResult}
)} {toolViewMode === 'technical' && !(isFileEdit && view.inlineDiff) && ( diff --git a/apps/desktop/src/components/pane-shell/tree/renderer/lone-header.test.ts b/apps/desktop/src/components/pane-shell/tree/renderer/lone-header.test.ts index 198359df18ae..8cfe0560ebcd 100644 --- a/apps/desktop/src/components/pane-shell/tree/renderer/lone-header.test.ts +++ b/apps/desktop/src/components/pane-shell/tree/renderer/lone-header.test.ts @@ -6,6 +6,7 @@ describe('forceLoneHeaderForPanes', () => { const chrome = (placement?: string, uncloseable = false) => () => ({ placement, uncloseable }) + const noCollapse = () => false it('forces a header for session-tile ids even without registered chrome', () => { diff --git a/apps/desktop/src/hermes.test.ts b/apps/desktop/src/hermes.test.ts index 0a6553d24284..46a7cef5f928 100644 --- a/apps/desktop/src/hermes.test.ts +++ b/apps/desktop/src/hermes.test.ts @@ -108,7 +108,9 @@ describe('Hermes REST session helpers', () => { if (path.startsWith('/api/profiles/sessions/sidebar')) { // The exact skew failure: Electron surfaces the backend catch-all. return Promise.reject( - new Error('Error invoking remote method \'hermes:api\': Error: 404: {"detail":"No such API endpoint: /api/profiles/sessions/sidebar"}') + new Error( + 'Error invoking remote method \'hermes:api\': Error: 404: {"detail":"No such API endpoint: /api/profiles/sessions/sidebar"}' + ) ) } diff --git a/apps/desktop/src/store/provider-collapse.ts b/apps/desktop/src/store/provider-collapse.ts index bd484ac008a7..d7da2c90a88c 100644 --- a/apps/desktop/src/store/provider-collapse.ts +++ b/apps/desktop/src/store/provider-collapse.ts @@ -19,18 +19,10 @@ const STORAGE_KEY = 'hermes.desktop.collapsed-providers' * the render loop only visits providers present in the active `groups`, and * `collapsedProviders.includes(slug)` against an absent slug is a no-op. */ -export const $collapsedProviders = persistentAtom( - STORAGE_KEY, - [], - Codecs.stringArray -) +export const $collapsedProviders = persistentAtom(STORAGE_KEY, [], Codecs.stringArray) /** Toggle a provider slug in/out of the collapsed set. */ export function toggleCollapsedProvider(slug: string): void { const current = $collapsedProviders.get() - $collapsedProviders.set( - current.includes(slug) - ? current.filter(s => s !== slug) - : [...current, slug] - ) + $collapsedProviders.set(current.includes(slug) ? current.filter(s => s !== slug) : [...current, slug]) } diff --git a/ui-tui/src/app/slash/commands/session.ts b/ui-tui/src/app/slash/commands/session.ts index 9eba0f67a162..e2092a14b745 100644 --- a/ui-tui/src/app/slash/commands/session.ts +++ b/ui-tui/src/app/slash/commands/session.ts @@ -47,17 +47,22 @@ const reasoningConfigPayload = (arg: string, sid: string) => { for (const part of parts) { const flag = part.toLowerCase() + if (REASONING_GLOBAL_FLAGS.has(flag)) { scope = 'global' + continue } + if (REASONING_SESSION_FLAGS.has(flag)) { // Session scope is the default; accept the flag for parity with /model. if (!scope) { scope = 'session' } + continue } + valueParts.push(part) } @@ -486,31 +491,29 @@ export const sessionCommands: SlashCommand[] = [ ) } - ctx.gateway - .rpc('config.set', reasoningConfigPayload(arg, ctx.sid ?? '')) - .then( - ctx.guarded(r => { - if (!r.value) { - return - } + ctx.gateway.rpc('config.set', reasoningConfigPayload(arg, ctx.sid ?? '')).then( + ctx.guarded(r => { + if (!r.value) { + return + } - if (r.value === 'hide') { - patchUiState(state => ({ - ...state, - sections: { ...state.sections, thinking: 'hidden' }, - showReasoning: false - })) - } else if (r.value === 'show') { - patchUiState(state => ({ - ...state, - sections: { ...state.sections, thinking: 'expanded' }, - showReasoning: true - })) - } + if (r.value === 'hide') { + patchUiState(state => ({ + ...state, + sections: { ...state.sections, thinking: 'hidden' }, + showReasoning: false + })) + } else if (r.value === 'show') { + patchUiState(state => ({ + ...state, + sections: { ...state.sections, thinking: 'expanded' }, + showReasoning: true + })) + } - ctx.transcript.sys(`reasoning: ${r.value}`) - }) - ) + ctx.transcript.sys(`reasoning: ${r.value}`) + }) + ) } },