From b166d831cecdbb7806bff118ff0460caff1ea43c Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Fri, 6 Feb 2026 15:27:16 +0200 Subject: [PATCH] refactor(ui): unify primary agent filtering for mentions and UI (#339) --- .../chat/AgentMentionAutocomplete.tsx | 16 ++++++------- packages/ui/src/components/chat/ChatInput.tsx | 6 ++--- .../ui/src/components/chat/ModelControls.tsx | 24 +++++++++---------- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/packages/ui/src/components/chat/AgentMentionAutocomplete.tsx b/packages/ui/src/components/chat/AgentMentionAutocomplete.tsx index ad53420a..05fc0acf 100644 --- a/packages/ui/src/components/chat/AgentMentionAutocomplete.tsx +++ b/packages/ui/src/components/chat/AgentMentionAutocomplete.tsx @@ -18,6 +18,11 @@ export interface AgentMentionAutocompleteHandle { type AutocompleteTab = 'commands' | 'agents' | 'files'; +const isMentionableAgentMode = (mode?: string | null): boolean => { + if (!mode) return false; + return mode !== 'primary'; +}; + interface AgentMentionAutocompleteProps { searchQuery: string; onAgentSelect: (agentName: string) => void; @@ -27,14 +32,7 @@ interface AgentMentionAutocompleteProps { onTabSelect?: (tab: AutocompleteTab) => void; } -const isMentionable = (mode?: string | null): boolean => { - if (!mode) { - return false; - } - return mode !== 'primary'; -}; - -export const AgentMentionAutocomplete = React.forwardRef(({ +export const AgentMentionAutocomplete = React.forwardRef(({ searchQuery, onAgentSelect, onClose, @@ -59,7 +57,7 @@ export const AgentMentionAutocomplete = React.forwardRef { const visibleAgents = getVisibleAgents(); const filtered = visibleAgents - .filter((agent) => isMentionable(agent.mode)) + .filter((agent) => isMentionableAgentMode(agent.mode)) .map((agent) => { const metadata = agentsWithMetadata.find(a => a.name === agent.name) as (AgentWithExtras & { scope?: string }) | undefined; return { diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index 6ec9d045..7adbb40f 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -54,8 +54,6 @@ interface ChatInputProps { scrollToBottom?: (options?: { instant?: boolean; force?: boolean; clearAnchor?: boolean }) => void; } -const isPrimaryMode = (mode?: string) => mode === 'primary' || mode === 'all' || mode === undefined || mode === null; - const CHAT_INPUT_DRAFT_KEY = 'openchamber_chat_input_draft'; // Helper to safely read from localStorage @@ -118,6 +116,7 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo const { currentProviderId, currentModelId, currentVariant, currentAgentName, setAgent, getVisibleAgents } = useConfigStore(); const agents = getVisibleAgents(); + const primaryAgents = React.useMemo(() => agents.filter((agent) => agent.mode === 'primary'), [agents]); const { isMobile, inputBarOffset, isKeyboardOpen, setTimelineDialogOpen, cornerRadius, persistChatDraft } = useUIStore(); const { working } = useAssistantStatus(); const { currentTheme } = useThemeSystem(); @@ -799,7 +798,6 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo }, [abortCurrentOperation, clearAbortPrompt, startAbortIndicator]); const handleCycleAgent = React.useCallback(() => { - const primaryAgents = agents.filter(agent => isPrimaryMode(agent.mode)); if (primaryAgents.length <= 1) return; const currentIndex = primaryAgents.findIndex(agent => agent.name === currentAgentName); @@ -811,7 +809,7 @@ export const ChatInput: React.FC = ({ onOpenSettings, scrollToBo if (currentSessionId) { saveSessionAgentSelection(currentSessionId, nextAgent.name); } - }, [agents, currentAgentName, currentSessionId, setAgent, saveSessionAgentSelection]); + }, [primaryAgents, currentAgentName, currentSessionId, setAgent, saveSessionAgentSelection]); const adjustTextareaHeight = React.useCallback(() => { const textarea = textareaRef.current; diff --git a/packages/ui/src/components/chat/ModelControls.tsx b/packages/ui/src/components/chat/ModelControls.tsx index a2f05062..5b15b537 100644 --- a/packages/ui/src/components/chat/ModelControls.tsx +++ b/packages/ui/src/components/chat/ModelControls.tsx @@ -57,8 +57,6 @@ type IconComponent = ComponentType; type ProviderModel = Record & { id?: string; name?: string }; -const isPrimaryMode = (mode?: string) => mode === 'primary' || mode === 'all' || mode === undefined || mode === null; - type PermissionAction = 'allow' | 'ask' | 'deny'; type PermissionRule = { permission: string; pattern: string; action: PermissionAction }; @@ -288,6 +286,7 @@ export const ModelControls: React.FC = ({ // Use visible agents (excludes hidden internal agents) const agents = getVisibleAgents(); + const primaryAgents = React.useMemo(() => agents.filter((agent) => agent.mode === 'primary'), [agents]); const { currentSessionId, @@ -432,8 +431,12 @@ export const ModelControls: React.FC = ({ setModelSelectedIndex(0); }, [desktopModelQuery]); + const selectableDesktopAgents = React.useMemo(() => { + return agents.filter((agent) => agent.mode !== 'subagent'); + }, [agents]); + const sortedAndFilteredAgents = React.useMemo(() => { - const sorted = [...agents].sort((a, b) => a.name.localeCompare(b.name)); + const sorted = [...selectableDesktopAgents].sort((a, b) => a.name.localeCompare(b.name)); if (!agentSearchQuery.trim()) { return sorted; } @@ -441,17 +444,17 @@ export const ModelControls: React.FC = ({ fuzzyMatch(agentSearchQuery, agent.name) || (agent.description && fuzzyMatch(agentSearchQuery, agent.description)) ); - }, [agents, agentSearchQuery]); + }, [selectableDesktopAgents, agentSearchQuery]); const defaultAgentName = React.useMemo(() => { if (settingsDefaultAgent) { - const found = agents.find(a => a.name === settingsDefaultAgent); + const found = selectableDesktopAgents.find(a => a.name === settingsDefaultAgent); if (found) return found.name; } - const buildAgent = agents.find(a => a.name === 'build'); + const buildAgent = selectableDesktopAgents.find(a => a.name === 'build'); if (buildAgent) return buildAgent.name; - return agents[0]?.name; - }, [settingsDefaultAgent, agents]); + return selectableDesktopAgents[0]?.name; + }, [settingsDefaultAgent, selectableDesktopAgents]); const currentAgent = React.useMemo(() => { if (uiAgentName) { @@ -696,7 +699,6 @@ export const ModelControls: React.FC = ({ return; } - const primaryAgents = agents.filter(agent => isPrimaryMode(agent.mode)); const fallbackAgent = agents.find(agent => agent.name === 'build') || primaryAgents[0] || agents[0]; if (!fallbackAgent) { return; @@ -821,6 +823,7 @@ export const ModelControls: React.FC = ({ currentSessionId, currentSessionMessageCount, agents, + primaryAgents, currentAgentName, getAgentModelForSession, setAgent, @@ -1107,7 +1110,6 @@ export const ModelControls: React.FC = ({ const getAgentDisplayName = () => { if (!uiAgentName) { - const primaryAgents = agents.filter(agent => isPrimaryMode(agent.mode)); const buildAgent = primaryAgents.find(agent => agent.name === 'build'); const defaultAgent = buildAgent || primaryAgents[0]; return defaultAgent ? capitalizeAgentName(defaultAgent.name) : 'Select Agent'; @@ -1724,8 +1726,6 @@ export const ModelControls: React.FC = ({ const renderMobileAgentPanel = () => { if (!isCompact) return null; - const primaryAgents = agents.filter(agent => isPrimaryMode(agent.mode)); - return (