fix: simplify local agent setup surfaces

This commit is contained in:
lucaronin
2026-05-26 11:26:48 +02:00
parent fbb51a0b4c
commit ea54560e11
21 changed files with 111 additions and 112 deletions

View File

@@ -21,7 +21,7 @@ function getPromptCopy(statuses: AiAgentsStatus) {
if (isAiAgentsStatusChecking(statuses)) {
return {
accentClassName: 'bg-muted text-muted-foreground',
description: 'Checking coding agents. You can also use a local model or API provider.',
description: 'Checking local agents. You can also use a local model or API provider.',
icon: <Loader2 className="size-7 animate-spin" />,
title: 'Checking AI agents',
}
@@ -38,7 +38,7 @@ function getPromptCopy(statuses: AiAgentsStatus) {
return {
accentClassName: 'bg-[var(--feedback-success-bg)] text-[var(--feedback-success-text)]',
description: 'You can use the detected coding agents, or add local/API models in Settings.',
description: 'You can use the detected local agents, or add local/API models in Settings.',
icon: <CheckCircle2 className="size-7" />,
title: 'AI is ready',
}
@@ -58,7 +58,7 @@ function AiModeChoices() {
},
{
icon: <Terminal className="size-4" />,
title: 'Coding agent',
title: 'Local agent',
description: 'Use Claude Code, Codex, OpenCode, Gemini CLI, or Pi for tool-capable vault editing on desktop.',
},
]

View File

@@ -130,7 +130,7 @@ describe('SettingsPanel', () => {
expect(screen.getAllByText('Sync & Updates').length).toBeGreaterThan(0)
})
it('separates coding agents, local models, and API models in AI settings', async () => {
it('separates local agents, local models, and API models in AI settings', async () => {
const aiAgentsStatus: AiAgentsStatus = {
claude_code: { status: 'installed', version: '2.1.18' },
codex: { status: 'missing', version: null },
@@ -148,7 +148,7 @@ describe('SettingsPanel', () => {
/>
)
expect(screen.getByText('Recognized coding agents')).toBeInTheDocument()
expect(screen.getByText('Recognized local agents')).toBeInTheDocument()
expect(screen.getByText('Claude Code')).toBeInTheDocument()
expect(screen.getByText('2.1.18')).toBeInTheDocument()
expect(screen.getByRole('tab', { name: 'Local model' })).toBeInTheDocument()
@@ -159,7 +159,7 @@ describe('SettingsPanel', () => {
fireEvent.click(screen.getByRole('button', { name: 'Test model' }))
expect(await screen.findByText('Connection works. The model replied successfully.')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add local model' })).toBeInTheDocument()
expect(screen.queryByText('Recognized coding agents')).not.toBeInTheDocument()
expect(screen.queryByText('Recognized local agents')).not.toBeInTheDocument()
fireEvent.mouseDown(screen.getByRole('tab', { name: 'API model' }), { button: 0, ctrlKey: false })
expect(screen.getByRole('button', { name: 'Add API model' })).toBeInTheDocument()
@@ -182,7 +182,7 @@ describe('SettingsPanel', () => {
)
expect(within(screen.getByTestId('settings-ai-features-enabled')).getByRole('switch')).toHaveAttribute('aria-checked', 'false')
expect(screen.queryByText('Recognized coding agents')).not.toBeInTheDocument()
expect(screen.queryByText('Recognized local agents')).not.toBeInTheDocument()
fireEvent.click(within(screen.getByTestId('settings-ai-features-enabled')).getByRole('switch'))
saveSettingsPanel()

View File

@@ -105,6 +105,25 @@ describe('AiAgentsBadge', () => {
expect(screen.getByRole('menuitemradio', { name: /OpenAI.*gpt-5\.5/ })).toHaveAttribute('aria-checked', 'true')
})
it('does not show install actions for missing local agents', () => {
renderBadge({
statuses: {
claude_code: { status: 'installed', version: '1.0.20' },
codex: { status: 'missing', version: null },
opencode: { status: 'missing', version: null },
pi: { status: 'missing', version: null },
gemini: { status: 'missing', version: null },
},
})
openAiAgentsMenu()
expect(screen.queryByText('Install')).not.toBeInTheDocument()
expect(screen.queryByText('Install Codex')).not.toBeInTheDocument()
expect(screen.queryByText('Install OpenCode')).not.toBeInTheDocument()
expect(screen.queryByText('Install Pi')).not.toBeInTheDocument()
expect(screen.queryByText('Install Gemini CLI')).not.toBeInTheDocument()
})
it('shows the vault guidance summary and restore action', async () => {
const onRestoreGuidance = vi.fn()

View File

@@ -26,7 +26,6 @@ import {
type VaultAiGuidanceStatus,
} from '../../lib/vaultAiGuidance'
import { translate, type AppLocale } from '../../lib/i18n'
import { openExternalUrl } from '../../utils/url'
import {
DropdownMenu,
DropdownMenuContent,
@@ -82,10 +81,6 @@ function installedAgentDefinitions(statuses: AiAgentsStatus): AiAgentDefinition[
return AI_AGENT_DEFINITIONS.filter((definition) => isAiAgentInstalled(statuses, definition.id))
}
function missingAgentDefinitions(statuses: AiAgentsStatus): AiAgentDefinition[] {
return AI_AGENT_DEFINITIONS.filter((definition) => !isAiAgentInstalled(statuses, definition.id))
}
function triggerLabel(defaultAgent: AiAgentId): string {
return getAiAgentDefinition(defaultAgent).shortLabel
}
@@ -188,7 +183,6 @@ function AgentMenuContent({
locale = 'en',
}: AiAgentsBadgeProps & { selectedTarget: AiTarget; selectedAgentReady: boolean }) {
const installedAgents = installedAgentDefinitions(statuses)
const missingAgents = missingAgentDefinitions(statuses)
const modelTargets = configuredModelTargets(providers)
const selectedAgentValue = selectedTarget.kind === 'agent' && selectedAgentReady ? selectedTarget.agent : undefined
@@ -226,20 +220,6 @@ function AgentMenuContent({
locale={locale}
onSetDefaultTarget={onSetDefaultTarget}
/>
{missingAgents.length > 0 && (
<>
<DropdownMenuSeparator />
<DropdownMenuLabel>{translate(locale, 'status.ai.install')}</DropdownMenuLabel>
{missingAgents.map((definition) => (
<DropdownMenuItem
key={definition.id}
onSelect={() => void openExternalUrl(definition.installUrl)}
>
{translate(locale, 'status.ai.installAgent', { agent: definition.label })}
</DropdownMenuItem>
))}
</>
)}
<GuidanceMenuSection
guidanceStatus={guidanceStatus}
locale={locale}