From d40925eb083dac971e2f917895c506bfa8d08461 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 9 Nov 2026 01:13:24 -0800 Subject: [PATCH] fix(agent-picker): list agents named by the search before other agents' model setups (#39806) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(agent-picker): list agents named by the search before other agents' model setups Typing "codex" in the agent picker showed OpenCode/Cursor model setups (e.g. "OpenCode · GPT-5 Codex") above the Codex agent itself, and Enter picked the first setup. Direct agent matches now render first and are the Enter target. * test(agent-picker): assert search highlight and Enter target for named agent * test(agent-picker): import fireEvent after rebase (cherry picked from commit f04e06b023ef5ff7c08640c7271a0aba319fe201) (cherry picked from commit f2960d1b73e13f76970f5eed68f924572471f22c) --- .../AgentLaunchPickerMenu.search.test.tsx | 76 +++++++++++++++++++ .../agent/AgentLaunchPickerMenu.tsx | 53 ++++++------- 2 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 src/renderer/src/components/agent/AgentLaunchPickerMenu.search.test.tsx diff --git a/src/renderer/src/components/agent/AgentLaunchPickerMenu.search.test.tsx b/src/renderer/src/components/agent/AgentLaunchPickerMenu.search.test.tsx new file mode 100644 index 00000000000..af1d00002d1 --- /dev/null +++ b/src/renderer/src/components/agent/AgentLaunchPickerMenu.search.test.tsx @@ -0,0 +1,76 @@ +// @vitest-environment happy-dom +import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, expect, it, vi } from 'vitest' +import type { ComponentProps } from 'react' +import type * as LaunchSetup from './AgentLaunchSetupButton' +import type { AgentLaunchSetupButton } from './AgentLaunchSetupButton' +import { AgentLaunchPickerMenu } from './AgentLaunchPickerMenu' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + DropdownMenuSub, + DropdownMenuSubTrigger +} from '@/components/ui/dropdown-menu' +import type { AgentPickerEntry } from '../../../../shared/agent-launch/picker/agent-launch-picker' + +vi.mock('@/hooks/useShortcutLabel', () => ({ useOptionalShortcutLabel: () => null })) +vi.mock('./AgentLaunchSetupButton', async (importOriginal) => ({ + ...(await importOriginal()), + AgentLaunchSetupButton: vi.fn((props: ComponentProps) => ( + + + {props.renderAnchor?.()} + + + )) +})) +afterEach(() => { + cleanup() + vi.clearAllMocks() +}) + +it('lists an agent named by the query before other agents’ model setups', () => { + const codex: AgentPickerEntry = { + agent: 'codex', + label: 'Codex', + rows: [{ agent: 'codex', kind: 'cli' }], + surfaces: ['terminal'], + defaultSurface: 'terminal', + terminal: {}, + structured: {}, + catalog: { origin: 'probe', fetchedAt: 1, models: [] } + } + const opencode: AgentPickerEntry = { + ...codex, + agent: 'opencode', + label: 'OpenCode', + rows: [{ agent: 'opencode', kind: 'cli' }], + catalog: { + origin: 'probe', + fetchedAt: 1, + models: [{ id: 'gpt-5-codex', label: 'GPT-5 Codex', isDefault: false, efforts: [] }] + } + } + const launch = vi.fn() + render( + + Open + + + + + ) + fireEvent.focus(screen.getByRole('textbox')) + fireEvent.change(screen.getByRole('textbox'), { target: { value: 'codex' } }) + const items = screen.getAllByRole('menuitem').map((item) => item.textContent) + expect(items[0]).toContain('Codex') + expect(items[0]).not.toContain('OpenCode') + expect(items.some((text) => text?.includes('OpenCode · GPT-5 Codex'))).toBe(true) + const highlighted = document.querySelectorAll('[data-search-default]') + expect(highlighted).toHaveLength(1) + expect(highlighted[0].textContent).toContain('Codex') + expect(highlighted[0].textContent).not.toContain('OpenCode') + fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Enter' }) + expect(launch).toHaveBeenCalledWith({ agent: 'codex', kind: 'cli' }, undefined) +}) diff --git a/src/renderer/src/components/agent/AgentLaunchPickerMenu.tsx b/src/renderer/src/components/agent/AgentLaunchPickerMenu.tsx index 42e4135285f..bad532842c3 100644 --- a/src/renderer/src/components/agent/AgentLaunchPickerMenu.tsx +++ b/src/renderer/src/components/agent/AgentLaunchPickerMenu.tsx @@ -136,8 +136,9 @@ export function AgentLaunchPickerMenu({ } // Why: Enter in the search picks the first enabled row, so show it highlighted like cmdk comboboxes. const enabled = (entry: AgentPickerEntry) => !disabled && entry.surfaces.length > 0 - const firstSetup = setups.find((setup) => enabled(setup.entry)) - const firstEntry = firstSetup ? undefined : visible.find(enabled) + // Why: a query naming an agent should surface that agent before other agents’ model setups. + const firstEntry = visible.find(enabled) + const firstSetup = firstEntry ? undefined : setups.find((setup) => enabled(setup.entry)) const showSearchDefault = searchFocused && query.trim().length > 0 return (
@@ -178,6 +179,30 @@ export function AgentLaunchPickerMenu({
) : null}
+ {visible.map((entry) => { + const source = draftForEntry?.(entry) + return ( + + ) + })} {setups.map((setup) => { const source = draftForEntry?.(setup.entry) return ( @@ -203,30 +228,6 @@ export function AgentLaunchPickerMenu({ /> ) })} - {visible.map((entry) => { - const source = draftForEntry?.(entry) - return ( - - ) - })} {!visible.length && !setups.length ? ( {t('auto.components.agent.AgentCombobox.579c768bde', 'No agents match your search.')}