diff --git a/apps/emdash-desktop/src/main/core/automations/actions/taskCreate.test.ts b/apps/emdash-desktop/src/main/core/automations/actions/taskCreate.test.ts index 5b1bd78628..ed054a8925 100644 --- a/apps/emdash-desktop/src/main/core/automations/actions/taskCreate.test.ts +++ b/apps/emdash-desktop/src/main/core/automations/actions/taskCreate.test.ts @@ -326,6 +326,83 @@ describe('executeTaskCreate', () => { ); }); + it('uses automation default agent and model when the automation has no provider or model', async () => { + vi.mocked(appSettingsService.get).mockImplementation(async (key) => { + if (key === 'defaultAutomationAgent') return 'codex' as never; + if (key === 'defaultAutomationModel') return 'gpt-5-codex' as never; + return null as never; + }); + + await executeTaskCreate( + { + ...automation, + conversationConfig: { prompt: 'Check things', provider: '', autoApprove: false }, + }, + run, + noopStep + ); + + expect(createConversation).toHaveBeenCalledWith( + expect.objectContaining({ + provider: 'codex', + model: 'gpt-5-codex', + }) + ); + }); + + it('keeps an automation-specific model ahead of the automation default model', async () => { + vi.mocked(appSettingsService.get).mockImplementation(async (key) => { + if (key === 'defaultAutomationAgent') return 'claude' as never; + if (key === 'defaultAutomationModel') return 'claude-default' as never; + return null as never; + }); + + await executeTaskCreate( + { + ...automation, + conversationConfig: { + prompt: 'Check things', + provider: 'claude', + autoApprove: false, + model: 'claude-specific', + }, + }, + run, + noopStep + ); + + expect(createConversation).toHaveBeenCalledWith( + expect.objectContaining({ + provider: 'claude', + model: 'claude-specific', + }) + ); + }); + + it('does not apply a later automation default model to an automation with an explicit provider', async () => { + vi.mocked(appSettingsService.get).mockImplementation(async (key) => { + if (key === 'defaultAutomationAgent') return 'claude' as never; + if (key === 'defaultAutomationModel') return 'claude-default' as never; + return null as never; + }); + + await executeTaskCreate( + { + ...automation, + conversationConfig: { prompt: 'Check things', provider: 'claude', autoApprove: false }, + }, + run, + noopStep + ); + + expect(createConversation).toHaveBeenCalledWith( + expect.objectContaining({ + provider: 'claude', + model: undefined, + }) + ); + }); + it('creates ACP conversations with an initial queue and eagerly starts the session', async () => { await executeTaskCreate( { @@ -345,6 +422,53 @@ describe('executeTaskCreate', () => { expect(createConversation).toHaveBeenCalledWith( expect.objectContaining({ provider: 'claude', + model: 'sonnet', + initialQueue: [{ text: 'Check things' }], + isInitialConversation: true, + type: 'acp', + }) + ); + expect(vi.mocked(createConversation).mock.calls[0]?.[0].initialPrompt).toBeUndefined(); + expect(acpRuntimeProcedures.startSession).toHaveBeenCalledWith({ + input: expect.objectContaining({ + conversationId: expect.any(String), + projectId: 'project-1', + taskId: expect.any(String), + providerId: 'claude', + workspaceId: 'workspace-1', + cwd: '/tmp/task', + sessionId: null, + model: 'sonnet', + initialQueue: [{ text: 'Check things' }], + }), + }); + }); + + it('passes automation default model to ACP startup when no provider is configured', async () => { + vi.mocked(appSettingsService.get).mockImplementation(async (key) => { + if (key === 'defaultAutomationAgent') return 'claude' as never; + if (key === 'defaultAutomationModel') return 'sonnet' as never; + return null as never; + }); + + await executeTaskCreate( + { + ...automation, + conversationConfig: { + prompt: 'Check things', + provider: '', + autoApprove: false, + type: 'acp', + }, + }, + run, + noopStep + ); + + expect(createConversation).toHaveBeenCalledWith( + expect.objectContaining({ + provider: 'claude', + model: 'sonnet', initialQueue: [{ text: 'Check things' }], isInitialConversation: true, type: 'acp', diff --git a/apps/emdash-desktop/src/main/core/automations/actions/taskCreate.ts b/apps/emdash-desktop/src/main/core/automations/actions/taskCreate.ts index ab6df7a5f6..d974985e3a 100644 --- a/apps/emdash-desktop/src/main/core/automations/actions/taskCreate.ts +++ b/apps/emdash-desktop/src/main/core/automations/actions/taskCreate.ts @@ -5,7 +5,6 @@ import { createConversation } from '@main/core/conversations/createConversation' import { issueController } from '@main/core/issues/controller'; import { openProject } from '@main/core/projects/operations/openProject'; import { projectManager } from '@main/core/projects/project-manager'; -import { DEFAULT_AGENT_ID } from '@main/core/settings/settings-registry'; import { appSettingsService } from '@main/core/settings/settings-service'; import { generateRandom } from '@main/core/tasks/name-generation/generateTaskName'; import { @@ -130,9 +129,18 @@ export async function executeTaskCreate( } const workspaceConfig = scopeWorkspaceConfigToRun(taskConfig.workspaceConfig, taskName); - const provider = (automation.conversationConfig?.provider || - (await appSettingsService.get('defaultAgent')) || - DEFAULT_AGENT_ID) as AgentProviderId; + // appSettingsService.get() resolves unset keys to registry defaults, so the automation + // agent default is never empty and needs no further fallback here. + const configuredProvider = automation.conversationConfig?.provider || null; + const provider = (configuredProvider || + (await appSettingsService.get('defaultAutomationAgent'))) as AgentProviderId; + // Automation model defaults are intentionally independent from task/conversation defaults. + // They only apply when the automation has no explicit provider, so later default changes do + // not alter automations that were already configured with a specific agent. + const defaultAutomationModel = configuredProvider + ? null + : await appSettingsService.get('defaultAutomationModel'); + const model = automation.conversationConfig?.model || defaultAutomationModel || undefined; const conversationType = automation.conversationConfig?.type ?? 'pty'; const initialQueue = conversationType === 'acp' @@ -239,7 +247,7 @@ export async function executeTaskCreate( provider, automation.conversationConfig?.autoApprove ), - model: automation.conversationConfig?.model || undefined, + model, ...(conversationType === 'acp' ? { initialQueue } : { initialPrompt: prompt }), isInitialConversation: true, type: conversationType, @@ -254,7 +262,7 @@ export async function executeTaskCreate( workspaceId: provision.data.workspaceId, cwd: provision.data.path, sessionId: null, - model: automation.conversationConfig?.model || null, + model: model ?? null, initialQueue, }, }); diff --git a/apps/emdash-desktop/src/main/core/settings/schema.ts b/apps/emdash-desktop/src/main/core/settings/schema.ts index e6d8e79570..8ac8fb0af7 100644 --- a/apps/emdash-desktop/src/main/core/settings/schema.ts +++ b/apps/emdash-desktop/src/main/core/settings/schema.ts @@ -57,6 +57,7 @@ export const themeSchema = z .default(null); export const defaultAgentSchema = z.optional(z.enum(AGENT_PROVIDER_IDS)).default(DEFAULT_AGENT_ID); +export const defaultModelSchema = z.string().nullable().optional().default(null); export const keyboardSettingsSchema = z .optional( @@ -137,6 +138,9 @@ export const APP_SETTINGS_SCHEMA_MAP = { project: projectSettingsSchema, tasks: taskSettingsSchema, defaultAgent: defaultAgentSchema, + defaultModel: defaultModelSchema, + defaultAutomationAgent: defaultAgentSchema, + defaultAutomationModel: defaultModelSchema, keyboard: keyboardSettingsSchema, notifications: notificationSettingsSchema, theme: themeSchema, @@ -154,6 +158,9 @@ export const appSettingsSchema = z.object({ project: projectSettingsSchema, tasks: taskSettingsSchema, defaultAgent: defaultAgentSchema, + defaultModel: defaultModelSchema, + defaultAutomationAgent: defaultAgentSchema, + defaultAutomationModel: defaultModelSchema, keyboard: keyboardSettingsSchema, notifications: notificationSettingsSchema, theme: themeSchema, diff --git a/apps/emdash-desktop/src/main/core/settings/settings-registry.ts b/apps/emdash-desktop/src/main/core/settings/settings-registry.ts index 9fd25dd7bd..0f983e6af4 100644 --- a/apps/emdash-desktop/src/main/core/settings/settings-registry.ts +++ b/apps/emdash-desktop/src/main/core/settings/settings-registry.ts @@ -47,6 +47,9 @@ export const SETTINGS_DEFAULTS = { }, theme: null, defaultAgent: DEFAULT_AGENT_ID, + defaultModel: null, + defaultAutomationAgent: DEFAULT_AGENT_ID, + defaultAutomationModel: null, keyboard: {}, openIn: { default: 'terminal' as const, diff --git a/apps/emdash-desktop/src/renderer/features/automations/useAutomationFormState.ts b/apps/emdash-desktop/src/renderer/features/automations/useAutomationFormState.ts index 200768faf0..cf4dbb6e98 100644 --- a/apps/emdash-desktop/src/renderer/features/automations/useAutomationFormState.ts +++ b/apps/emdash-desktop/src/renderer/features/automations/useAutomationFormState.ts @@ -103,6 +103,9 @@ export function useAutomationFormState( const initialConversation = useInitialConversationState(effectiveProjectId, seedProvider, false, { resetPromptOnProjectChange: false, + defaultAgentSettingKey: 'defaultAutomationAgent', + defaultModelSettingKey: 'defaultAutomationModel', + applyDefaultModel: !seed, }); const [promptSeeded, setPromptSeeded] = useState(false); diff --git a/apps/emdash-desktop/src/renderer/features/automations/useAutomationSettingsAutoSave.ts b/apps/emdash-desktop/src/renderer/features/automations/useAutomationSettingsAutoSave.ts index 139eebe5da..ac0bbe8c3e 100644 --- a/apps/emdash-desktop/src/renderer/features/automations/useAutomationSettingsAutoSave.ts +++ b/apps/emdash-desktop/src/renderer/features/automations/useAutomationSettingsAutoSave.ts @@ -16,6 +16,7 @@ export function useAutomationSettingsAutoSave(automation: Automation) { effectiveProjectId, prompt, provider, + model, triggerConfig, cronTz, canSave, @@ -23,13 +24,14 @@ export function useAutomationSettingsAutoSave(automation: Automation) { name, workspaceConfig, } = formState; + const useChatUi = formState.initialConversation.useChatUi; function buildConversationConfig(): ConversationConfig { - const useChatUi = formState.initialConversation.useChatUi; return { prompt: prompt.trim(), provider, autoApprove: false, + model: model ?? undefined, type: useChatUi ? 'acp' : 'pty', }; } @@ -70,9 +72,9 @@ export function useAutomationSettingsAutoSave(automation: Automation) { return; } if (canSave) savePatch(); - // We intentionally only track provider here; other fields use action-at-change-site. + // We intentionally only track provider/model/type here; other fields use action-at-change-site. // oxlint-disable-next-line react/exhaustive-deps - }, [provider]); + }, [provider, model, useChatUi]); // Workspace config changes (preset, branch name, sandbox toggle, etc.) are not // interceptable at the setter level because they go through useWorkspaceConfig diff --git a/apps/emdash-desktop/src/renderer/features/conversations/create-conversation-modal.tsx b/apps/emdash-desktop/src/renderer/features/conversations/create-conversation-modal.tsx index 6455118fb3..7a53d441c8 100644 --- a/apps/emdash-desktop/src/renderer/features/conversations/create-conversation-modal.tsx +++ b/apps/emdash-desktop/src/renderer/features/conversations/create-conversation-modal.tsx @@ -1,7 +1,8 @@ import { observer } from 'mobx-react-lite'; -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { conversationRegistry } from '@renderer/features/conversations/stores/conversation-registry'; import { getProjectSshConnectionId } from '@renderer/features/projects/stores/project-selectors'; +import { useAppSettingsKey } from '@renderer/features/settings/use-app-settings-key'; // TODO(conversations-extraction): Pass task settings into the modal instead of importing task hooks. import { useTaskSettings } from '@renderer/features/tasks/hooks/useTaskSettings'; import { AgentSelector } from '@renderer/lib/components/agent-selector/agent-selector'; @@ -40,7 +41,8 @@ export const CreateConversationModal = observer(function CreateConversationModal taskId: string; }) { const connectionId = getProjectSshConnectionId(projectId); - const { providerId, setProviderOverride, createDisabled } = useEffectiveProvider(connectionId); + const { providerId, defaultProviderId, setProviderOverride, createDisabled } = + useEffectiveProvider(connectionId); const conversationMgr = conversationRegistry.get(taskId); const taskSettings = useTaskSettings(); const [isSubmitting, setIsSubmitting] = useState(false); @@ -48,13 +50,21 @@ export const CreateConversationModal = observer(function CreateConversationModal const [autoApproveOverride, setAutoApproveOverride] = useState(null); const [selectedModel, setSelectedModel] = useState(null); const [useAcpOverride, setUseAcpOverride] = useState(false); + const [modelTouched, setModelTouched] = useState(false); const chatUiEnabled = useFeatureFlag('chat-ui'); useCloseGuard(isSubmitting); const { data: agents } = useAgents(); + const { value: defaultModelValue } = useAppSettingsKey('defaultModel'); const modelsCapability = agents?.find((a) => a.id === providerId)?.capabilities.models; const modelOptions = modelsCapability?.kind === 'selectable' ? modelsCapability.modelOptions : null; + const defaultModel = + providerId === defaultProviderId && + typeof defaultModelValue === 'string' && + modelOptions?.[defaultModelValue] + ? defaultModelValue + : null; const showAutoApproveToggle = providerId ? providerSupportsAutoApprove(providerId) : false; const showAcpToggle = chatUiEnabled && providerId ? providerSupportsAcp(providerId) : false; @@ -73,10 +83,15 @@ export const CreateConversationModal = observer(function CreateConversationModal setProviderOverride(next); setSelectedModel(null); setUseAcpOverride(false); + setModelTouched(false); }, [setProviderOverride] ); + useEffect(() => { + if (!modelTouched) setSelectedModel(defaultModel); + }, [defaultModel, modelTouched]); + const handleCreateConversation = useCallback(async () => { if (createDisabled || isSubmitting || !conversationMgr || !providerId) return; const id = crypto.randomUUID(); @@ -135,7 +150,10 @@ export const CreateConversationModal = observer(function CreateConversationModal Model update((val || null) as ModelDefaultValue)} + disabled={disabled || isLoading || isSaving} + > + + + {defaultModel ? (modelOptions[defaultModel]?.name ?? defaultModel) : 'Default model'} + + + + Default model + {Object.entries(modelOptions).map(([id, opt]) => ( + + {opt.name} + + ))} + + + + ); +} diff --git a/apps/emdash-desktop/src/renderer/features/tasks/task-config/initial-conversation-section.test.ts b/apps/emdash-desktop/src/renderer/features/tasks/task-config/initial-conversation-section.test.ts index fc08f4abe1..c07482ced1 100644 --- a/apps/emdash-desktop/src/renderer/features/tasks/task-config/initial-conversation-section.test.ts +++ b/apps/emdash-desktop/src/renderer/features/tasks/task-config/initial-conversation-section.test.ts @@ -18,6 +18,13 @@ const mocks = vi.hoisted(() => ({ getProjectSshConnectionId: vi.fn(), setProviderOverride: vi.fn(), chatUiFeature: true, + defaultModelValue: null as string | null, + agents: [] as Array<{ + id: string; + capabilities: { + models?: { kind: 'selectable'; modelOptions: Record }; + }; + }>, editorText: '', editorApi: { focus: vi.fn(), @@ -88,7 +95,11 @@ vi.mock('../context-bar/add-context-popover', () => ({ })); vi.mock('@renderer/lib/stores/use-agents', () => ({ - useAgents: () => ({ data: [] }), + useAgents: () => ({ data: mocks.agents }), +})); + +vi.mock('@renderer/features/settings/use-app-settings-key', () => ({ + useAppSettingsKey: () => ({ value: mocks.defaultModelValue }), })); vi.mock('@renderer/lib/hooks/useFeatureFlag', () => ({ @@ -111,6 +122,7 @@ vi.mock('@renderer/utils/logger', () => ({ vi.mock('@renderer/features/conversations/use-effective-provider', () => ({ useEffectiveProvider: () => ({ providerId: 'claude', + defaultProviderId: 'claude', setProviderOverride: mocks.setProviderOverride, createDisabled: false, }), @@ -131,6 +143,25 @@ function Probe({ return null; } +function SeedModelProbe({ + projectId, + options, + model, +}: { + projectId: string; + options?: InitialConversationOptions; + model: string; +}) { + const [seeded, setSeeded] = React.useState(false); + const state = useInitialConversationState(projectId, undefined, false, options); + latestState = state; + if (!seeded) { + setSeeded(true); + state.setModel(model); + } + return null; +} + function FieldProbe({ linkedIssue, includeIssueContextByDefault = false, @@ -166,6 +197,8 @@ describe('useInitialConversationState', () => { mocks.editorText = ''; mocks.lastChatComposerProps = null; mocks.getProjectSshConnectionId.mockReturnValue(undefined); + mocks.defaultModelValue = null; + mocks.agents = []; dom = new JSDOM('
', { url: 'http://localhost', @@ -195,6 +228,17 @@ describe('useInitialConversationState', () => { }); } + async function renderSeedModelProbe( + projectId: string, + model: string, + options?: InitialConversationOptions + ) { + await act(async () => { + root.render(React.createElement(SeedModelProbe, { projectId, model, options })); + }); + await act(async () => {}); + } + async function setPrompt(prompt: string) { await act(async () => { latestState?.setPrompt(prompt); @@ -223,6 +267,66 @@ describe('useInitialConversationState', () => { expect(latestState?.prompt).toBe('Keep this automation prompt'); }); + it('applies the configured default model when default model application is enabled', async () => { + mocks.defaultModelValue = 'claude-sonnet'; + mocks.agents = [ + { + id: 'claude', + capabilities: { + models: { + kind: 'selectable', + modelOptions: { 'claude-sonnet': { name: 'Claude Sonnet' } }, + }, + }, + }, + ]; + + await renderProbe('project-1'); + + expect(latestState?.model).toBe('claude-sonnet'); + }); + + it('does not apply the configured default model when default model application is disabled', async () => { + mocks.defaultModelValue = 'claude-sonnet'; + mocks.agents = [ + { + id: 'claude', + capabilities: { + models: { + kind: 'selectable', + modelOptions: { 'claude-sonnet': { name: 'Claude Sonnet' } }, + }, + }, + }, + ]; + + await renderProbe('project-1', { applyDefaultModel: false }); + + expect(latestState?.model).toBeNull(); + }); + + it('preserves a seeded model when default model application is disabled', async () => { + mocks.defaultModelValue = 'claude-sonnet'; + mocks.agents = [ + { + id: 'claude', + capabilities: { + models: { + kind: 'selectable', + modelOptions: { + 'claude-haiku': { name: 'Claude Haiku' }, + 'claude-sonnet': { name: 'Claude Sonnet' }, + }, + }, + }, + }, + ]; + + await renderSeedModelProbe('project-1', 'claude-haiku', { applyDefaultModel: false }); + + expect(latestState?.model).toBe('claude-haiku'); + }); + it('defaults chat UI on when the provider supports ACP', async () => { await renderProbe('project-1'); @@ -254,6 +358,8 @@ describe('InitialConversationField', () => { mocks.editorText = ''; mocks.lastChatComposerProps = null; mocks.getProjectSshConnectionId.mockReturnValue(undefined); + mocks.defaultModelValue = null; + mocks.agents = []; dom = new JSDOM('
', { url: 'http://localhost', diff --git a/apps/emdash-desktop/src/renderer/features/tasks/task-config/initial-conversation-section.tsx b/apps/emdash-desktop/src/renderer/features/tasks/task-config/initial-conversation-section.tsx index 9bbd45ee9b..7dc32d29ac 100644 --- a/apps/emdash-desktop/src/renderer/features/tasks/task-config/initial-conversation-section.tsx +++ b/apps/emdash-desktop/src/renderer/features/tasks/task-config/initial-conversation-section.tsx @@ -13,6 +13,7 @@ import { useEffectiveProvider } from '@renderer/features/conversations/use-effec import { IntegrationIcon } from '@renderer/features/integrations/integration-icon'; import { usePromptLibrary } from '@renderer/features/library/prompts/use-prompt-library'; import { getProjectSshConnectionId } from '@renderer/features/projects/stores/project-selectors'; +import { useAppSettingsKey } from '@renderer/features/settings/use-app-settings-key'; import { AgentSelector } from '@renderer/lib/components/agent-selector/agent-selector'; import { useFeatureFlag } from '@renderer/lib/hooks/useFeatureFlag'; import { useLocalStorage } from '@renderer/lib/hooks/useLocalStorage'; @@ -56,6 +57,9 @@ export type InitialConversationState = { interface InitialConversationStateOptions { resetPromptOnProjectChange?: boolean; + defaultAgentSettingKey?: 'defaultAgent' | 'defaultAutomationAgent'; + defaultModelSettingKey?: 'defaultModel' | 'defaultAutomationModel'; + applyDefaultModel?: boolean; } export function useInitialConversationState( @@ -64,20 +68,39 @@ export function useInitialConversationState( autoApproveByDefault = false, options: InitialConversationStateOptions = {} ): InitialConversationState { - const { resetPromptOnProjectChange = true } = options; + const { + resetPromptOnProjectChange = true, + defaultAgentSettingKey = 'defaultAgent', + defaultModelSettingKey = 'defaultModel', + applyDefaultModel = true, + } = options; const connectionId = projectId ? getProjectSshConnectionId(projectId) : undefined; - const { providerId, setProviderOverride } = useEffectiveProvider(connectionId, initialProvider); + const { providerId, defaultProviderId, setProviderOverride } = useEffectiveProvider( + connectionId, + initialProvider, + defaultAgentSettingKey + ); + const { value: defaultModelValue } = useAppSettingsKey(defaultModelSettingKey); const chatUiFeatureEnabled = useFeatureFlag('chat-ui'); const [prompt, setPrompt] = useState(''); const [issueContext, setIssueContext] = useState(null); const [autoApproveOverride, setAutoApproveOverride] = useState(null); const [issueContextEditorOpen, setIssueContextEditorOpen] = useState(false); const [model, setModel] = useState(null); + const [modelTouched, setModelTouched] = useState(false); const [issueMentionContexts, setIssueMentionContexts] = useState>({}); const [useChatUiPreference, setUseChatUiPreference] = useLocalStorage( 'initial-conversation:chat-ui-enabled', true ); + const modelOptions = useModelOptions(providerId); + const defaultModel = + applyDefaultModel && + providerId === defaultProviderId && + typeof defaultModelValue === 'string' && + modelOptions?.[defaultModelValue] + ? defaultModelValue + : null; const [prevProjectId, setPrevProjectId] = useState(projectId); const [prevProviderId, setPrevProviderId] = useState(providerId); @@ -94,16 +117,27 @@ export function useInitialConversationState( setAutoApproveOverride(null); setIssueContextEditorOpen(false); setModel(null); + setModelTouched(false); setIssueMentionContexts({}); } else if (providerChanged) { setPrevProviderId(providerId); setModel(null); + setModelTouched(false); } + useEffect(() => { + if (!applyDefaultModel) return; + if (!modelTouched) setModel(defaultModel); + }, [applyDefaultModel, defaultModel, modelTouched]); + const autoApproveSupported = providerId ? providerSupportsAutoApprove(providerId) : false; const autoApprove = autoApproveSupported && (autoApproveOverride ?? autoApproveByDefault); const acpSupported = chatUiFeatureEnabled && providerId ? providerSupportsAcp(providerId) : false; const useChatUi = acpSupported && useChatUiPreference; + const setSelectedModel = (nextModel: string | null) => { + setModelTouched(true); + setModel(nextModel); + }; return { provider: providerId, @@ -118,7 +152,7 @@ export function useInitialConversationState( issueContextEditorOpen, setIssueContextEditorOpen, model, - setModel, + setModel: setSelectedModel, connectionId, useChatUi, setUseChatUi: setUseChatUiPreference, diff --git a/apps/emdash-desktop/src/renderer/lib/components/agent-selector/agent-info-card.tsx b/apps/emdash-desktop/src/renderer/lib/components/agent-selector/agent-info-card.tsx index 54858204d2..68fb5c4915 100644 --- a/apps/emdash-desktop/src/renderer/lib/components/agent-selector/agent-info-card.tsx +++ b/apps/emdash-desktop/src/renderer/lib/components/agent-selector/agent-info-card.tsx @@ -1,5 +1,6 @@ import { ExternalLink } from 'lucide-react'; import React from 'react'; +import { DefaultModelSelect } from '@renderer/features/settings/agents-page/DefaultModelSelect'; import { InstallSection } from '@renderer/features/settings/agents-page/InstallSection'; import { useAppSettingsKey } from '@renderer/features/settings/use-app-settings-key'; import { AgentIcon } from '@renderer/lib/components/agent-icon'; @@ -37,6 +38,10 @@ export const AgentInfoCard: React.FC = ({ id, connectionId }) => { const isInstalled = (statusData?.status ?? payload?.status) === 'available'; const isDefaultAgent = defaultAgent === id; + const modelOptions = + payload?.capabilities.models.kind === 'selectable' + ? payload.capabilities.models.modelOptions + : null; function handleSetDefaultAgent(checked: boolean) { if (!checked || isDefaultAgent) return; @@ -67,20 +72,34 @@ export const AgentInfoCard: React.FC = ({ id, connectionId }) => {

{description}

) : null} -
- - +
+
+ + +
+ {isDefaultAgent && modelOptions ? ( +
+ Default model + +
+ ) : null}
{payload && (