Settings: choose Chat or CLI in each agent's row; remove Chat → Providers (#36185)

* feat(settings): choose chat or CLI per agent in the Agents list

Each agent's open-as-chat choice lived in two places: Settings > Chat >
Providers and a Chat | CLI control under Default Agent. The choice now
lives only in each chat agent's row of Settings > Agents > Installed,
writing the same per-agent setting. Agents whose chat runs without their
CLI (a downloaded chat runtime) are listed as installed with CLI
unavailable, and the downloaded runtime and chat sign-in move into the
agent's row. Search and the install card's hint point at Agents. Shown
only while Chat UI is on, as before.

* Preserve agent choices and setup across inventory changes

* Fix settings regression test type conformity
This commit is contained in:
Brennan Benson
2026-10-29 20:07:00 -07:00
committed by GitHub
parent 1ed54cf4b5
commit ca2148a30d
32 changed files with 1046 additions and 685 deletions
@@ -15,7 +15,7 @@ Existing terminal-backed chats use a transcript view over an agent running in a
1. Turn **Chat UI** on.
1. Open a new supported agent tab to start in structured chat. Turn **Chat UI** off to open new agents in the terminal instead. Existing chats stay available.
You can also choose which supported agents open as chats under **Settings → Chat → Providers**. Turning an agent off there makes its new sessions open in the terminal.
You can also choose whether each supported agent opens as a chat in **Settings → Agents**: each agent's row in the **Installed** list has a **Chat | CLI** choice. Choosing **CLI** makes its new sessions open in the terminal. Agents whose chat runs on a program Orca downloads, such as Antigravity, show that download and their chat sign-in when you expand their row.
## Continue a conversation in chat or the terminal
@@ -33,7 +33,7 @@ describe('managedRuntimeInstallCard', () => {
title: 'Install Fake to start chatting',
detail:
"One-time 112 MB download. It's shared by Fake chats on the computer running this chat.",
hint: 'Also in Settings › Chat › Providers',
hint: 'Also in Settings › Agents',
action: { kind: 'install', label: 'Install Fake' }
})
expect(managedRuntimeInstallCard(idle, 'Fake', 'strip')).toMatchObject({
@@ -140,10 +140,7 @@ function offerCard(
const label = hero
? translate('components.managed-runtime.install', 'Install {{agent}}', { agent })
: translate('components.managed-runtime.installShort', 'Install')
const hint = translate(
'components.managed-runtime.settingsHint',
'Also in Settings › Chat › Providers'
)
const hint = translate('components.managed-runtime.settingsHint', 'Also in Settings › Agents')
return {
tone: 'offer',
title,
@@ -27,6 +27,8 @@ import {
type AgentPermissionChoiceRow
} from './AgentPermissionChoiceControls'
import { openAppHttpLinkFromAnchor } from '@/lib/app-http-link-open'
import type { AgentOpensAs } from './native-chat-provider-choice'
import { AgentOpensAsControl } from './AgentChatChoiceControls'
type AgentAvailability = 'enabled' | 'disabled'
@@ -96,6 +98,10 @@ export type AgentCatalogRowProps = {
/** The text a terminal launch gets, when it differs from the editable Arguments. */
launchArgs?: string
environmentEditor?: React.ReactNode
/** Whether new sessions open as a chat or the CLI; absent where this computer offers no choice. */
opensAs?: AgentOpensAs
/** The downloaded chat program's install and sign-in, shown in the row's details. */
chatRuntime?: React.ReactNode
}
export function AgentCatalogRow({
@@ -122,7 +128,9 @@ export function AgentCatalogRow({
environmentEditor,
permission,
permissionChoice,
launchArgs = argsOverride
launchArgs = argsOverride,
opensAs,
chatRuntime
}: AgentCatalogRowProps): React.JSX.Element {
const envSummary = environmentNames?.join(' ') ?? stringifyAgentDefaultEnvDraft(envOverride)
const defaultEnvSummary = stringifyAgentDefaultEnvDraft(defaultEnv)
@@ -147,10 +155,14 @@ export function AgentCatalogRow({
)?.focus()
})
}
const expandable = isDetected || permission !== undefined || permissionChoice !== undefined
const expandable =
isDetected ||
permission !== undefined ||
permissionChoice !== undefined ||
chatRuntime !== undefined
return (
<div className={cn('py-3', !isDetected && 'opacity-70')}>
<div className={cn('py-3', !isDetected && !opensAs?.cliMissing && 'opacity-70')}>
<div className="flex flex-wrap items-start gap-3">
<div className="flex size-7 shrink-0 items-center justify-center rounded-md border border-border/50 bg-background/50">
<AgentIcon agent={agentId} size={16} />
@@ -201,6 +213,7 @@ export function AgentCatalogRow({
/>
</div>
)}
{opensAs && <AgentOpensAsControl label={label} opensAs={opensAs} />}
<AgentAvailabilityControl
label={label}
isEnabled={isEnabled}
@@ -273,6 +286,7 @@ export function AgentCatalogRow({
{expandable && cmdOpen && (
<div className="mt-3 space-y-3 pl-10">
{chatRuntime}
{permissionChoice && (
<div ref={permissionRef}>
<AgentPermissionMenu agentId={agentId} label={label} permission={permissionChoice} />
@@ -0,0 +1,97 @@
import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target'
import type { TuiAgent } from '../../../../shared/tui-agent'
import { translate } from '@/i18n/i18n'
import { antigravityChatSignInRunning } from '@/runtime/antigravity-chat-sign-in-status'
import { useManagedRuntimeInstall } from '@/runtime/use-managed-runtime-install'
import { ManagedRuntimeInstallRow } from '../managed-runtime/ManagedRuntimeInstallRow'
import { useAntigravityChatSignIn } from './AntigravityChatSignInRow'
import {
AntigravityProviderSignInArea,
antigravityProviderSignInShown
} from './AntigravityProviderSignInArea'
import { getHostRuntimeLabel, getRuntimeSentenceLabel } from './accounts-pane-runtime'
import type { AgentOpensAs } from './native-chat-provider-choice'
import { SettingsSegmentedControl } from './SettingsFormControls'
// The chat choice is this computer's, so the downloaded chat programs are too.
const CHAT_HOST: RuntimeClientTarget = { kind: 'local' }
export function AgentOpensAsControl({
label,
opensAs
}: {
label: string
opensAs: AgentOpensAs
}): React.JSX.Element {
return (
<SettingsSegmentedControl<'chat' | 'cli'>
value={opensAs.value}
onChange={opensAs.onChange}
ariaLabel={translate('settings.agents.defaultOpensAs', 'Open {{value0}} as', {
value0: label
})}
size="sm"
options={[
{ value: 'chat', label: translate('settings.agents.defaultOpensAsChat', 'Chat') },
{
value: 'cli',
label: translate('settings.agents.defaultOpensAsCli', 'CLI'),
disabled: opensAs.cliMissing,
tooltip: opensAs.cliMissing
? translate(
'settings.agents.installCliForTerminal',
'Install the {{value0}} CLI to open it in a terminal.',
{ value0: label }
)
: undefined
}
]}
/>
)
}
/** The program Orca downloads for an agent's chats, apart from its CLI; mounted only while its row is open. */
export function AgentChatRuntimeArea({
agent,
label
}: {
agent: TuiAgent
label: string
}): React.JSX.Element | null {
const runtime = useManagedRuntimeInstall(CHAT_HOST, agent, true)
// Install and Remove wait for a running sign-in, which runs the installed server.
const signIn = useAntigravityChatSignIn(
agent === 'antigravity' && runtime.kind === 'active' ? CHAT_HOST : null
)
if (runtime.kind !== 'active') {
return null
}
return (
<div className="space-y-2">
<ManagedRuntimeInstallRow
observation={runtime}
agentLabel={label}
signInRunning={antigravityChatSignInRunning(signIn.flow)}
>
{(line, actions) => (
<div className="flex items-center gap-3">
<div className="min-w-0 flex-1">
<p className="text-xs font-medium leading-none">
{translate('settings.agents.chatRuntime', 'Chat runtime')}
</p>
<div className="mt-1">{line}</div>
</div>
{actions}
</div>
)}
</ManagedRuntimeInstallRow>
{agent === 'antigravity' && antigravityProviderSignInShown(runtime) ? (
<AntigravityProviderSignInArea
owner={CHAT_HOST}
label={getRuntimeSentenceLabel({ runtime: 'host', label: getHostRuntimeLabel() })}
install={runtime}
/>
) : null}
</div>
)
}
@@ -4,13 +4,7 @@ import type { AgentCatalogEntry } from '@/lib/agent-catalog'
import { AgentIcon } from '@/lib/agent-catalog'
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
import { SettingsSegmentedControl, SettingsSubsectionHeader } from './SettingsFormControls'
export type DefaultAgentOpensAs = {
agentLabel: string
value: 'chat' | 'cli'
onChange: (value: 'chat' | 'cli') => void
}
import { SettingsSubsectionHeader } from './SettingsFormControls'
function DefaultAgentPill({
active,
@@ -47,7 +41,6 @@ export function AgentDefaultSetting({
enabledDetectedAgents,
catalog,
description,
opensAs,
onSetDefault,
defaultOpensAsChat = false
}: {
@@ -58,8 +51,6 @@ 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 =
@@ -110,29 +101,6 @@ export function AgentDefaultSetting({
)
})}
</div>
{opensAs ? (
<div className="flex flex-wrap items-center gap-3">
<SettingsSegmentedControl<'chat' | 'cli'>
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') }
]}
/>
<p className="text-xs text-muted-foreground">
{translate(
'settings.agents.defaultOpensAsScope',
'Applies to new {{value0}} sessions launched from this computer, including on paired hosts. Matches its Providers switch.',
{ value0: opensAs.agentLabel }
)}
</p>
</div>
) : null}
</section>
)
}
@@ -1,3 +1,4 @@
import { Fragment } from 'react'
import { AlertTriangle, RefreshCw } from 'lucide-react'
import type { AgentCatalogEntry } from '@/lib/agent-catalog'
import { cn } from '@/lib/utils'
@@ -5,6 +6,7 @@ import { translate } from '@/i18n/i18n'
import { Button } from '../ui/button'
import { AgentCatalogRow, type AgentCatalogRowProps } from './AgentCatalogRow'
import { SettingsBadge, SettingsSubsectionHeader } from './SettingsFormControls'
import { AGENTS_INSTALLED_SECTION_ID } from './agents-search'
export function AgentDetectionCatalog({
detectedAgents,
@@ -41,83 +43,99 @@ export function AgentDetectionCatalog({
</div>
)}
{detectedAgents.length > 0 && (
<section className="space-y-3">
<SettingsSubsectionHeader
title={
<span className="flex items-center gap-2">
{translate('auto.components.settings.AgentsPane.02e0143be5', 'Installed')}
<SettingsBadge tone="accent">
{detectedAgents.length}{' '}
{translate('auto.components.settings.AgentsPane.ed3e110e61', 'detected')}
</SettingsBadge>
{activeServerName ? (
<SettingsBadge tone="muted">
{translate('auto.components.settings.AgentsPane.03e1a5081a', 'on {{value0}}', {
value0: activeServerName
})}
</SettingsBadge>
) : null}
</span>
}
action={
<Button
type="button"
variant="ghost"
size="xs"
onClick={onRefresh}
disabled={isRefreshing}
title={
activeServerEnvironmentId
? translate(
'auto.components.settings.AgentsPane.25a41a9aad',
'Re-detect agents installed on this server'
)
: translate(
'auto.components.settings.AgentsPane.13647f9f80',
'Re-read your shell PATH and re-detect installed agents'
)
}
className="h-7 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
>
<RefreshCw className={cn('size-3', isRefreshing && 'animate-spin')} />
{isRefreshing
? translate('auto.components.settings.AgentsPane.c9b33eb5c0', 'Refreshing…')
: translate('auto.components.settings.AgentsPane.0d9e293a02', 'Refresh')}
</Button>
}
/>
<div className="divide-y divide-border/40">
{detectedAgents.map((agent) => (
<AgentCatalogRow key={agent.id} {...getRowProps(agent, true)} />
))}
</div>
</section>
)}
{undetectedAgents.length > 0 && (
<section className="space-y-3">
<SettingsSubsectionHeader
title={
<span className="flex items-center gap-2 text-muted-foreground">
{translate(
'auto.components.settings.AgentsPane.e8da2af684',
'Available to install'
)}
<SettingsBadge tone="muted">
{undetectedAgents.length}{' '}
{translate('auto.components.settings.AgentsPane.024bd95089', 'agents')}
</SettingsBadge>
</span>
}
/>
<div className="divide-y divide-border/40">
{undetectedAgents.map((agent) => (
<AgentCatalogRow key={agent.id} {...getRowProps(agent, false)} />
))}
</div>
</section>
)}
<section id={AGENTS_INSTALLED_SECTION_ID}>
{[
...detectedAgents.map((agent) => ({ agent, installed: true })),
...undetectedAgents.map((agent) => ({ agent, installed: false }))
].map(({ agent, installed }, index) => {
const firstInSection = index === 0 || index === detectedAgents.length
return (
<Fragment key={agent.id}>
{firstInSection ? (
<div className={cn('mb-3', index > 0 && 'mt-8')}>
{installed ? (
<SettingsSubsectionHeader
title={
<span className="flex items-center gap-2">
{translate('auto.components.settings.AgentsPane.02e0143be5', 'Installed')}
<SettingsBadge tone="accent">
{detectedAgents.length}{' '}
{translate(
'auto.components.settings.AgentsPane.ed3e110e61',
'detected'
)}
</SettingsBadge>
{activeServerName ? (
<SettingsBadge tone="muted">
{translate(
'auto.components.settings.AgentsPane.03e1a5081a',
'on {{value0}}',
{
value0: activeServerName
}
)}
</SettingsBadge>
) : null}
</span>
}
action={
<Button
type="button"
variant="ghost"
size="xs"
onClick={onRefresh}
disabled={isRefreshing}
title={
activeServerEnvironmentId
? translate(
'auto.components.settings.AgentsPane.25a41a9aad',
'Re-detect agents installed on this server'
)
: translate(
'auto.components.settings.AgentsPane.13647f9f80',
'Re-read your shell PATH and re-detect installed agents'
)
}
className="h-7 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
>
<RefreshCw className={cn('size-3', isRefreshing && 'animate-spin')} />
{isRefreshing
? translate(
'auto.components.settings.AgentsPane.c9b33eb5c0',
'Refreshing…'
)
: translate(
'auto.components.settings.AgentsPane.0d9e293a02',
'Refresh'
)}
</Button>
}
/>
) : (
<SettingsSubsectionHeader
title={
<span className="flex items-center gap-2 text-muted-foreground">
{translate(
'auto.components.settings.AgentsPane.e8da2af684',
'Available to install'
)}
<SettingsBadge tone="muted">
{undetectedAgents.length}{' '}
{translate('auto.components.settings.AgentsPane.024bd95089', 'agents')}
</SettingsBadge>
</span>
}
/>
)}
</div>
) : null}
<div className={cn(!firstInSection && 'border-t border-border/40')}>
<AgentCatalogRow {...getRowProps(agent, installed)} />
</div>
</Fragment>
)
})}
</section>
{detectionPending && !detectionFailed && (
<div className="flex items-center justify-center rounded-md border border-dashed border-border/50 py-6 text-sm text-muted-foreground">
@@ -40,17 +40,25 @@ import {
} from '../../../../shared/tui-agent-launch-defaults'
import { isTuiAgentEnabled } from '../../../../shared/tui-agent-selection'
import { getAgentCatalog } from '@/lib/agent-catalog'
import { agentChoiceOpensAsChat } from '@/lib/host-chat-program'
import {
agentChoiceOpensAsChat,
readChatProgram,
resolveChatProgramContext
} from '@/lib/host-chat-program'
import { useAppStore } from '@/store'
import { readNativeDetectedAgents } from '@/store/slices/local-detected-agent-store-state'
import { useDetectedAgents, type AgentDetectionTarget } from '@/hooks/useDetectedAgents'
import { AgentDefaultSetting } from './AgentDefaultSetting'
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 { readNativeDetectedAgents } from '@/store/slices/local-detected-agent-store-state'
import { buildAgentOpensAs } from './native-chat-provider-choice'
import { AgentChatRuntimeArea } from './AgentChatChoiceControls'
import { chatProviderAgents, useHostStructuredAgentsVersion } from '@/lib/chat-provider-agents'
import { readHostStructuredAgents } from '@/runtime/host-structured-agents'
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
import { isNativeChatEnabled } from '../../../../shared/structured-native-chat-launch-route'
import { translate } from '@/i18n/i18n'
export type AgentPermissionsRenderer = (props: AgentPermissionsSettingProps) => React.ReactNode
@@ -103,11 +111,42 @@ export function AgentLaunchSettingsCatalog({
? agentChoiceOpensAsChat(state, chosen, workspace, detected)
: false
)
// The chat-or-CLI choice is this computer's, so a paired host's page doesn't offer it.
const chatAgents =
local && isNativeChatEnabled(local.settings)
? chatProviderAgents(LOCAL_EXECUTION_HOST_ID, undefined)
: []
const managedChatAgents = new Set(
readHostStructuredAgents(LOCAL_EXECUTION_HOST_ID, undefined)
?.filter((row) => row.managedRuntime)
.map((row) => row.agent)
)
// Agents whose chat program is here without their CLI, like a downloaded chat runtime, are installed for chat.
const chatOnlyKey = useAppStore((state) => {
if (chatAgents.length === 0) {
return ''
}
const context = resolveChatProgramContext(state, workspace)
return chatAgents
.filter(
(agent) =>
!installedHere?.includes(agent) &&
readChatProgram(state, agent, context, installedHere) === 'available'
)
.join(' ')
})
const chatOnly = new Set(chatOnlyKey.split(' '))
const catalog = getAgentCatalog()
const detectedAgents =
detectedIds === null ? [] : catalog.filter((agent) => detectedIds.has(agent.id))
const installedAgents = catalog.filter(
(agent) => detectedIds?.has(agent.id) || chatOnly.has(agent.id)
)
const undetectedAgents = catalog.filter(
(agent) => detectedIds !== null && !detectedIds.has(agent.id)
(agent) =>
!detectedIds?.has(agent.id) &&
!chatOnly.has(agent.id) &&
(detectedIds !== null || chatAgents.includes(agent.id))
)
const save = (mutation: AgentLaunchSettingsMutation): void => {
const scopedMutation =
@@ -176,7 +215,7 @@ export function AgentLaunchSettingsCatalog({
autoSupported,
save
})
const rowProps = (
const cliRowProps = (
agent: (typeof catalog)[number],
isDetected: boolean
): AgentCatalogRowProps => ({
@@ -231,6 +270,30 @@ export function AgentLaunchSettingsCatalog({
? buildCodexSessionSourceHomeControl(local.settings, local.updateSettings)
: undefined
})
const rowProps = (
agent: (typeof catalog)[number],
listedInstalled: boolean
): AgentCatalogRowProps => {
const isDetected = listedInstalled && detectedIds?.has(agent.id) === true
return {
...cliRowProps(agent, isDetected),
opensAs: local
? buildAgentOpensAs({
agent: agent.id,
cliDetected: installedHere === null ? null : installedHere.includes(agent.id),
chatProgramHere: chatOnly.has(agent.id),
settings: local.settings,
updateSettings: local.updateSettings
})
: undefined,
chatRuntime:
chatAgents.includes(agent.id) &&
managedChatAgents.has(agent.id) &&
isTuiAgentEnabled(agent.id, settings.disabledTuiAgents) ? (
<AgentChatRuntimeArea agent={agent.id} label={agent.label} />
) : undefined
}
}
return (
<>
<AgentDefaultSetting
@@ -244,17 +307,6 @@ 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 })}
defaultOpensAsChat={defaultOpensAsChat}
/>
@@ -285,7 +337,7 @@ export function AgentLaunchSettingsCatalog({
autoSupported
})}
<AgentDetectionCatalog
detectedAgents={detectedAgents}
detectedAgents={installedAgents}
undetectedAgents={undetectedAgents}
detectionPending={detectedIds === null}
detectionFailed={detectionFailed}
@@ -8,8 +8,24 @@ import type {
} from '../../../../shared/antigravity-account-types'
import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target'
import { callAntigravityChatSignIn } from '@/runtime/runtime-antigravity-accounts-client'
import { AntigravityChatSignInRow } from './AntigravityChatSignInRow'
import { AntigravityProviderSignInArea } from './AntigravityProviderSignInArea'
import type * as ManagedRuntimeInstallModule from '@/runtime/managed-runtime-install-status'
import { AgentChatRuntimeArea } from './AgentChatChoiceControls'
import { ManagedRuntimeInstallRow } from '../managed-runtime/ManagedRuntimeInstallRow'
import { antigravityChatSignInRunning } from '@/runtime/antigravity-chat-sign-in-status'
import {
useManagedRuntimeInstall,
type ManagedRuntimeInstallObservation
} from '@/runtime/use-managed-runtime-install'
import {
installManagedRuntime,
removeManagedRuntime
} from '@/runtime/managed-runtime-install-status'
import { getHostRuntimeLabel, getRuntimeSentenceLabel } from './accounts-pane-runtime'
import { AntigravityChatSignInRow, useAntigravityChatSignIn } from './AntigravityChatSignInRow'
import {
AntigravityProviderSignInArea,
antigravityProviderSignInShown
} from './AntigravityProviderSignInArea'
import { TooltipProvider } from '../ui/tooltip'
const url = 'https://accounts.google.com/o/oauth2/v2/auth?state=s'
@@ -74,6 +90,17 @@ vi.mock('@/runtime/runtime-antigravity-accounts-client', () => ({
}
)
}))
const install = vi.hoisted((): { current: ManagedRuntimeInstallObservation } => ({
current: { kind: 'pending' }
}))
vi.mock('@/runtime/use-managed-runtime-install', () => ({
useManagedRuntimeInstall: vi.fn(() => install.current)
}))
vi.mock('@/runtime/managed-runtime-install-status', async (importOriginal) => ({
...(await importOriginal<typeof ManagedRuntimeInstallModule>()),
installManagedRuntime: vi.fn(async () => {}),
removeManagedRuntime: vi.fn(async () => {})
}))
afterEach(() => cleanup())
async function showRow(owner: RuntimeClientTarget) {
@@ -193,3 +220,150 @@ it("names the account and why it's needed before the host knows its sign-in", as
await act(async () => publish(owner, null, 'signedOut'))
expect(screen.getByRole('status').textContent).toMatch(/^Not signed in\./)
})
// Setup composition from the removed provider page; the shared controls own all commands and refusals.
function PreviousProviderSetup() {
const runtime = useManagedRuntimeInstall({ kind: 'local' }, 'antigravity', true)
const signIn = useAntigravityChatSignIn(runtime.kind === 'active' ? { kind: 'local' } : null)
if (runtime.kind !== 'active') {
return null
}
return (
<>
<ManagedRuntimeInstallRow
observation={runtime}
agentLabel="Antigravity"
signInRunning={antigravityChatSignInRunning(signIn.flow)}
>
{(line, actions) => (
<>
{line}
{actions}
</>
)}
</ManagedRuntimeInstallRow>
{antigravityProviderSignInShown(runtime) ? (
<AntigravityProviderSignInArea
owner={{ kind: 'local' }}
label={getRuntimeSentenceLabel({ runtime: 'host', label: getHostRuntimeLabel() })}
install={runtime}
/>
) : null}
</>
)
}
it('preserves moved setup owner, actions and failure states through collapse and a late sign-in reply', async () => {
const owner = { kind: 'local' } as const
const subject = { target: owner, runtimeId: null, agent: 'antigravity' }
for (const previous of [true, false]) {
vi.clearAllMocks()
publish(owner, null, 'signedOut')
const openUrl = vi.fn(async () => undefined)
Object.assign(window, { api: { shell: { openUrl } } })
install.current = { kind: 'active', subject, removable: true, snapshot: { kind: 'readFailed' } }
const element = (expanded = true) => (
<TooltipProvider>
{expanded ? (
previous ? (
<PreviousProviderSetup />
) : (
<AgentChatRuntimeArea agent="antigravity" label="Antigravity" />
)
) : null}
</TooltipProvider>
)
const view = render(element())
await act(async () => undefined)
expect(useManagedRuntimeInstall).toHaveBeenCalledWith(owner, 'antigravity', true)
expect(screen.getByText('Could not check Antigravity on this host. Retrying.')).toBeTruthy()
expect(screen.getByRole('button', { name: 'Sign in' }).hasAttribute('disabled')).toBe(false)
const state = {
agent: 'antigravity',
revision: 1,
available: true,
version: '1',
installedVersion: null,
installedCurrent: false,
operation: null,
source: null
}
install.current = {
kind: 'active',
subject,
removable: true,
snapshot: { kind: 'known', state }
}
view.rerender(element())
expect(screen.queryByText('Account')).toBeNull()
vi.mocked(installManagedRuntime).mockRejectedValueOnce(
new Error('Installation refused. Try again.')
)
await userEvent.click(screen.getByRole('button', { name: 'Install' }))
expect(installManagedRuntime).toHaveBeenCalledExactlyOnceWith(subject)
await screen.findByText('Installation refused. Try again.')
install.current = {
kind: 'active',
subject,
removable: true,
snapshot: {
kind: 'known',
state: {
...state,
source: 'managed',
installedVersion: '1',
installedCurrent: true,
canRemove: true
}
}
}
view.rerender(element())
expect(screen.getByText('Account')).toBeTruthy()
await userEvent.click(screen.getByRole('button', { name: 'More actions for Antigravity' }))
await userEvent.click(screen.getByRole('menuitem', { name: 'Remove downloaded runtime' }))
vi.mocked(removeManagedRuntime).mockRejectedValueOnce(new Error('Removal refused. Try again.'))
await userEvent.click(screen.getByRole('button', { name: 'Remove' }))
expect(removeManagedRuntime).toHaveBeenCalledExactlyOnceWith(subject)
await screen.findByText('Removal refused. Try again.')
vi.mocked(callAntigravityChatSignIn).mockRejectedValueOnce(
new Error('Sign-in refused. Try again.')
)
await userEvent.click(screen.getByRole('button', { name: 'Sign in' }))
expect(callAntigravityChatSignIn).toHaveBeenCalledWith(owner, 'Start')
await screen.findByText('Sign-in refused. Try again.')
const late = Promise.withResolvers<{ flowId: string }>()
fake.start = late.promise
await userEvent.click(screen.getByRole('button', { name: 'Retry sign-in' }))
view.rerender(element(false))
await act(async () => {
late.resolve({ flowId: 'late-moved' })
publish(owner, { flowId: 'late-moved', phase: 'waiting', url })
})
expect(openUrl).not.toHaveBeenCalled()
fake.start = null
view.rerender(element())
await screen.findByText('Finish signing in in your browser.')
await userEvent.click(screen.getByRole('button', { name: 'More actions for Antigravity' }))
expect(
screen
.getByRole('menuitem', { name: 'Remove downloaded runtime' })
.getAttribute('aria-disabled')
).toBe('true')
await userEvent.keyboard('{Escape}')
await userEvent.type(screen.getByRole('textbox'), 'invalid callback')
vi.mocked(callAntigravityChatSignIn).mockRejectedValueOnce(
new Error('Paste the complete browser address.')
)
await userEvent.click(screen.getByRole('button', { name: 'Continue' }))
expect(callAntigravityChatSignIn).toHaveBeenCalledWith(owner, 'Complete', {
flowId: 'late-moved',
callbackUrl: 'invalid callback'
})
await screen.findByText('Paste the complete browser address.')
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }))
expect(callAntigravityChatSignIn).toHaveBeenCalledWith(owner, 'Cancel', {
flowId: 'late-moved'
})
cleanup()
}
})
@@ -46,7 +46,7 @@ export function AntigravityProviderSignInArea(props: {
// A settled failure says only why; a running sign-in keeps its step beside a copy failure.
const line = alert && !ownFlow ? null : signIn.line
return (
<div className="ml-10 space-y-2">
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="min-w-0 flex-1">
<p className="text-xs font-medium leading-none">
@@ -1,12 +1,9 @@
// @vitest-environment happy-dom
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { getDefaultSettings } from '../../../../shared/constants'
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import type { TuiAgent } from '../../../../shared/tui-agent'
import { decodeAgentSessionAgentsResult } from '../../../../shared/agent-session/agent-session-registered-agents'
import { ChatSettingsSection } from './ChatSettingsSection'
import { NativeChatProvidersSetting } from './NativeChatProvidersSetting'
import { TooltipProvider } from '../ui/tooltip'
import { ActiveSettingsSectionProvider } from './SettingsSection'
import { getChatAppearanceSearchEntries } from './chat-appearance-search'
@@ -15,26 +12,16 @@ import { getChatInlineVisualsSearchEntry } from './chat-inline-visuals-search'
import { getChatBehaviorSearchEntries } from './chat-behavior-search'
import { AGENT_SESSION_QUEUED_MESSAGES_RUNTIME_CAPABILITY } from '../../../../shared/protocol-version'
import { setLocalRuntimeCapabilitiesForTests } from '@/runtime/local-runtime-capabilities'
import type * as HostStructuredAgentsModule from '@/runtime/host-structured-agents'
import { i18n } from '@/i18n/i18n'
import { buildSettingsNavigationMetadata } from '@/hooks/useSettingsNavigationMetadata'
import { buildCmdJSettingsResults } from '../cmd-j/palette-results'
import { isSettingsNavigationTarget } from '@/lib/settings-navigation-types'
import { getSettingsSectionId, getSettingsScrollTarget } from './settings-navigation-foundations'
const state = vi.hoisted(
(): {
settingsSearchQuery: string
settings: GlobalSettings | null
localDetectedAgentIdsByContext: Record<string, TuiAgent[] | null>
} => ({ settingsSearchQuery: '', settings: null, localDetectedAgentIdsByContext: {} })
)
vi.mock('@/runtime/host-structured-agents', async (importOriginal) => ({
...(await importOriginal<typeof HostStructuredAgentsModule>()),
readHostStructuredAgents: () =>
decodeAgentSessionAgentsResult({ agents: [{ agent: 'grok', capabilities: {} }] }) ?? []
const state = vi.hoisted((): { settingsSearchQuery: string; settings: GlobalSettings | null } => ({
settingsSearchQuery: '',
settings: null
}))
vi.mock('@/lib/host-chat-program-acquisition', () => ({ useChatProgramFacts: () => {} }))
vi.mock('../../store', () => ({
useAppStore: Object.assign((selector: (value: typeof state) => unknown) => selector(state), {
getState: () => state
@@ -49,7 +36,6 @@ afterEach(async () => {
beforeEach(() => {
state.settingsSearchQuery = ''
state.settings = null
state.localDetectedAgentIdsByContext = {}
setLocalRuntimeCapabilitiesForTests([AGENT_SESSION_QUEUED_MESSAGES_RUNTIME_CAPABILITY])
})
@@ -89,36 +75,26 @@ function renderChat(
}
describe('Chat settings page', () => {
it('preserves clicked provider choices while queued saves and inventory changes settle', async () => {
let latest = getDefaultSettings('/tmp')
state.settings = latest
state.localDetectedAgentIdsByContext = { host: [] }
let finish: () => void = () => {}
const delayed = new Promise<void>((resolve) => {
finish = resolve
})
const updateSettings = vi.fn(async (updates: Partial<GlobalSettings>) => {
await delayed
latest = { ...latest, ...updates }
state.settings = latest
})
const element = () => (
<NativeChatProvidersSetting settings={latest} updateSettings={updateSettings} />
)
const { rerender } = render(element())
const claude = screen.getByRole('switch', { name: 'Open Claude as a chat' })
const grok = screen.getByRole('switch', { name: 'Open Grok as a chat' })
expect(grok.getAttribute('aria-checked')).toBe('false')
fireEvent.click(claude)
await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(1))
fireEvent.click(grok)
fireEvent.click(claude)
state.localDetectedAgentIdsByContext = { host: ['grok'] }
rerender(element())
expect(grok.getAttribute('aria-checked')).toBe('true')
finish()
await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(3))
expect(latest.nativeChatProviders).toEqual({ claude: false, grok: true })
it('moves the chat-or-CLI choice from the Chat page to the Agents list and its search', () => {
renderChat(true)
expect(screen.queryByText('Providers')).toBeNull()
expect(screen.queryByRole('switch', { name: /as a chat/ })).toBeNull()
const results = (nativeChatEnabled: boolean) =>
buildCmdJSettingsResults(
buildSettingsNavigationMetadata({
isMac: true,
isWindows: false,
isWebClient: false,
nativeChatEnabled,
repos: []
})
).filter((entry) => entry.targetSectionId === 'agents-installed')
const [choice] = results(true)
expect(choice?.sectionId).toBe('agents')
for (const term of ['chat', 'cli', 'terminal', 'provider']) {
expect(choice?.configKeywords.some((keyword) => keyword.includes(term))).toBe(true)
}
expect(results(false)).toEqual([])
})
it('retains the unsaved naming draft when search no longer matches Chat', () => {
@@ -3,7 +3,6 @@ import { AppearanceChatSection } from './AppearanceChatSection'
import { ChatBehaviorSetting } from './ChatBehaviorSetting'
import { ChatNamingSetting } from './ChatNamingSetting'
import { NativeChatInlineVisualsSetting } from './NativeChatInlineVisualsSetting'
import { NativeChatProvidersSetting } from './NativeChatProvidersSetting'
import { SettingsSection } from './SettingsSection'
import { SettingsSubsectionHeader } from './SettingsFormControls'
import { matchesSettingsSearch } from './settings-search'
@@ -80,13 +79,6 @@ export function ChatSettingsSection({
</Card>
</section>
) : null}
{showDesktopOnlySettings ? (
<NativeChatProvidersSetting
settings={settings}
updateSettings={updateSettings}
forceVisible={matchesSettingsSearch(query, [{ title }])}
/>
) : null}
{showDesktopOnlySettings ? (
<NativeChatInlineVisualsSetting
settings={settings}
@@ -109,11 +109,13 @@ describe('host launch settings form', () => {
it('shows resolved launch defaults in a collapsed legacy host row', async () => {
transport.read.mockResolvedValue(projectAgentLaunchSettings({ agentDefaultArgs: {} }))
render(pane(17))
const installed = (await screen.findByText('Installed')).closest('section')
if (!installed) {
throw new Error('missing installed agents section')
const row = (await screen.findByRole('radiogroup', { name: 'Claude availability' })).closest(
'.py-3'
)
if (!(row instanceof HTMLElement)) {
throw new Error('missing Claude settings row')
}
expect(within(installed).getByText('--dangerously-skip-permissions')).toBeTruthy()
expect(within(row).getByText('--dangerously-skip-permissions')).toBeTruthy()
expect(transport.mutate).not.toHaveBeenCalled()
})
@@ -1,62 +0,0 @@
// @vitest-environment happy-dom
import { render, cleanup } from '@testing-library/react'
import { afterEach, expect, it, vi } from 'vitest'
import { getDefaultSettings } from '../../../../shared/constants'
import type { ManagedRuntimeInstallObservation } from '@/runtime/use-managed-runtime-install'
const observation = vi.hoisted((): { current: ManagedRuntimeInstallObservation } => ({
current: { kind: 'pending' }
}))
vi.mock('@/runtime/use-managed-runtime-install', () => ({
useManagedRuntimeInstall: () => observation.current
}))
vi.mock('@/lib/host-chat-program-acquisition', () => ({ useChatProgramFacts: () => {} }))
vi.mock('@/lib/chat-provider-agents', () => ({
chatProviderAgents: () => ['grok'],
useHostStructuredAgentsVersion: () => 0
}))
vi.mock('./AntigravityChatSignInRow', () => ({ useAntigravityChatSignIn: () => ({ flow: null }) }))
import { useAppStore } from '@/store'
import { NativeChatProvidersSetting } from './NativeChatProvidersSetting'
const initial = useAppStore.getState()
afterEach(() => {
cleanup()
useAppStore.setState(initial, true)
})
it('reserves the switch footprint through unknown and failed reads, then shows managed/external installed and saved false', () => {
useAppStore.setState({ localDetectedAgentIdsByContext: { host: [] } })
const settings = getDefaultSettings('/tmp')
const surface = () => <NativeChatProvidersSetting settings={settings} updateSettings={vi.fn()} />
const view = render(surface())
expect(view.queryByRole('switch')).toBeNull()
expect(view.container.querySelector('.h-5.w-9')).not.toBeNull()
const active = {
kind: 'active',
subject: { target: { kind: 'local' }, runtimeId: null, agent: 'grok' },
removable: false
} as const
observation.current = { ...active, snapshot: { kind: 'readFailed' } }
view.rerender(surface())
expect(view.queryByRole('switch')).toBeNull()
const installed = {
agent: 'grok',
revision: 1,
available: true,
version: '1',
installedVersion: '1',
installedCurrent: true,
operation: null
} as const
for (const source of ['managed', 'external'] as const) {
observation.current = {
...active,
snapshot: { kind: 'known', state: { ...installed, source } }
}
view.rerender(surface())
expect(view.getByRole('switch').getAttribute('aria-checked')).toBe('true')
const icon = view.getByRole('switch').parentElement?.firstElementChild
expect(icon?.classList.contains('size-7')).toBe(true)
}
settings.nativeChatProviders = { grok: false }
view.rerender(surface())
expect(view.getByRole('switch').getAttribute('aria-checked')).toBe('false')
})
@@ -1,239 +0,0 @@
import { useChatProgramFacts } from '@/lib/host-chat-program-acquisition'
import { readHostStructuredAgents } from '@/runtime/host-structured-agents'
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 { isNativeChatProviderDisplayedOn } from '../../../../shared/native-chat/native-chat-providers'
import type { TuiAgent } from '../../../../shared/tui-agent'
import { isTuiAgentEnabled } from '../../../../shared/tui-agent-selection'
import { translate } from '@/i18n/i18n'
import { AgentIcon, getAgentCatalog, getAgentLabel } from '@/lib/agent-catalog'
import { chatProviderAgents, useHostStructuredAgentsVersion } from '@/lib/chat-provider-agents'
import { useManagedRuntimeInstall } from '@/runtime/use-managed-runtime-install'
import { useAppStore } from '../../store'
import { readNativeDetectedAgents } from '@/store/slices/local-detected-agent-store-state'
import { saveNativeChatProviderChoice } from './native-chat-provider-choice'
import { Card, CardContent } from '../ui/card'
import { ManagedRuntimeInstallRow } from '../managed-runtime/ManagedRuntimeInstallRow'
import { SearchableSetting } from './SearchableSetting'
import { SettingsSubsectionHeader, SettingsSwitch } from './SettingsFormControls'
import { getChatProvidersSearchEntry } from './chat-providers-search'
import { antigravityChatSignInRunning } from '@/runtime/antigravity-chat-sign-in-status'
import { useAntigravityChatSignIn } from './AntigravityChatSignInRow'
import {
AntigravityProviderSignInArea,
antigravityProviderSignInShown
} from './AntigravityProviderSignInArea'
import { getHostRuntimeLabel, getRuntimeSentenceLabel } from './accounts-pane-runtime'
import { openAppHttpLinkFromAnchor } from '@/lib/app-http-link-open'
// The agents listed here are this computer's, so their downloaded programs are too.
const CHAT_HOST: RuntimeClientTarget = { kind: 'local' }
const BUILT_IN_CHAT_AGENTS: readonly TuiAgent[] = ['claude', 'codex']
function ProviderStatus({
agent,
installed,
enabledInAgents
}: {
agent: TuiAgent
installed: readonly TuiAgent[] | null
enabledInAgents: boolean
}): React.JSX.Element | null {
if (!enabledInAgents) {
return (
<span>
{translate('settings.chat.providers.agentDisabled', 'Turned off in Agent settings')}
</span>
)
}
if (installed === null) {
return null
}
if (installed.includes(agent)) {
return <span>{translate('settings.chat.providers.installed', 'Installed')}</span>
}
const homepageUrl = getAgentCatalog().find((entry) => entry.id === agent)?.homepageUrl
return (
<span>
{translate('settings.chat.providers.notInstalled', 'Not installed')}
{homepageUrl ? (
<>
{' · '}
<a
href={homepageUrl}
target="_blank"
onClick={openAppHttpLinkFromAnchor}
rel="noopener noreferrer"
className="text-foreground underline-offset-2 hover:underline"
>
{translate('settings.chat.providers.install', 'Install')}
</a>
</>
) : null}
</span>
)
}
/** Use managed chat-runtime status for downloaded providers; CLI installation does not establish chat readiness. */
function ProviderDetail(props: {
agent: TuiAgent
label: string
installed: readonly TuiAgent[] | null
enabledInAgents: boolean
settings: GlobalSettings
updateSettings: (updates: Partial<GlobalSettings>) => void | Promise<void>
}): React.JSX.Element {
const runtime = useManagedRuntimeInstall(CHAT_HOST, props.agent, props.enabledInAgents)
// Install and Remove wait for a running sign-in, which runs the installed server.
const signIn = useAntigravityChatSignIn(
props.agent === 'antigravity' && props.enabledInAgents && runtime.kind === 'active'
? CHAT_HOST
: null
)
const state =
runtime.kind === 'active' && runtime.snapshot.kind === 'known' ? runtime.snapshot.state : null
const on = isNativeChatProviderDisplayedOn(props.settings, props.agent, [
...(props.installed ?? []),
...(state?.source ? [props.agent] : [])
])
const unsettled =
!on &&
props.settings.nativeChatProviders?.[props.agent] === undefined &&
(props.installed === null ||
runtime.kind === 'pending' ||
(runtime.kind === 'active' && runtime.snapshot.kind !== 'known'))
const control = unsettled ? (
<div className="h-5 w-9 shrink-0 rounded-full bg-muted/50 animate-pulse" aria-hidden="true" />
) : (
<SettingsSwitch
checked={on}
disabled={!props.enabledInAgents}
ariaLabel={translate('settings.chat.providers.toggleLabel', 'Open {{value0}} as a chat', {
value0: props.label
})}
onChange={() =>
saveNativeChatProviderChoice({
agent: props.agent,
on: !on,
fallbackSettings: props.settings,
updateSettings: props.updateSettings
})
}
/>
)
const summary = (line: React.ReactNode): React.JSX.Element => (
<div className="min-w-0 flex-1">
<div className="text-sm font-medium leading-none">{props.label}</div>
<div className="mt-1 text-xs text-muted-foreground">{line}</div>
</div>
)
if (!props.enabledInAgents || runtime.kind !== 'active') {
return (
<>
{summary(
<ProviderStatus
agent={props.agent}
installed={props.installed}
enabledInAgents={props.enabledInAgents}
/>
)}
{control}
</>
)
}
return (
<ManagedRuntimeInstallRow
observation={runtime}
agentLabel={props.label}
signInRunning={antigravityChatSignInRunning(signIn.flow)}
>
{(line, actions) => (
<>
{summary(line)}
{actions}
{control}
</>
)}
</ManagedRuntimeInstallRow>
)
}
function AntigravityChatProviderSignIn(): React.JSX.Element | null {
const runtime = useManagedRuntimeInstall(CHAT_HOST, 'antigravity', true)
if (runtime.kind !== 'active' || !antigravityProviderSignInShown(runtime)) {
return null
}
return (
<AntigravityProviderSignInArea
owner={CHAT_HOST}
label={getRuntimeSentenceLabel({ runtime: 'host', label: getHostRuntimeLabel() })}
install={runtime}
/>
)
}
export function NativeChatProvidersSetting({
settings,
updateSettings,
forceVisible = false
}: {
settings: GlobalSettings
updateSettings: (updates: Partial<GlobalSettings>) => void | Promise<void>
forceVisible?: boolean
}): React.JSX.Element | null {
useChatProgramFacts({ kind: 'floating' })
const entry = getChatProvidersSearchEntry()
useHostStructuredAgentsVersion()
const agents = chatProviderAgents(LOCAL_EXECUTION_HOST_ID, undefined)
const detectedIds = useAppStore(readNativeDetectedAgents)
const managed = readHostStructuredAgents(LOCAL_EXECUTION_HOST_ID, undefined)
const visibleAgents = agents.filter(
(agent) =>
detectedIds !== null ||
BUILT_IN_CHAT_AGENTS.includes(agent) ||
settings.nativeChatProviders?.[agent] !== undefined ||
managed?.some((row) => row.agent === agent && row.managedRuntime)
)
return (
<SearchableSetting
{...entry}
id={entry.targetSectionId}
forceVisible={forceVisible}
className="max-w-none space-y-3"
>
<SettingsSubsectionHeader title={entry.title} description={entry.description} />
<Card>
<CardContent>
<div className="divide-y divide-border/40">
{visibleAgents.map((agent) => {
const label = getAgentLabel(agent)
const enabledInAgents = isTuiAgentEnabled(agent, settings.disabledTuiAgents)
return (
<div key={agent} className="space-y-3 py-3">
<div className="flex items-center gap-3">
<div className="flex size-7 shrink-0 items-center justify-center rounded-md border border-border/50 bg-background/50">
<AgentIcon agent={agent} size={16} />
</div>
<ProviderDetail
agent={agent}
label={label}
installed={detectedIds}
enabledInAgents={enabledInAgents}
settings={settings}
updateSettings={updateSettings}
/>
</div>
{agent === 'antigravity' && enabledInAgents ? (
<AntigravityChatProviderSignIn />
) : null}
</div>
)
})}
</div>
</CardContent>
</Card>
</SearchableSetting>
)
}
@@ -84,12 +84,17 @@ function expandAgentSearchText(value: string): string[] {
type AgentsPaneSearchOptions = {
includeAgentAwake?: boolean
/** Chat UI's per-agent Chat | CLI choice, shown in the Installed list. */
includeAgentChatChoice?: boolean
includeAgentRuntime?: boolean
includeAgentWorkspaceTrust?: boolean
includeCodexTerminalServerIsolation?: boolean
}
const AGENT_AWAKE_SEARCH_ENTRY_ID = 'agent-awake'
const AGENT_CHAT_CHOICE_SEARCH_ENTRY_ID = 'agent-chat-choice'
/** The Installed list, where each chat agent's row holds its Chat | CLI choice. */
export const AGENTS_INSTALLED_SECTION_ID = 'agents-installed'
const AGENT_WORKSPACE_TRUST_SEARCH_ENTRY_ID = 'agent-workspace-trust'
const AGENT_RUNTIME_SEARCH_ENTRY_ID = 'agent-runtime'
const CODEX_TERMINAL_SERVER_ISOLATION_SEARCH_ENTRY_ID = 'codex-terminal-server-isolation'
@@ -130,6 +135,24 @@ const getAllAgentsPaneSearchEntries = createLocalizedCatalog(() => [
...translateSearchKeyword('auto.components.settings.agents.search.719f53350c', 'path')
]
},
{
id: AGENT_CHAT_CHOICE_SEARCH_ENTRY_ID,
targetSectionId: AGENTS_INSTALLED_SECTION_ID,
title: translate('settings.agents.chatChoiceTitle', 'Open as chat or CLI'),
description: translate(
'settings.agents.chatChoiceDescription',
'Choose whether each agent opens as a chat or in its CLI.'
),
keywords: [
...translateSearchKeyword('settings.appearance.chat.title', 'Chat'),
...translateSearchKeyword('settings.chat.providers.searchCli', 'CLI'),
...translateSearchKeyword('settings.chat.providers.searchTerminal', 'terminal'),
...translateSearchKeyword('settings.chat.providers.title', 'Providers', {
aliases: ['provider']
}),
...translateSearchKeyword('settings.chat.providers.searchAgents', 'agents')
]
},
{
title: getAgentStatusHooksTitle(),
description: getAgentStatusHooksDescription(),
@@ -201,6 +224,7 @@ const getAllAgentsPaneSearchEntries = createLocalizedCatalog(() => [
export function getAgentsPaneSearchEntries({
includeAgentAwake = true,
includeAgentChatChoice = false,
includeAgentRuntime = true,
includeAgentWorkspaceTrust = true,
includeCodexTerminalServerIsolation = true
@@ -209,6 +233,9 @@ export function getAgentsPaneSearchEntries({
if (!includeAgentAwake) {
hiddenIds.add(AGENT_AWAKE_SEARCH_ENTRY_ID)
}
if (!includeAgentChatChoice) {
hiddenIds.add(AGENT_CHAT_CHOICE_SEARCH_ENTRY_ID)
}
if (!includeAgentRuntime) {
hiddenIds.add(AGENT_RUNTIME_SEARCH_ENTRY_ID)
}
@@ -1,17 +0,0 @@
import { translate } from '@/i18n/i18n'
import { createLocalizedCatalog } from '@/i18n/localized-catalog'
import type { SettingsSearchEntry } from './settings-search'
export const getChatProvidersSearchEntry = createLocalizedCatalog((): SettingsSearchEntry => ({
targetSectionId: 'chat-providers',
title: translate('settings.chat.providers.title', 'Providers'),
description: translate(
'settings.chat.providers.description',
'Choose which agents open as a chat. Turned off, an installed agent opens in a terminal.'
),
keywords: [
translate('settings.chat.providers.searchAgents', 'agents'),
translate('settings.chat.providers.searchCli', 'CLI'),
translate('settings.chat.providers.searchTerminal', 'terminal')
]
}))
@@ -1,59 +1,455 @@
// @vitest-environment happy-dom
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { projectAgentLaunchSettings } from '../../../../shared/agent-launch-settings'
import { createGlobalSettingsFixture } from '../../../../shared/global-settings-test-fixture'
import { AgentDefaultSetting } from './AgentDefaultSetting'
import { buildDefaultAgentOpensAs } from './native-chat-provider-choice'
import {
isNativeChatProviderDisplayedOn,
normalizeNativeChatProviderChoices
} from '../../../../shared/native-chat/native-chat-providers'
import { installManagedRuntime } from '@/runtime/managed-runtime-install-status'
import { buildNewTabAgentMenuRows } from '@/lib/new-tab-agent-menu-rows'
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import type { TuiAgent } from '../../../../shared/tui-agent'
import type { ManagedRuntimeInstallObservation } from '@/runtime/use-managed-runtime-install'
import type * as HostChatProgramModule from '@/lib/host-chat-program'
import type * as ManagedRuntimeInstallModule from '@/runtime/managed-runtime-install-status'
import type * as SignInAreaModule from './AntigravityProviderSignInArea'
import { TooltipProvider } from '../ui/tooltip'
import { AgentCatalogRow } from './AgentCatalogRow'
import { AgentLaunchSettingsCatalog } from './AgentLaunchSettingsCatalog'
import { buildAgentOpensAs } from './native-chat-provider-choice'
const store = vi.hoisted((): { settings: unknown } => ({ settings: null }))
vi.mock('../../store', () => ({ useAppStore: { getState: () => store } }))
afterEach(cleanup)
const store = vi.hoisted(
(): { settings: unknown; localDetectedAgentIdsByContext: { host: TuiAgent[] | null } } => ({
settings: null,
localDetectedAgentIdsByContext: { host: ['claude'] }
})
)
const inventory = vi.hoisted(
(): {
detected: TuiAgent[] | null
failed: boolean
chatAvailable: boolean
nativePrograms: TuiAgent[]
} => ({
detected: ['claude'],
failed: false,
chatAvailable: true,
nativePrograms: []
})
)
const runtime = vi.hoisted((): { current: ManagedRuntimeInstallObservation } => ({
current: { kind: 'pending' }
}))
vi.mock('../../store', () => ({
useAppStore: Object.assign((selector: (value: unknown) => unknown) => selector(store), {
getState: () => store
})
}))
vi.mock('@/lib/chat-provider-agents', () => ({
chatProviderAgents: () => ['claude', 'codex', 'antigravity'],
useHostStructuredAgentsVersion: () => 0
}))
vi.mock('@/hooks/useDetectedAgents', () => ({
useDetectedAgents: () => ({
detectedIds: inventory.detected,
detectionFailed: inventory.failed,
isRefreshing: false,
refresh: vi.fn()
})
}))
vi.mock('@/lib/host-chat-program-acquisition', () => ({ useChatProgramFacts: () => {} }))
vi.mock('@/lib/host-chat-program', async (importOriginal) => ({
...(await importOriginal<typeof HostChatProgramModule>()),
agentChoiceOpensAsChat: () => false,
resolveChatProgramContext: () => ({}),
// The host publishes Antigravity's downloaded chat program; its agy CLI is not installed.
readChatProgram: (_store: unknown, agent: TuiAgent) =>
(agent === 'antigravity' && inventory.chatAvailable) || inventory.nativePrograms.includes(agent)
? 'available'
: 'missing'
}))
vi.mock('@/runtime/host-structured-agents', () => ({
readHostStructuredAgents: () => [{ agent: 'antigravity', managedRuntime: true }]
}))
vi.mock('@/runtime/managed-runtime-install-status', async (importOriginal) => ({
...(await importOriginal<typeof ManagedRuntimeInstallModule>()),
installManagedRuntime: vi.fn(async () => {})
}))
vi.mock('@/runtime/use-managed-runtime-install', () => ({
useManagedRuntimeInstall: () => runtime.current
}))
vi.mock('./AntigravityChatSignInRow', () => ({ useAntigravityChatSignIn: () => ({ flow: null }) }))
vi.mock('./AntigravityProviderSignInArea', async (importOriginal) => ({
...(await importOriginal<typeof SignInAreaModule>()),
AntigravityProviderSignInArea: () => <p>Antigravity account</p>
}))
beforeEach(() => {
inventory.detected = ['claude']
inventory.failed = false
inventory.chatAvailable = true
inventory.nativePrograms = []
store.localDetectedAgentIdsByContext.host = ['claude']
runtime.current = { kind: 'pending' }
vi.stubGlobal('api', { platform: { get: () => ({ platform: 'darwin' }) } })
})
afterEach(() => {
cleanup()
vi.unstubAllGlobals()
})
function renderDefault(settings: ReturnType<typeof createGlobalSettingsFixture>) {
function renderRow(
agent: TuiAgent,
settings: ReturnType<typeof createGlobalSettingsFixture>,
cliDetected: boolean,
chatRuntime?: React.ReactNode
) {
const updateSettings = vi.fn()
store.settings = settings
const label = agent === 'antigravity' ? 'Antigravity' : agent === 'codex' ? 'Codex' : 'Claude'
render(
<AgentDefaultSetting
defaultAgent={settings.defaultTuiAgent}
detectedIds={new Set(['claude'])}
enabledDetectedAgents={[]}
catalog={[]}
description=""
opensAs={buildDefaultAgentOpensAs({
defaultAgent: settings.defaultTuiAgent,
settings,
updateSettings,
installedHere: ['claude']
})}
<AgentCatalogRow
agentId={agent}
label={label}
homepageUrl="https://example.com"
defaultCmd={agent}
defaultArgs=""
defaultEnv={{}}
isDetected={cliDetected}
isEnabled
isDefault={false}
cmdOverride={undefined}
argsOverride=""
envOverride={{}}
onSetDefault={vi.fn()}
/>
onSetEnabled={vi.fn()}
onSaveOverride={vi.fn()}
onSaveArgs={vi.fn()}
onSaveEnv={vi.fn()}
opensAs={buildAgentOpensAs({
agent,
cliDetected,
chatProgramHere: !cliDetected,
settings,
updateSettings
})}
chatRuntime={chatRuntime}
/>,
{ wrapper: TooltipProvider }
)
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 checked = (name: string): string | null | undefined =>
screen.getByRole('radiogroup', { name }).querySelector('[aria-checked="true"]')?.textContent
describe('agent row chat or CLI', () => {
it('offers only Chat for an agent whose chat runs without its CLI', () => {
const updateSettings = renderRow(
'antigravity',
createGlobalSettingsFixture({ experimentalNativeChat: true }),
false
)
expect(checked('Open Antigravity as')).toBe('Chat')
const cli = screen.getByRole('radio', { name: 'CLI' })
expect(cli.getAttribute('aria-disabled')).toBe('true')
fireEvent.click(cli)
expect(updateSettings).not.toHaveBeenCalled()
// It is installed for chat, so the row is not dimmed like an uninstalled agent.
expect(document.querySelector('.opacity-70')).toBeNull()
})
it('offers no choice while Chat UI is off or the agent is turned off', () => {
renderRow('claude', createGlobalSettingsFixture({ experimentalNativeChat: false }), true)
expect(screen.queryByRole('radiogroup', { name: 'Open Claude as' })).toBeNull()
cleanup()
renderRow(
'claude',
createGlobalSettingsFixture({ experimentalNativeChat: true, disabledTuiAgents: ['claude'] }),
true
)
expect(screen.queryByRole('radiogroup', { name: 'Open Claude as' })).toBeNull()
})
it('keeps Antigravity setup expanded from first install through its installed-row transition', async () => {
runtime.current = {
kind: 'active',
subject: { target: { kind: 'local' }, runtimeId: null, agent: 'antigravity' },
removable: true,
snapshot: {
kind: 'known',
state: {
agent: 'antigravity',
revision: 1,
available: true,
version: '1',
installedVersion: null,
installedCurrent: false,
operation: null,
source: null
}
}
}
inventory.detected = null
inventory.failed = true
inventory.chatAvailable = false
const settings = createGlobalSettingsFixture({ experimentalNativeChat: true })
store.settings = settings
const element = () => (
<AgentLaunchSettingsCatalog
settings={projectAgentLaunchSettings(settings, 'darwin')}
mutate={vi.fn(async () => {})}
detectionTarget={{ kind: 'local' }}
hostName={null}
renderPermissions={() => null}
local={{ settings, updateSettings: vi.fn(), saveEnvironment: vi.fn(async () => {}) }}
/>
)
const view = render(element(), { wrapper: TooltipProvider })
expect(screen.queryByText('Chat runtime')).toBeNull()
const row = screen.getByRole('radiogroup', { name: 'Open Antigravity as' }).closest('.py-3')
if (!(row instanceof HTMLElement)) {
throw new Error('Antigravity row is missing')
}
fireEvent.click(within(row).getByRole('button', { name: 'Expand agent settings' }))
expect(screen.getByText('Chat runtime')).toBeTruthy()
expect(screen.queryByText('Antigravity account')).toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'Install' }))
await vi.waitFor(() =>
expect(installManagedRuntime).toHaveBeenCalledWith({
target: { kind: 'local' },
runtimeId: null,
agent: 'antigravity'
})
)
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 }
})
if (runtime.current.kind !== 'active' || runtime.current.snapshot.kind !== 'known') {
throw new Error('Managed runtime snapshot is missing')
}
Object.assign(runtime.current.snapshot.state, {
revision: 2,
source: 'managed',
installedVersion: '1',
installedCurrent: true
})
view.rerender(element())
expect(screen.getByText('Antigravity account')).toBeTruthy()
inventory.chatAvailable = true
view.rerender(element())
expect(screen.getByRole('radiogroup', { name: 'Open Antigravity as' }).closest('.py-3')).toBe(
row
)
expect(document.getElementById('agents-installed')?.textContent).toContain('Antigravity')
expect(within(row).getByRole('button', { name: 'Collapse agent settings' })).toBeTruthy()
expect(
row.compareDocumentPosition(screen.getByRole('heading', { name: /Available to install/ })) &
Node.DOCUMENT_POSITION_FOLLOWING
).not.toBe(0)
expect(within(row).getByRole('status').textContent).toContain('Installed')
expect(screen.getByText('Antigravity account')).toBeTruthy()
fireEvent.click(within(row).getByRole('button', { name: 'Collapse agent settings' }))
inventory.chatAvailable = false
view.rerender(element())
expect(screen.queryByText('Antigravity account')).toBeNull()
expect(screen.getByRole('radiogroup', { name: 'Open Antigravity as' }).closest('.py-3')).toBe(
row
)
})
it('offers no choice while Chat UI is off', () => {
renderDefault(
createGlobalSettingsFixture({ experimentalNativeChat: false, defaultTuiAgent: 'claude' })
it('lists an agent whose chat runs without its CLI as installed, with Chat only', () => {
vi.stubGlobal('api', { platform: { get: () => ({ platform: 'darwin' }) } })
const settings = createGlobalSettingsFixture({ experimentalNativeChat: true })
store.settings = settings
render(
<AgentLaunchSettingsCatalog
settings={projectAgentLaunchSettings(settings, 'darwin')}
mutate={vi.fn(async () => {})}
detectionTarget={{ kind: 'local' }}
hostName={null}
renderPermissions={() => null}
local={{ settings, updateSettings: vi.fn(), saveEnvironment: vi.fn(async () => {}) }}
/>,
{ wrapper: TooltipProvider }
)
expect(screen.queryByRole('radiogroup')).toBeNull()
const installed = document.getElementById('agents-installed')
expect(installed?.textContent).toContain('Antigravity')
expect(checked('Open Antigravity as')).toBe('Chat')
const group = screen.getByRole('radiogroup', { name: 'Open Antigravity as' })
expect(group.querySelector('[aria-checked="false"]')?.getAttribute('aria-disabled')).toBe(
'true'
)
expect(checked('Open Claude as')).toBe('Chat')
expect(checked('Open Codex as')).toBe('Chat')
vi.unstubAllGlobals()
})
it('retains saved client choices through missing inventory and writes the preference paired launches consume', async () => {
for (const priorChoice of [false, true, undefined]) {
const settings = createGlobalSettingsFixture({
experimentalNativeChat: true,
defaultTuiAgent: 'codex',
nativeChatProviders: normalizeNativeChatProviderChoices(
JSON.parse(JSON.stringify({ claude: false, codex: priorChoice }))
)
})
store.settings = settings
const updateSettings = vi.fn(async (updates: Partial<GlobalSettings>) => {
Object.assign(settings, updates)
})
const element = () => (
<AgentLaunchSettingsCatalog
settings={projectAgentLaunchSettings(settings, 'darwin')}
mutate={vi.fn(async () => {})}
detectionTarget={{ kind: 'local' }}
hostName={null}
renderPermissions={() => null}
local={{ settings, updateSettings, saveEnvironment: vi.fn(async () => {}) }}
/>
)
inventory.detected = null
inventory.chatAvailable = false
const view = render(element(), { wrapper: TooltipProvider })
const pairedRows = () =>
buildNewTabAgentMenuRows({
settings,
detectedOnHost: ['codex'],
chatAgents: ['codex'],
hostChatAnswer: () => 'yes',
chatProgram: () => 'available'
})
for (const observation of [
{ detected: null, failed: false },
{ detected: null, failed: true },
{ detected: [], failed: false },
{ detected: ['claude', 'codex'], failed: false },
{ detected: ['claude'], failed: false }
] satisfies { detected: TuiAgent[] | null; failed: boolean }[]) {
Object.assign(inventory, observation)
view.rerender(element())
expect(checked('Open Codex as')).toBe(priorChoice === false ? 'CLI' : 'Chat')
expect(
within(screen.getByRole('radiogroup', { name: 'Open Codex as' }))
.getByRole('radio', { name: 'CLI' })
.getAttribute('aria-disabled')
).not.toBe('true')
expect(isNativeChatProviderDisplayedOn(settings, 'codex', observation.detected)).toBe(
priorChoice !== false
)
expect(updateSettings).not.toHaveBeenCalled()
expect(settings.defaultTuiAgent).toBe('codex')
}
expect(pairedRows()).toEqual(
priorChoice === false
? [{ kind: 'cli', agent: 'codex', chat: true }]
: [{ kind: 'chat', agent: 'codex' }]
)
fireEvent.click(
within(screen.getByRole('radiogroup', { name: 'Open Codex as' })).getByRole('radio', {
name: priorChoice === false ? 'Chat' : 'CLI'
})
)
await vi.waitFor(() => expect(updateSettings).toHaveBeenCalledOnce())
view.rerender(element())
expect(settings.nativeChatProviders).toEqual({ claude: false, codex: priorChoice === false })
expect(pairedRows()).toEqual(
priorChoice === false
? [{ kind: 'chat', agent: 'codex' }]
: [{ kind: 'cli', agent: 'codex', chat: true }]
)
cleanup()
}
})
it('serializes choices from different rows while an inventory update arrives during the save', async () => {
const settings = createGlobalSettingsFixture({ experimentalNativeChat: true })
store.settings = settings
const save = Promise.withResolvers<void>()
const updateSettings = vi.fn(async (updates: Partial<GlobalSettings>) => {
await save.promise
Object.assign(settings, updates)
})
const element = () => (
<AgentLaunchSettingsCatalog
settings={projectAgentLaunchSettings(settings, 'darwin')}
mutate={vi.fn(async () => {})}
detectionTarget={{ kind: 'local' }}
hostName={null}
renderPermissions={() => null}
local={{ settings, updateSettings, saveEnvironment: vi.fn(async () => {}) }}
/>
)
const view = render(element(), { wrapper: TooltipProvider })
const cli = (label: string) =>
within(screen.getByRole('radiogroup', { name: `Open ${label} as` })).getByRole('radio', {
name: 'CLI'
})
fireEvent.click(cli('Claude'))
await vi.waitFor(() => expect(updateSettings).toHaveBeenCalledOnce())
fireEvent.click(cli('Codex'))
inventory.detected = []
view.rerender(element())
expect(updateSettings).toHaveBeenCalledOnce()
save.resolve()
await vi.waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(2))
expect(settings.nativeChatProviders).toEqual({ claude: false, codex: false })
})
it('uses native facts for the computer-wide mode and contextual facts for CLI settings', async () => {
inventory.nativePrograms = ['claude']
const inventories: {
native: TuiAgent[] | null
contextual: TuiAgent[]
cliDisabled: boolean
}[] = [
{ native: ['claude'], contextual: [], cliDisabled: false },
{ native: [], contextual: ['claude'], cliDisabled: true },
{ native: null, contextual: ['claude'], cliDisabled: false }
]
for (const { native, contextual, cliDisabled } of inventories) {
inventory.detected = contextual
store.localDetectedAgentIdsByContext.host = native
const settings = createGlobalSettingsFixture({ experimentalNativeChat: true })
store.settings = settings
const updateSettings = vi.fn(async (updates: Partial<GlobalSettings>) => {
Object.assign(settings, updates)
})
render(
<AgentLaunchSettingsCatalog
settings={projectAgentLaunchSettings(settings, 'win32')}
mutate={vi.fn(async () => {})}
detectionTarget={{ kind: 'local' }}
hostName={null}
renderPermissions={() => null}
local={{ settings, updateSettings, saveEnvironment: vi.fn(async () => {}) }}
/>,
{ wrapper: TooltipProvider }
)
const group = screen.getByRole('radiogroup', { name: 'Open Claude as' })
const cli = within(group).getByRole('radio', { name: 'CLI' })
expect(cli.getAttribute('aria-disabled') === 'true').toBe(cliDisabled)
const row = group.closest('.py-3')
if (!(row instanceof HTMLElement)) {
throw new Error('Claude row is missing')
}
expect(within(row).queryByRole('button', { name: 'Default' }) !== null).toBe(
contextual.includes('claude')
)
fireEvent.click(cli)
if (cliDisabled) {
expect(updateSettings).not.toHaveBeenCalled()
} else {
await vi.waitFor(() => expect(updateSettings).toHaveBeenCalledOnce())
expect(
buildNewTabAgentMenuRows({
settings,
detectedOnHost: native,
chatAgents: ['claude'],
hostChatAnswer: () => 'yes',
chatProgram: () => 'available'
})
).toEqual(native?.includes('claude') ? [{ kind: 'cli', agent: 'claude', chat: true }] : [])
}
cleanup()
}
})
})
@@ -1,4 +1,3 @@
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/native-chat-providers'
@@ -8,11 +7,9 @@ import {
} 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'
// Serialize shared-map writes across Settings rows so rapid flips cannot overwrite each other.
const enqueueProviderUpdate = createAgentSettingsUpdateQueue()
@@ -36,33 +33,38 @@ export function saveNativeChatProviderChoice({
})
}
/** Offer chat/CLI choice only when its stored value agrees with the Providers switch. */
export function buildDefaultAgentOpensAs({
defaultAgent,
export type AgentOpensAs = {
value: 'chat' | 'cli'
/** Chat runs from Orca's downloaded program, so there is no terminal to open. */
cliMissing: boolean
onChange: (value: 'chat' | 'cli') => void
}
// This computer's preference also routes paired-host launches, independently of local inventory.
export function buildAgentOpensAs({
agent,
cliDetected,
chatProgramHere,
settings,
updateSettings,
installedHere
updateSettings
}: {
defaultAgent: TuiAgent | 'blank' | null
agent: TuiAgent
cliDetected: boolean | null
chatProgramHere: boolean
settings: GlobalSettings
updateSettings: (updates: Partial<GlobalSettings>) => void | Promise<void>
installedHere: readonly TuiAgent[] | null
}): DefaultAgentOpensAs | undefined {
if (defaultAgent === null || defaultAgent === 'blank' || !isNativeChatEnabled(settings)) {
return undefined
}
const agent = defaultAgent
// Require local inventory so this control agrees with the local Providers switch’s unset-value behavior.
}): AgentOpensAs | undefined {
const offered =
isNativeChatEnabled(settings) &&
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),
// Matches launch: an unchosen agent opens as a chat.
value: isNativeChatDefaultFor(settings, agent) ? 'chat' : 'cli',
cliMissing: chatProgramHere && cliDetected === false,
onChange: (value) =>
void saveNativeChatProviderChoice({
agent,
@@ -28,7 +28,8 @@ import type { SettingsNavigationBuildOptions } from './settings-navigation-build
export function buildCapabilitySettingsSections({
isLocalWindowsHost,
isWebClient,
isLinearConnected
isLinearConnected,
nativeChatEnabled
}: SettingsNavigationBuildOptions): SettingsNavSection[] {
const showDesktopOnlySettings = !isWebClient
return [
@@ -42,6 +43,7 @@ export function buildCapabilitySettingsSections({
icon: Bot,
searchEntries: getAgentsPaneSearchEntries({
includeAgentAwake: !isWebClient,
includeAgentChatChoice: nativeChatEnabled && !isWebClient,
includeAgentRuntime: isLocalWindowsHost,
includeAgentWorkspaceTrust: !isWebClient,
includeCodexTerminalServerIsolation: !isWebClient
@@ -4,7 +4,6 @@ import { getChatBehaviorSearchEntries } from '@/components/settings/chat-behavio
import { getChatNamingSearchEntry } from '@/components/settings/chat-naming-search'
import { getChatInlineVisualsSearchEntry } from '@/components/settings/chat-inline-visuals-search'
import { getChatPermissionSearchEntry } from '@/components/settings/chat-permission-search'
import { getChatProvidersSearchEntry } from '@/components/settings/chat-providers-search'
import { getInputPaneSearchEntries } from '@/components/settings/input-search'
import { getNotificationsPaneSearchEntries } from '@/components/settings/notifications-search'
import { getShortcutsPaneSearchEntries } from '@/components/settings/shortcuts-search'
@@ -58,11 +57,7 @@ export function buildInterfaceSettingsSections({
(entry) => hostQueuesMessages || entry.targetSectionId !== 'chat-queue-follow-ups'
),
...(showDesktopOnlySettings
? [
getChatProvidersSearchEntry(),
getChatNamingSearchEntry(),
getChatInlineVisualsSearchEntry()
]
? [getChatNamingSearchEntry(), getChatInlineVisualsSearchEntry()]
: [])
],
group: 'interface'
+6 -9
View File
@@ -496,15 +496,9 @@
},
"providers": {
"title": "Anbieter",
"description": "Auswählen, welche Agenten als Chat geöffnet werden. Bei deaktivierter Option wird ein installierter Agent in einem Terminal geöffnet.",
"searchAgents": "Agenten",
"searchCli": "CLI",
"searchTerminal": "Terminal",
"agentDisabled": "In den Agenteneinstellungen deaktiviert",
"installed": "Installiert",
"notInstalled": "Nicht installiert",
"install": "Installieren",
"toggleLabel": "{{value0}} als Chat öffnen",
"antigravityDescription": "Antigravity-Chats nutzen einen Server, den Orca auf diesen Host herunterlädt, unabhängig von der agy-CLI.",
"antigravityNotSignedIn": "Nicht angemeldet. Chats erfordern eine separate Antigravity-Anmeldung, unabhängig vom agy-Terminal-Konto.",
"antigravityAccount": "Konto",
@@ -757,7 +751,6 @@
"defaultOpensAs": "{{value0}} öffnen als",
"defaultOpensAsChat": "Chat",
"defaultOpensAsCli": "CLI",
"defaultOpensAsScope": "Gilt für neue {{value0}}-Sitzungen, die von diesem Computer aus gestartet werden, auch auf gekoppelten Hosts. Entspricht dem zugehörigen Schalter unter „Anbieter“.",
"save": "Speichern",
"clearDefault": "Standardagent-Auswahl aufheben",
"autoUnsupported": "„Automatisch“ erfordert eine neuere Orca-Version auf diesem Host.",
@@ -807,7 +800,11 @@
"description": "Erlaubt jedem Codex-Konto und deinem eigenen ~/.codex, Unterhaltungen fortzusetzen, die in den anderen begonnen wurden. Schalte es aus, um den Verlauf jedes Kontos getrennt zu halten. Beim Ausschalten wird nichts entfernt, was bereits geteilt wurde.",
"unsupported": "Aktualisiere Orca auf {{host}}, um diese Einstellung dort zu ändern.",
"updateFailed": "Die Einstellung auf {{host}} konnte nicht aktualisiert werden."
}
},
"installCliForTerminal": "Installiere die {{value0}}-CLI, um es in einem Terminal zu öffnen.",
"chatRuntime": "Chat-Laufzeit",
"chatChoiceTitle": "Als Chat oder CLI öffnen",
"chatChoiceDescription": "Lege fest, ob jeder Agent als Chat oder in seiner CLI geöffnet wird."
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "Symbolischer Link",
@@ -20033,7 +20030,7 @@
"heroBody": "Einmaliger Download von {{size}} MB. Alle Chats mit {{agent}} auf dem Computer, auf dem dieser Chat läuft, verwenden ihn gemeinsam.",
"heroBodyNoSize": "Einmaliger Download. Alle Chats mit {{agent}} auf dem Computer, auf dem dieser Chat läuft, verwenden ihn gemeinsam.",
"stripSize": "Download von {{size}} MB auf dem Computer, auf dem dieser Chat läuft",
"settingsHint": "Auch unter Einstellungen › Chat › Anbieter",
"settingsHint": "Auch unter Einstellungen › Agenten",
"installShort": "Installieren",
"tryAgain": "Erneut versuchen",
"cancelShort": "Abbrechen",
+6 -9
View File
@@ -516,15 +516,9 @@
},
"providers": {
"title": "Providers",
"description": "Choose which agents open as a chat. Turned off, an installed agent opens in a terminal.",
"searchAgents": "agents",
"searchCli": "CLI",
"searchTerminal": "terminal",
"agentDisabled": "Turned off in Agent settings",
"installed": "Installed",
"notInstalled": "Not installed",
"install": "Install",
"toggleLabel": "Open {{value0}} as a chat",
"antigravityDescription": "Antigravity chats run a server Orca downloads to this host, separate from the agy CLI.",
"antigravityNotSignedIn": "Not signed in. Chats need an Antigravity sign-in, separate from your agy terminal account.",
"antigravityAccount": "Account",
@@ -797,7 +791,6 @@
"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",
"autoUnsupported": "Approve for me needs a newer Orca on this host.",
@@ -850,7 +843,11 @@
"description": "Lets each Codex account, and your own ~/.codex, resume conversations started on the others. Turn off to keep every account's history separate. Turning it off removes nothing already shared.",
"unsupported": "Update Orca on {{host}} to change this setting there.",
"updateFailed": "Could not update the setting on {{host}}."
}
},
"installCliForTerminal": "Install the {{value0}} CLI to open it in a terminal.",
"chatRuntime": "Chat runtime",
"chatChoiceTitle": "Open as chat or CLI",
"chatChoiceDescription": "Choose whether each agent opens as a chat or in its CLI."
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "symlink",
@@ -20529,7 +20526,7 @@
"heroBody": "One-time {{size}} MB download. It's shared by {{agent}} chats on the computer running this chat.",
"heroBodyNoSize": "One-time download. It's shared by {{agent}} chats on the computer running this chat.",
"stripSize": "{{size}} MB download on the computer running this chat",
"settingsHint": "Also in Settings › Chat › Providers",
"settingsHint": "Also in Settings › Agents",
"installShort": "Install",
"tryAgain": "Try again",
"cancelShort": "Cancel",
+7 -10
View File
@@ -58,13 +58,7 @@
"antigravitySignInNeeded": "Los chats necesitan un inicio de sesión de Antigravity, independiente de tu cuenta de agy en la terminal.",
"searchAgents": "agentes",
"searchTerminal": "Terminal",
"installed": "Instalado",
"notInstalled": "No instalado",
"install": "Instalar",
"searchCli": "CLI",
"toggleLabel": "Abrir {{value0}} como chat",
"agentDisabled": "Desactivado en la configuración del agente",
"description": "Elige qué agentes se abren como chat. Si desactivas esta opción, los agentes instalados se abren en una terminal."
"searchCli": "CLI"
},
"permissionsDescriptionOwnAgents": "Los chats nuevos siguen el permiso de cada agente en Configuración → Agentes."
},
@@ -326,7 +320,6 @@
"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",
"customArgsBadge": "Personalizado",
@@ -382,7 +375,11 @@
"description": "Permite que cada cuenta de Codex, y tu propio ~/.codex, reanude conversaciones iniciadas en las demás. Desactívalo para mantener separado el historial de cada cuenta. Desactivarlo no elimina nada de lo que ya se compartió.",
"unsupported": "Actualiza Orca en {{host}} para cambiar este ajuste allí.",
"updateFailed": "No se pudo actualizar el ajuste en {{host}}."
}
},
"installCliForTerminal": "Instala la CLI de {{value0}} para abrirlo en un terminal.",
"chatRuntime": "Entorno de chat",
"chatChoiceTitle": "Abrir como chat o CLI",
"chatChoiceDescription": "Elige si cada agente se abre como chat o en su CLI."
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "enlace simbólico",
@@ -20196,7 +20193,7 @@
"heroBody": "Descarga única de {{size}} MB. La comparten los chats de {{agent}} en el equipo que ejecuta este chat.",
"heroBodyNoSize": "Descarga única. La comparten los chats de {{agent}} en el equipo que ejecuta este chat.",
"stripSize": "Descarga de {{size}} MB en el equipo que ejecuta este chat",
"settingsHint": "También en Ajustes › Chat › Proveedores",
"settingsHint": "También en Ajustes › Agentes",
"installShort": "Instalar",
"tryAgain": "Reintentar",
"cancelShort": "Cancelar",
+7 -10
View File
@@ -277,14 +277,8 @@
"antigravityAccount": "Compte",
"antigravitySignInNeeded": "Les discussions nécessitent une connexion Antigravity, distincte de votre compte agy du terminal.",
"searchTerminal": "Terminal",
"installed": "Installé",
"notInstalled": "Non installé",
"install": "Installer",
"searchAgents": "agents",
"searchCli": "CLI",
"agentDisabled": "Désactivé dans les paramètres de l'agent",
"description": "Choisissez les agents à ouvrir dans un chat. Si cette option est désactivée, un agent installé s’ouvre dans un terminal.",
"toggleLabel": "Ouvrir {{value0}} dans un chat"
"searchCli": "CLI"
},
"permissionsDescriptionOwnAgents": "Les nouveaux chats suivent l’autorisation de chaque agent dans Paramètres → Agents."
},
@@ -546,7 +540,6 @@
"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",
"customArgsBadge": "Personnalisé",
@@ -602,7 +595,11 @@
"description": "Permet à chaque compte Codex, ainsi qu'à votre propre ~/.codex, de reprendre les conversations commencées sur les autres. Désactivez-le pour garder l'historique de chaque compte séparé. Le désactiver ne supprime rien de ce qui a déjà été partagé.",
"unsupported": "Mettez à jour Orca sur {{host}} pour y modifier ce réglage.",
"updateFailed": "Impossible de mettre à jour le réglage sur {{host}}."
}
},
"installCliForTerminal": "Installez la CLI {{value0}} pour l’ouvrir dans un terminal.",
"chatRuntime": "Environnement de chat",
"chatChoiceTitle": "Ouvrir en chat ou en CLI",
"chatChoiceDescription": "Choisissez si chaque agent s’ouvre en chat ou dans sa CLI."
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "lien symbolique",
@@ -20416,7 +20413,7 @@
"heroBody": "Téléchargement unique de {{size}} Mo, partagé par les conversations {{agent}} sur l’ordinateur qui exécute cette conversation.",
"heroBodyNoSize": "Téléchargement unique, partagé par les conversations {{agent}} sur l’ordinateur qui exécute cette conversation.",
"stripSize": "Téléchargement de {{size}} Mo sur l’ordinateur qui exécute cette conversation",
"settingsHint": "Aussi dans Paramètres › Chat › Fournisseurs",
"settingsHint": "Aussi dans Paramètres › Agents",
"installShort": "Installer",
"tryAgain": "Réessayer",
"cancelShort": "Annuler",
+6 -9
View File
@@ -58,12 +58,6 @@
"antigravitySignInNeeded": "チャットには、ターミナルの agy アカウントとは別の Antigravity サインインが必要です。",
"searchAgents": "Agent",
"searchTerminal": "ターミナル",
"installed": "インストール済み",
"notInstalled": "未インストール",
"install": "インストール",
"description": "どの Agent をチャットとして開くかを選択します。オフにすると、インストールされた Agent がターミナルで開きます。",
"agentDisabled": "Agent 設定でオフになっています",
"toggleLabel": "{{value0}} をチャットとして開きます",
"searchCli": "CLI"
},
"permissionsDescriptionOwnAgents": "新しいチャットは、設定 → Agent にある各 Agent の権限に従います。"
@@ -326,7 +320,6 @@
"defaultOpensAs": "{{value0}}の開き方",
"defaultOpensAsChat": "チャット",
"defaultOpensAsCli": "CLI",
"defaultOpensAsScope": "このコンピューターから起動する新しい{{value0}}セッション(ペアリングしたホスト上も含む)に適用されます。プロバイダーのスイッチと連動します。",
"save": "保存",
"clearDefault": "デフォルトの Agent をクリア",
"customArgsBadge": "カスタム",
@@ -379,7 +372,11 @@
"description": "各 Codex アカウントとご自身の ~/.codex で、ほかのアカウントで始めた会話を再開できるようにします。オフにすると、アカウントごとに履歴を分けたままにします。オフにしても、すでに共有されたものは削除されません。",
"unsupported": "この設定を変更するには、{{host}} の Orca を更新してください。",
"updateFailed": "{{host}} の設定を更新できませんでした。"
}
},
"installCliForTerminal": "ターミナルで開くには {{value0}} CLI をインストールしてください。",
"chatRuntime": "チャットランタイム",
"chatChoiceTitle": "チャットまたは CLI で開く",
"chatChoiceDescription": "各 Agent をチャットで開くか CLI で開くかを選びます。"
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "シンボリックリンク",
@@ -19612,7 +19609,7 @@
"heroBody": "{{size}} MB の一度きりのダウンロードです。このチャットを実行するコンピューター上の {{agent}} チャットで共有されます。",
"heroBodyNoSize": "一度きりのダウンロードです。このチャットを実行するコンピューター上の {{agent}} チャットで共有されます。",
"stripSize": "このチャットを実行するコンピューターに {{size}} MB をダウンロード",
"settingsHint": "設定 › チャット › プロバイダー からも行えます",
"settingsHint": "設定 › Agent からも行えます",
"installShort": "インストール",
"tryAgain": "再試行",
"cancelShort": "キャンセル",
+7 -10
View File
@@ -58,13 +58,7 @@
"antigravitySignInNeeded": "채팅에는 터미널 agy 계정과 별개인 Antigravity 로그인이 필요합니다.",
"searchAgents": "에이전트",
"searchTerminal": "터미널",
"installed": "설치됨",
"notInstalled": "설치되지 않음",
"install": "설치",
"searchCli": "CLI",
"description": "채팅으로 열 에이전트를 선택하세요. 끄면 설치된 에이전트가 터미널에서 열립니다.",
"agentDisabled": "에이전트 설정에서 사용 중지됨",
"toggleLabel": "{{value0}}을(를) 채팅으로 엽니다"
"searchCli": "CLI"
},
"permissionsDescriptionOwnAgents": "새 채팅은 설정 → 에이전트에 있는 각 에이전트의 권한을 따릅니다."
},
@@ -326,7 +320,6 @@
"defaultOpensAs": "{{value0}} 여는 방식",
"defaultOpensAsChat": "채팅",
"defaultOpensAsCli": "CLI",
"defaultOpensAsScope": "이 컴퓨터에서 시작하는 새 {{value0}} 세션(페어링된 호스트 포함)에 적용됩니다. 공급자의 스위치와 같습니다.",
"save": "저장",
"clearDefault": "기본 에이전트 지우기",
"customArgsBadge": "사용자 정의",
@@ -379,7 +372,11 @@
"description": "각 Codex 계정과 내 ~/.codex에서 다른 계정에서 시작한 대화를 이어서 할 수 있게 합니다. 끄면 계정마다 기록을 따로 유지합니다. 꺼도 이미 공유된 항목은 삭제되지 않습니다.",
"unsupported": "이 설정을 변경하려면 {{host}}의 Orca를 업데이트하세요.",
"updateFailed": "{{host}}의 설정을 업데이트하지 못했습니다."
}
},
"installCliForTerminal": "터미널에서 열려면 {{value0}} CLI를 설치하세요.",
"chatRuntime": "채팅 런타임",
"chatChoiceTitle": "채팅 또는 CLI로 열기",
"chatChoiceDescription": "각 에이전트를 채팅으로 열지 CLI로 열지 선택합니다."
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "심볼릭 링크",
@@ -19612,7 +19609,7 @@
"heroBody": "한 번만 {{size}}MB를 다운로드합니다. 이 채팅을 실행하는 컴퓨터의 {{agent}} 채팅이 함께 사용합니다.",
"heroBodyNoSize": "한 번만 다운로드합니다. 이 채팅을 실행하는 컴퓨터의 {{agent}} 채팅이 함께 사용합니다.",
"stripSize": "이 채팅을 실행하는 컴퓨터에 {{size}}MB 다운로드",
"settingsHint": "설정 › 채팅 › 공급자에서도 설치할 수 있습니다",
"settingsHint": "설정 › 에이전트에서도 설치할 수 있습니다",
"installShort": "설치",
"tryAgain": "다시 시도",
"cancelShort": "취소",
+6 -9
View File
@@ -1411,15 +1411,9 @@
},
"providers": {
"title": "Provedores",
"description": "Escolha quais agentes abrem como chat. Quando desativado, um agente instalado abre em um terminal.",
"searchAgents": "agentes",
"searchCli": "CLI",
"searchTerminal": "terminal",
"agentDisabled": "Desativado nas configurações de Agentes",
"installed": "Instalado",
"notInstalled": "Não instalado",
"install": "Instalar",
"toggleLabel": "Abrir {{value0}} como chat",
"antigravityDescription": "Os chats do Antigravity executam um servidor que o Orca baixa neste host, separado da CLI agy.",
"antigravityNotSignedIn": "Sessão não iniciada. Os chats exigem login no Antigravity, separado da sua conta agy do terminal.",
"antigravityAccount": "Conta",
@@ -1511,7 +1505,6 @@
"defaultOpensAs": "Abrir {{value0}} como",
"defaultOpensAsChat": "Chat",
"defaultOpensAsCli": "CLI",
"defaultOpensAsScope": "Aplica-se a novas sessões de {{value0}} iniciadas neste computador, inclusive em hosts pareados. Corresponde à opção em Provedores.",
"permissionsTitle": "Permissões",
"permissionsChoiceExplanation": "O que todos os agentes podem fazer sem perguntar. Você pode configurar um agente de forma diferente abaixo.",
"permissionEachOwn": "Configurações próprias de cada agente",
@@ -1548,7 +1541,11 @@
"description": "Permite que cada conta do Codex, e o seu próprio ~/.codex, retome conversas iniciadas nas outras. Desative para manter o histórico de cada conta separado. Desativar não remove nada que já tenha sido compartilhado.",
"unsupported": "Atualize o Orca em {{host}} para alterar essa configuração lá.",
"updateFailed": "Não foi possível atualizar a configuração em {{host}}."
}
},
"installCliForTerminal": "Instale a CLI do {{value0}} para abri-lo em um terminal.",
"chatRuntime": "Runtime de chat",
"chatChoiceTitle": "Abrir como chat ou CLI",
"chatChoiceDescription": "Escolha se cada agente abre como chat ou na sua CLI."
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "link simbólico",
@@ -21080,7 +21077,7 @@
"heroBody": "Download único de {{size}} MB. Ele é compartilhado pelos chats do {{agent}} no computador que executa este chat.",
"heroBodyNoSize": "Download único. Ele é compartilhado pelos chats do {{agent}} no computador que executa este chat.",
"stripSize": "Download de {{size}} MB no computador que executa este chat",
"settingsHint": "Também em Configurações › Chat › Provedores",
"settingsHint": "Também em Configurações › Agentes",
"installShort": "Instalar",
"tryAgain": "Tentar novamente",
"cancelShort": "Cancelar",
+6 -9
View File
@@ -546,15 +546,9 @@
},
"providers": {
"title": "Провайдеры",
"description": "Выберите агентов, которые открываются в чате. Если выключено, установленный агент открывается в терминале.",
"searchAgents": "агенты",
"searchCli": "командная строка",
"searchTerminal": "терминал",
"agentDisabled": "Выключено в настройках агентов",
"installed": "Установлено",
"notInstalled": "Не установлено",
"install": "Установить",
"toggleLabel": "Открывать {{value0}} в чате",
"antigravityDescription": "Для чатов Antigravity Orca загружает на этот сервер отдельный серверный компонент, независимый от agy CLI.",
"antigravityNotSignedIn": "Вход не выполнен. Для чатов нужен отдельный вход в Antigravity, независимо от учётной записи agy в терминале.",
"antigravityAccount": "Учётная запись",
@@ -803,7 +797,6 @@
"defaultOpensAs": "Открывать {{value0}} как",
"defaultOpensAsChat": "Чат",
"defaultOpensAsCli": "Командная строка",
"defaultOpensAsScope": "Для новых сессий {{value0}}, запускаемых с этого компьютера, в том числе на подключённых серверах. Соответствует переключателю в разделе «Провайдеры».",
"save": "Сохранить",
"clearDefault": "Сбросить агента по умолчанию",
"autoUnsupported": "Для автоматического режима нужна более новая версия Orca на этом сервере.",
@@ -859,7 +852,11 @@
"description": "Позволяет каждому аккаунту Codex и вашему собственному ~/.codex продолжать разговоры, начатые в других. Выключите, чтобы история каждого аккаунта оставалась отдельной. При выключении ничего из уже общего не удаляется.",
"unsupported": "Обновите Orca на {{host}}, чтобы изменить там эту настройку.",
"updateFailed": "Не удалось обновить настройку на {{host}}."
}
},
"installCliForTerminal": "Установите CLI {{value0}}, чтобы открывать его в терминале.",
"chatRuntime": "Среда чата",
"chatChoiceTitle": "Открывать как чат или CLI",
"chatChoiceDescription": "Выберите, открывается ли каждый агент как чат или в своём CLI."
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "символическая ссылка",
@@ -20717,7 +20714,7 @@
"heroBody": "Однократная загрузка {{size}} МБ. Эта копия общая для чатов {{agent}} на компьютере, где работает этот чат.",
"heroBodyNoSize": "Однократная загрузка. Эта копия общая для чатов {{agent}} на компьютере, где работает этот чат.",
"stripSize": "Загрузка {{size}} МБ на компьютере, где работает этот чат",
"settingsHint": "Также в разделе «Настройки › Чат › Провайдеры»",
"settingsHint": "Также в разделе «Настройки › Агенты»",
"installShort": "Установить",
"tryAgain": "Повторить попытку",
"cancelShort": "Отмена",
+6 -9
View File
@@ -492,15 +492,9 @@
},
"providers": {
"title": "Sağlayıcılar",
"description": "Hangi ajanların sohbet olarak açılacağını seçin. Bu seçenek kapalıysa yüklü ajan terminalde açılır.",
"searchAgents": "ajanlar",
"searchCli": "CLI",
"searchTerminal": "terminal",
"agentDisabled": "Ajan ayarlarında kapalı",
"installed": "Yüklü",
"notInstalled": "Yüklü değil",
"install": "Yükle",
"toggleLabel": "{{value0}} öğesini sohbet olarak aç",
"antigravityDescription": "Antigravity sohbetleri, Orca'nın bu ana makineye indirdiği ve agy CLI'dan ayrı olan bir sunucuyu çalıştırır.",
"antigravityNotSignedIn": "Oturum açılmadı. Sohbetler için agy terminal hesabınızdan ayrı bir Antigravity oturumu gerekir.",
"antigravityAccount": "Hesap",
@@ -749,7 +743,6 @@
"defaultOpensAs": "{{value0}} açılış biçimi",
"defaultOpensAsChat": "Sohbet",
"defaultOpensAsCli": "CLI",
"defaultOpensAsScope": "Eşleştirilmiş sunuculardakiler dahil, bu bilgisayardan başlatılan yeni {{value0}} oturumları için geçerlidir. Sağlayıcılar bölümündeki seçimiyle eşleşir.",
"save": "Kaydet",
"clearDefault": "Varsayılan ajanı temizle",
"autoUnsupported": "Otomatik modu için bu ana makinede daha yeni bir Orca sürümü gerekir.",
@@ -799,7 +792,11 @@
"description": "Her Codex hesabının ve kendi ~/.codex dizininin, diğerlerinde başlatılan konuşmaları sürdürmesine izin verir. Her hesabın geçmişini ayrı tutmak için kapatın. Kapatmak, zaten paylaşılmış hiçbir şeyi kaldırmaz.",
"unsupported": "Bu ayarı orada değiştirmek için {{host}} üzerindeki Orca'yı güncelleyin.",
"updateFailed": "{{host}} üzerindeki ayar güncellenemedi."
}
},
"installCliForTerminal": "Terminalde açmak için {{value0}} CLI'ını yükleyin.",
"chatRuntime": "Sohbet çalışma ortamı",
"chatChoiceTitle": "Sohbet veya CLI olarak aç",
"chatChoiceDescription": "Her ajanın sohbet olarak mı yoksa kendi CLI'ında mı açılacağını seçin."
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "sembolik bağlantı",
@@ -20017,7 +20014,7 @@
"heroBody": "Bir defaya mahsus {{size}} MB indirilir. Bu sohbeti çalıştıran bilgisayardaki {{agent}} sohbetleri tarafından ortak kullanılır.",
"heroBodyNoSize": "Bir defaya mahsus indirilir. Bu sohbeti çalıştıran bilgisayardaki {{agent}} sohbetleri tarafından ortak kullanılır.",
"stripSize": "Bu sohbeti çalıştıran bilgisayara {{size}} MB indirilir",
"settingsHint": "Ayarlar › Sohbet › Sağlayıcılar bölümünden de erişilebilir",
"settingsHint": "Ayarlar › Ajanlar bölümünden de erişilebilir",
"installShort": "Yükle",
"tryAgain": "Yeniden dene",
"cancelShort": "İptal",
+6 -9
View File
@@ -465,15 +465,9 @@
},
"providers": {
"title": "提供者",
"description": "選擇要以聊天方式開啟的代理程式。關閉後,已安裝的代理程式會在終端機中開啟。",
"searchAgents": "代理程式",
"searchCli": "CLI",
"searchTerminal": "終端機",
"agentDisabled": "已在「代理程式」設定中關閉",
"installed": "已安裝",
"notInstalled": "未安裝",
"install": "安裝",
"toggleLabel": "以聊天方式開啟 {{value0}}",
"antigravityDescription": "Antigravity 聊天會執行由 Orca 下載至此主機的伺服器,與 agy CLI 各自獨立。",
"antigravityNotSignedIn": "尚未登入。聊天需另外登入 Antigravity,與終端機中的 agy 帳戶分開使用。",
"antigravityAccount": "帳戶",
@@ -722,7 +716,6 @@
"defaultOpensAs": "{{value0}} 的開啟方式",
"defaultOpensAsChat": "聊天",
"defaultOpensAsCli": "CLI",
"defaultOpensAsScope": "適用於從這台電腦啟動的新 {{value0}} 工作階段,包括在已配對主機上啟動的工作階段。與「提供者」中對應的開關一致。",
"save": "儲存",
"clearDefault": "清除預設代理程式",
"autoUnsupported": "此主機上的 Orca 需更新至較新版本才能使用自動模式。",
@@ -772,7 +765,11 @@
"description": "讓每個 Codex 帳戶以及你自己的 ~/.codex 都能繼續在其他帳戶中開始的對話。關閉後,每個帳戶的歷史記錄將保持獨立。關閉不會移除任何已共用的內容。",
"unsupported": "請更新 {{host}} 上的 Orca 以在那裡變更此設定。",
"updateFailed": "無法更新 {{host}} 上的設定。"
}
},
"installCliForTerminal": "安裝 {{value0}} CLI 即可在終端機中開啟。",
"chatRuntime": "聊天執行環境",
"chatChoiceTitle": "以聊天或 CLI 開啟",
"chatChoiceDescription": "選擇每個代理程式以聊天或其 CLI 開啟。"
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "符號連結",
@@ -19719,7 +19716,7 @@
"heroBody": "只需下載一次,共 {{size}} MB。執行此聊天的電腦上,所有 {{agent}} 聊天皆可共用。",
"heroBodyNoSize": "只需下載一次。執行此聊天的電腦上,所有 {{agent}} 聊天皆可共用。",
"stripSize": "在執行此聊天的電腦上下載 {{size}} MB",
"settingsHint": "也可在「設定 › 聊天 › 供應商」中找到",
"settingsHint": "也可在「設定 › 代理程式」中找到",
"installShort": "安裝",
"tryAgain": "再試一次",
"cancelShort": "取消",
+7 -10
View File
@@ -58,13 +58,7 @@
"antigravitySignInNeeded": "聊天需要单独的 Antigravity 登录,与终端中的 agy 帐户分开。",
"searchAgents": "智能体",
"searchTerminal": "终端",
"installed": "已安装",
"notInstalled": "未安装",
"install": "安装",
"searchCli": "CLI",
"agentDisabled": "已在智能体设置中关闭",
"description": "选择哪些智能体以聊天方式打开。关闭后,已安装的智能体将在终端中打开。",
"toggleLabel": "以聊天方式打开 {{value0}}"
"searchCli": "CLI"
},
"permissionsDescriptionOwnAgents": "新聊天遵循设置 → 智能体中各智能体的权限。"
},
@@ -329,7 +323,6 @@
"defaultOpensAs": "{{value0}} 的打开方式",
"defaultOpensAsChat": "聊天",
"defaultOpensAsCli": "CLI",
"defaultOpensAsScope": "适用于从这台电脑启动的新 {{value0}} 会话,包括在已配对主机上启动的会话。与提供方中的开关一致。",
"save": "保存",
"clearDefault": "清除默认智能体",
"customArgsBadge": "自定义",
@@ -379,7 +372,11 @@
"description": "让每个 Codex 账户以及你自己的 ~/.codex 都能继续在其他账户中开始的对话。关闭后,每个账户的历史记录将保持独立。关闭不会移除任何已共享的内容。",
"unsupported": "请更新 {{host}} 上的 Orca 以在那里更改此设置。",
"updateFailed": "无法更新 {{host}} 上的设置。"
}
},
"installCliForTerminal": "安装 {{value0}} CLI 即可在终端中打开。",
"chatRuntime": "聊天运行时",
"chatChoiceTitle": "以聊天或 CLI 打开",
"chatChoiceDescription": "选择每个智能体以聊天还是其 CLI 打开。"
},
"followSymlinkedDirectoriesKeywords": {
"symlink": "符号链接",
@@ -19613,7 +19610,7 @@
"heroBody": "一次性下载 {{size}} MB,由运行此聊天的电脑上的 {{agent}} 聊天共用。",
"heroBodyNoSize": "一次性下载,由运行此聊天的电脑上的 {{agent}} 聊天共用。",
"stripSize": "在运行此聊天的电脑上下载 {{size}} MB",
"settingsHint": "也可在 设置 › 聊天 › 供应商 中安装",
"settingsHint": "也可在 设置 › 智能体 中安装",
"installShort": "安装",
"tryAgain": "重试",
"cancelShort": "取消",