mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 09:07:40 +00:00
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:
@@ -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'
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "キャンセル",
|
||||
|
||||
@@ -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": "취소",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Отмена",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "取消",
|
||||
|
||||
@@ -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": "取消",
|
||||
|
||||
Reference in New Issue
Block a user