diff --git a/src/renderer/src/components/settings/AgentDefaultSetting.tsx b/src/renderer/src/components/settings/AgentDefaultSetting.tsx
index c83887fba21..ed21c9d971d 100644
--- a/src/renderer/src/components/settings/AgentDefaultSetting.tsx
+++ b/src/renderer/src/components/settings/AgentDefaultSetting.tsx
@@ -4,7 +4,13 @@ import type { AgentCatalogEntry } from '@/lib/agent-catalog'
import { AgentIcon } from '@/lib/agent-catalog'
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
-import { SettingsSubsectionHeader } from './SettingsFormControls'
+import { SettingsSegmentedControl, SettingsSubsectionHeader } from './SettingsFormControls'
+
+export type DefaultAgentOpensAs = {
+ agentLabel: string
+ value: 'chat' | 'cli'
+ onChange: (value: 'chat' | 'cli') => void
+}
function DefaultAgentPill({
active,
@@ -41,6 +47,7 @@ export function AgentDefaultSetting({
enabledDetectedAgents,
catalog,
description,
+ opensAs,
onSetDefault
}: {
defaultAgent: TuiAgent | 'blank' | null
@@ -48,6 +55,8 @@ export function AgentDefaultSetting({
enabledDetectedAgents: AgentCatalogEntry[]
catalog: AgentCatalogEntry[]
description: string
+ /** Present only when the default agent can open as a chat on this computer. */
+ opensAs?: DefaultAgentOpensAs
onSetDefault: (agent: TuiAgent | 'blank' | null) => void
}): React.JSX.Element {
const storedDefaultAgent =
@@ -97,6 +106,29 @@ export function AgentDefaultSetting({
)
})}
+ {opensAs ? (
+
+
+ value={opensAs.value}
+ onChange={opensAs.onChange}
+ ariaLabel={translate('settings.agents.defaultOpensAs', 'Open {{value0}} as', {
+ value0: opensAs.agentLabel
+ })}
+ size="sm"
+ options={[
+ { value: 'chat', label: translate('settings.agents.defaultOpensAsChat', 'Chat') },
+ { value: 'cli', label: translate('settings.agents.defaultOpensAsCli', 'CLI') }
+ ]}
+ />
+
+ {translate(
+ 'settings.agents.defaultOpensAsScope',
+ 'Applies to new {{value0}} sessions launched from this computer, including on paired hosts. Matches its Providers switch.',
+ { value0: opensAs.agentLabel }
+ )}
+
+
+ ) : null}
)
}
diff --git a/src/renderer/src/components/settings/AgentLaunchSettingsCatalog.tsx b/src/renderer/src/components/settings/AgentLaunchSettingsCatalog.tsx
index 1f674228a7f..20982ffcd14 100644
--- a/src/renderer/src/components/settings/AgentLaunchSettingsCatalog.tsx
+++ b/src/renderer/src/components/settings/AgentLaunchSettingsCatalog.tsx
@@ -21,6 +21,10 @@ import { AgentDetectionCatalog } from './AgentDetectionCatalog'
import type { AgentCatalogRowProps } from './AgentCatalogRow'
import { HostAgentEnvironmentEditor } from './HostAgentEnvironmentEditor'
import { buildCodexSessionSourceHomeControl } from './codex-session-source-home-control'
+import { buildDefaultAgentOpensAs } from './native-chat-provider-choice'
+import { useHostStructuredAgentsVersion } from '@/lib/chat-provider-agents'
+import { useAppStore } from '@/store'
+import { readNativeDetectedAgents } from '@/store/slices/local-detected-agent-store-state'
import { translate } from '@/i18n/i18n'
export type AgentPermissionsRenderer = (props: AgentPermissionsSettingProps) => React.ReactNode
@@ -56,6 +60,8 @@ export function AgentLaunchSettingsCatalog({
refresh
} = useDetectedAgents(detectionTarget)
const detectedIds = useMemo(() => (detected ? new Set(detected) : null), [detected])
+ useHostStructuredAgentsVersion()
+ const installedHere = useAppStore(readNativeDetectedAgents)
const catalog = getAgentCatalog()
const detectedAgents =
detectedIds === null ? [] : catalog.filter((agent) => detectedIds.has(agent.id))
@@ -138,6 +144,17 @@ export function AgentLaunchSettingsCatalog({
'settings.agents.hostDefaults',
'Defaults for new agents on the selected host.'
)}
+ // The chat-or-CLI choice is this computer's, so a paired host's page doesn't offer it.
+ opensAs={
+ local
+ ? buildDefaultAgentOpensAs({
+ defaultAgent: settings.defaultTuiAgent,
+ settings: local.settings,
+ updateSettings: local.updateSettings,
+ installedHere
+ })
+ : undefined
+ }
onSetDefault={(agent) => save({ type: 'default', agent })}
/>
{renderPermissions({
diff --git a/src/renderer/src/components/settings/NativeChatProvidersSetting.tsx b/src/renderer/src/components/settings/NativeChatProvidersSetting.tsx
index da426f076bc..bed0faaf52b 100644
--- a/src/renderer/src/components/settings/NativeChatProvidersSetting.tsx
+++ b/src/renderer/src/components/settings/NativeChatProvidersSetting.tsx
@@ -1,10 +1,7 @@
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target'
-import {
- buildNativeChatProviderUpdate,
- isNativeChatProviderDisplayedOn
-} from '../../../../shared/native-chat-providers'
+import { isNativeChatProviderDisplayedOn } from '../../../../shared/native-chat-providers'
import type { TuiAgent } from '../../../../shared/tui-agent'
import { isTuiAgentEnabled } from '../../../../shared/tui-agent-selection'
import { translate } from '@/i18n/i18n'
@@ -13,7 +10,7 @@ import { chatProviderAgents, useHostStructuredAgentsVersion } from '@/lib/chat-p
import { useManagedRuntimeInstall } from '@/runtime/use-managed-runtime-install'
import { useAppStore } from '../../store'
import { readNativeDetectedAgents } from '@/store/slices/local-detected-agent-store-state'
-import { createAgentSettingsUpdateQueue } from './agent-availability-settings'
+import { saveNativeChatProviderChoice } from './native-chat-provider-choice'
import { Card, CardContent } from '../ui/card'
import { ManagedRuntimeInstallRow } from '../managed-runtime/ManagedRuntimeInstallRow'
import { SearchableSetting } from './SearchableSetting'
@@ -27,8 +24,6 @@ import {
} from './AntigravityProviderSignInArea'
import { getHostRuntimeLabel, getRuntimeSentenceLabel } from './accounts-pane-runtime'
-const enqueueProviderUpdate = createAgentSettingsUpdateQueue()
-
// The agents listed here are this computer's, so their downloaded programs are too.
const CHAT_HOST: RuntimeClientTarget = { kind: 'local' }
@@ -193,16 +188,14 @@ export function NativeChatProvidersSetting({
'Open {{value0}} as a chat',
{ value0: label }
)}
- onChange={() => {
- const next = !on
- return enqueueProviderUpdate({
- getSettings: () => useAppStore.getState().settings,
+ onChange={() =>
+ saveNativeChatProviderChoice({
+ agent,
+ on: !on,
fallbackSettings: settings,
- updateSettings,
- buildUpdate: (latest) =>
- buildNativeChatProviderUpdate(latest, agent, next)
+ updateSettings
})
- }}
+ }
/>
{/* Chats sign in on the host they run on, under the program that serves them. */}
diff --git a/src/renderer/src/components/settings/native-chat-provider-choice.test.tsx b/src/renderer/src/components/settings/native-chat-provider-choice.test.tsx
new file mode 100644
index 00000000000..d429124df5d
--- /dev/null
+++ b/src/renderer/src/components/settings/native-chat-provider-choice.test.tsx
@@ -0,0 +1,59 @@
+// @vitest-environment happy-dom
+import { cleanup, fireEvent, render, screen } from '@testing-library/react'
+import { afterEach, describe, expect, it, vi } from 'vitest'
+import { createGlobalSettingsFixture } from '../../../../shared/global-settings-test-fixture'
+import { AgentDefaultSetting } from './AgentDefaultSetting'
+import { buildDefaultAgentOpensAs } from './native-chat-provider-choice'
+
+const store = vi.hoisted((): { settings: unknown } => ({ settings: null }))
+vi.mock('../../store', () => ({ useAppStore: { getState: () => store } }))
+afterEach(cleanup)
+
+function renderDefault(settings: ReturnType) {
+ const updateSettings = vi.fn()
+ store.settings = settings
+ render(
+
+ )
+ return updateSettings
+}
+
+describe('default agent chat or CLI', () => {
+ it("flips the default agent's own chat switch, the one Providers and the + menu read", async () => {
+ const updateSettings = renderDefault(
+ createGlobalSettingsFixture({
+ experimentalNativeChat: true,
+ defaultTuiAgent: 'claude',
+ nativeChatProviders: { codex: false }
+ })
+ )
+ const group = screen.getByRole('radiogroup', { name: 'Open Claude as' })
+ expect(group.querySelector('[aria-checked="true"]')?.textContent).toBe('Chat')
+ fireEvent.click(screen.getByRole('radio', { name: 'CLI' }))
+ await vi.waitFor(() =>
+ expect(updateSettings).toHaveBeenCalledWith({
+ nativeChatProviders: { codex: false, claude: false }
+ })
+ )
+ })
+
+ it('offers no choice while Chat UI is off', () => {
+ renderDefault(
+ createGlobalSettingsFixture({ experimentalNativeChat: false, defaultTuiAgent: 'claude' })
+ )
+ expect(screen.queryByRole('radiogroup')).toBeNull()
+ })
+})
diff --git a/src/renderer/src/components/settings/native-chat-provider-choice.ts b/src/renderer/src/components/settings/native-chat-provider-choice.ts
new file mode 100644
index 00000000000..ee39e1c47fc
--- /dev/null
+++ b/src/renderer/src/components/settings/native-chat-provider-choice.ts
@@ -0,0 +1,74 @@
+import { isAgentSessionHandleProvider } from '../../../../shared/agent-session-provider-handle'
+import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
+import type { GlobalSettings } from '../../../../shared/global-settings-types'
+import { buildNativeChatProviderUpdate } from '../../../../shared/native-chat-providers'
+import {
+ isNativeChatDefaultFor,
+ isNativeChatEnabled
+} from '../../../../shared/structured-native-chat-launch-route'
+import type { TuiAgent } from '../../../../shared/tui-agent'
+import { isTuiAgentEnabled } from '../../../../shared/tui-agent-selection'
+import { getAgentLabel } from '@/lib/agent-catalog'
+import { chatProviderAgents } from '@/lib/chat-provider-agents'
+import { useAppStore } from '../../store'
+import { createAgentSettingsUpdateQueue } from './agent-availability-settings'
+import type { DefaultAgentOpensAs } from './AgentDefaultSetting'
+
+// One queue for every settings row that writes this map, so back-to-back flips never overwrite each other.
+const enqueueProviderUpdate = createAgentSettingsUpdateQueue()
+
+export function saveNativeChatProviderChoice({
+ agent,
+ on,
+ fallbackSettings,
+ updateSettings
+}: {
+ agent: TuiAgent
+ on: boolean
+ fallbackSettings: GlobalSettings
+ updateSettings: (updates: Partial) => void | Promise
+}): Promise {
+ return enqueueProviderUpdate({
+ getSettings: () => useAppStore.getState().settings,
+ fallbackSettings,
+ updateSettings,
+ buildUpdate: (latest) => buildNativeChatProviderUpdate(latest, agent, on)
+ })
+}
+
+/** The default agent's chat-or-CLI choice, offered where the stored value and the Providers switch agree. */
+export function buildDefaultAgentOpensAs({
+ defaultAgent,
+ settings,
+ updateSettings,
+ installedHere
+}: {
+ defaultAgent: TuiAgent | 'blank' | null
+ settings: GlobalSettings
+ updateSettings: (updates: Partial) => void | Promise
+ installedHere: readonly TuiAgent[] | null
+}): DefaultAgentOpensAs | undefined {
+ if (defaultAgent === null || defaultAgent === 'blank' || !isNativeChatEnabled(settings)) {
+ return undefined
+ }
+ const agent = defaultAgent
+ // Built-ins or agents in this computer's own inventory: elsewhere Providers shows a different unset value.
+ const offered =
+ chatProviderAgents(LOCAL_EXECUTION_HOST_ID, undefined).includes(agent) &&
+ (isAgentSessionHandleProvider(agent) || installedHere?.includes(agent) === true) &&
+ isTuiAgentEnabled(agent, settings.disabledTuiAgents)
+ if (!offered) {
+ return undefined
+ }
+ return {
+ agentLabel: getAgentLabel(agent),
+ value: isNativeChatDefaultFor(settings, agent) ? 'chat' : 'cli',
+ onChange: (value) =>
+ void saveNativeChatProviderChoice({
+ agent,
+ on: value === 'chat',
+ fallbackSettings: settings,
+ updateSettings
+ })
+ }
+}
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index 889f88b7c67..ab8d0db9476 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -405,6 +405,10 @@
"readFailed": "Couldn’t read agent settings. Check the host connection and try again.",
"saveFailed": "Couldn’t save agent settings. Check the host connection and try again.",
"hostDefaults": "Defaults for new agents on the selected host.",
+ "defaultOpensAs": "Open {{value0}} as",
+ "defaultOpensAsChat": "Chat",
+ "defaultOpensAsCli": "CLI",
+ "defaultOpensAsScope": "Applies to new {{value0}} sessions launched from this computer, including on paired hosts. Matches its Providers switch.",
"save": "Save",
"clearDefault": "Clear default agent",
"autoPermissions": "Auto",
diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json
index f4056104fe8..eb0ce0077b4 100644
--- a/src/renderer/src/i18n/locales/es.json
+++ b/src/renderer/src/i18n/locales/es.json
@@ -211,6 +211,10 @@
"readFailed": "No se pudo leer la configuración de agentes. Comprueba la conexión con el host e inténtalo de nuevo.",
"saveFailed": "No se pudo guardar la configuración de agentes. Comprueba la conexión con el host e inténtalo de nuevo.",
"hostDefaults": "Valores predeterminados para nuevos agentes en el host seleccionado.",
+ "defaultOpensAs": "Abrir {{value0}} como",
+ "defaultOpensAsChat": "Chat",
+ "defaultOpensAsCli": "CLI",
+ "defaultOpensAsScope": "Se aplica a las nuevas sesiones de {{value0}} iniciadas desde este equipo, incluso en hosts emparejados. Coincide con su interruptor en Proveedores.",
"save": "Guardar",
"clearDefault": "Borrar agente predeterminado"
},
diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json
index 6ca667a54c1..08d1b8e986a 100644
--- a/src/renderer/src/i18n/locales/fr.json
+++ b/src/renderer/src/i18n/locales/fr.json
@@ -315,6 +315,10 @@
"readFailed": "Impossible de lire les paramètres des agents. Vérifiez la connexion à l’hôte et réessayez.",
"saveFailed": "Impossible d’enregistrer les paramètres des agents. Vérifiez la connexion à l’hôte et réessayez.",
"hostDefaults": "Paramètres par défaut des nouveaux agents sur l’hôte sélectionné.",
+ "defaultOpensAs": "Ouvrir {{value0}} en tant que",
+ "defaultOpensAsChat": "Chat",
+ "defaultOpensAsCli": "CLI",
+ "defaultOpensAsScope": "S’applique aux nouvelles sessions {{value0}} lancées depuis cet ordinateur, y compris sur les hôtes associés. Correspond à son interrupteur dans Fournisseurs.",
"save": "Enregistrer",
"clearDefault": "Effacer l'agent par défaut"
},
diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json
index 476dd5275b1..9f6d16c9a45 100644
--- a/src/renderer/src/i18n/locales/ja.json
+++ b/src/renderer/src/i18n/locales/ja.json
@@ -211,6 +211,10 @@
"readFailed": "Agent設定を読み込めませんでした。ホストへの接続を確認して再試行してください。",
"saveFailed": "Agent設定を保存できませんでした。ホストへの接続を確認して再試行してください。",
"hostDefaults": "選択したホストで起動する新しいAgentの既定値。",
+ "defaultOpensAs": "{{value0}}の開き方",
+ "defaultOpensAsChat": "チャット",
+ "defaultOpensAsCli": "CLI",
+ "defaultOpensAsScope": "このコンピューターから起動する新しい{{value0}}セッション(ペアリングしたホスト上も含む)に適用されます。プロバイダーのスイッチと連動します。",
"save": "保存",
"clearDefault": "デフォルトの Agent をクリア"
},
diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json
index 88438cb4542..1fbca31eb57 100644
--- a/src/renderer/src/i18n/locales/ko.json
+++ b/src/renderer/src/i18n/locales/ko.json
@@ -211,6 +211,10 @@
"readFailed": "에이전트 설정을 읽을 수 없습니다. 호스트 연결을 확인하고 다시 시도하세요.",
"saveFailed": "에이전트 설정을 저장할 수 없습니다. 호스트 연결을 확인하고 다시 시도하세요.",
"hostDefaults": "선택한 호스트의 새 에이전트 기본값입니다.",
+ "defaultOpensAs": "{{value0}} 여는 방식",
+ "defaultOpensAsChat": "채팅",
+ "defaultOpensAsCli": "CLI",
+ "defaultOpensAsScope": "이 컴퓨터에서 시작하는 새 {{value0}} 세션(페어링된 호스트 포함)에 적용됩니다. 공급자의 스위치와 같습니다.",
"save": "저장",
"clearDefault": "기본 에이전트 지우기"
},
diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json
index 900ed3aff66..2c7bbbb1486 100644
--- a/src/renderer/src/i18n/locales/zh.json
+++ b/src/renderer/src/i18n/locales/zh.json
@@ -211,6 +211,10 @@
"readFailed": "无法读取智能体设置。请检查主机连接并重试。",
"saveFailed": "无法保存智能体设置。请检查主机连接并重试。",
"hostDefaults": "所选主机上新智能体的默认设置。",
+ "defaultOpensAs": "{{value0}} 的打开方式",
+ "defaultOpensAsChat": "聊天",
+ "defaultOpensAsCli": "CLI",
+ "defaultOpensAsScope": "适用于从这台电脑启动的新 {{value0}} 会话,包括在已配对主机上启动的会话。与提供方中的开关一致。",
"save": "保存",
"clearDefault": "清除默认智能体"
},