Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions apps/desktop/src/app/command-palette/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,6 @@ type NonConfigSettingsLabel =
| 'keysSettings'
| 'keysTools'
| 'mcp'
| 'plugins'
| 'providerAccounts'
| 'providerApiKeys'

Expand Down Expand Up @@ -452,12 +451,6 @@ const NON_CONFIG_SETTINGS: ReadonlyArray<{
labelKey: 'keysSettings',
tab: 'keys&kview=settings'
},
{
icon: Package,
keywords: ['plugins', 'extensions', 'desktop plugins', 'addon', 'add-on'],
labelKey: 'plugins',
tab: 'plugins'
},
{ icon: Archive, keywords: ['history', 'archived'], labelKey: 'archivedChats', tab: 'sessions' },
{ icon: Info, keywords: ['version', 'about'], labelKey: 'about', tab: 'about' }
]
Expand Down Expand Up @@ -1116,6 +1109,13 @@ function CommandPaletteBody({ onExited }: { onExited: () => void }) {
keywords: ['mcp', 'servers', 'tools', 'capabilities', 'model context protocol'],
label: `${capLabel}: ${t.skills.tabMcp}`,
run: go(`${SKILLS_ROUTE}?tab=mcp`)
},
{
icon: Package,
id: 'cap-plugins',
keywords: ['plugins', 'extensions', 'desktop plugins', 'agent plugins', 'catalog', 'addon', 'add-on'],
label: `${capLabel}: ${t.skills.tabPlugins}`,
run: go(`${SKILLS_ROUTE}?tab=plugins`)
}
]
})
Expand Down Expand Up @@ -1195,8 +1195,15 @@ function CommandPaletteBody({ onExited }: { onExited: () => void }) {

if (settingsCatalog.pluginEntries.length > 0) {
result.push({
heading: t.settings.nav.plugins,
items: settingsCatalog.pluginEntries.map(settingsEntryItem)
heading: t.skills.tabPlugins,
items: settingsCatalog.pluginEntries.map(entry => ({
detail: entry.context,
icon: entry.icon,
id: `sp-${entry.id}`,
keywords: [entry.context, entry.description ?? '', ...entry.keywords],
label: entry.label,
run: go(`${SKILLS_ROUTE}?tab=plugins&plugin=${encodeURIComponent(entry.plugin)}`)
}))
})
}

Expand Down Expand Up @@ -1305,13 +1312,6 @@ function CommandPaletteBody({ onExited }: { onExited: () => void }) {
items: [...settingsCatalog.appearanceEntries, ...settingsCatalog.configEntries].map(settingsEntryItem)
})

if (settingsCatalog.pluginEntries.length > 0) {
result.push({
heading: t.settings.nav.plugins,
items: settingsCatalog.pluginEntries.map(settingsEntryItem)
})
}

if (settingsCatalog.credentialEntries.length > 0) {
result.push({
heading: t.settings.nav.apiKeys,
Expand Down
29 changes: 7 additions & 22 deletions apps/desktop/src/app/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
Info,
Keyboard,
KeyRound,
Package,
RefreshCw,
Search,
Settings2,
Expand All @@ -40,7 +39,6 @@ import { useRouteEnumParam } from '../hooks/use-route-enum-param'
import { OverlayIconButton } from '../overlays/overlay-chrome'
import { OverlayMain, OverlayNav, type OverlayNavGroup, OverlaySplitLayout } from '../overlays/overlay-split-layout'
import { OverlayView } from '../overlays/overlay-view'
import { SKILLS_ROUTE } from '../routes'

import { AboutSettings } from './about-settings'
import { AppearanceSettings } from './appearance-settings'
Expand All @@ -50,8 +48,8 @@ import { SECTIONS } from './constants'
import { GatewaySettings } from './gateway-settings'
import { KeybindSettings } from './keybind-settings'
import { KEYS_VIEWS, KeysSettings, type KeysView } from './keys-settings'
import { movedSettingsTabRedirect } from './moved-tabs'
import { NotificationsSettings } from './notifications-settings'
import { PluginsSettings } from './plugins-settings'
import { PROVIDER_VIEWS, ProvidersSettings, type ProviderView } from './providers-settings'
import { SessionsSettings } from './sessions-settings'
import type { SettingsPageProps, SettingsView as SettingsViewId } from './types'
Expand All @@ -67,7 +65,6 @@ const SETTINGS_VIEWS: readonly SettingsViewId[] = [
'keys',
'notifications',
'billing',
'plugins',
'sessions',
'about'
]
Expand All @@ -78,17 +75,14 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set
const navigate = useNavigate()
const { hash, pathname, search } = useLocation()

// MCP moved out of Settings into Capabilities (/skills?tab=mcp). Keep old
// `/settings?tab=mcp` deep links working — `useRouteEnumParam` would silently
// coerce the unknown tab to the default view otherwise. Preserve `server=` so
// an old bookmark still lands on (and highlights) the selected server.
// MCP and Plugins moved out of Settings into Capabilities. Keep old
// `/settings?tab=mcp|plugins` deep links working — `useRouteEnumParam` would
// silently coerce the unknown tab to the default view otherwise.
useEffect(() => {
const params = new URLSearchParams(search)
const redirect = movedSettingsTabRedirect(search)

if (params.get('tab') === 'mcp') {
const server = params.get('server')
const suffix = server ? `&server=${encodeURIComponent(server)}` : ''
navigate(`${SKILLS_ROUTE}?tab=mcp${suffix}`, { replace: true })
if (redirect) {
navigate(redirect, { replace: true })
}
}, [navigate, search])

Expand Down Expand Up @@ -281,13 +275,6 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set
label: t.settings.nav.apiKeys,
onSelect: () => setActiveView('keys')
},
{
active: activeView === 'plugins',
icon: Package,
id: 'plugins',
label: t.settings.nav.plugins,
onSelect: () => setActiveView('plugins')
},
{
active: activeView === 'sessions',
icon: Archive,
Expand Down Expand Up @@ -423,8 +410,6 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set
<NotificationsSettings />
) : activeView === 'billing' ? (
<BillingSettings />
) : activeView === 'plugins' ? (
<PluginsSettings />
) : (
<SessionsSettings />
)
Expand Down
18 changes: 18 additions & 0 deletions apps/desktop/src/app/settings/moved-tabs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest'

import { movedSettingsTabRedirect } from './moved-tabs'

describe('movedSettingsTabRedirect', () => {
it('sends the retired Settings plugin/MCP tabs to Capabilities, keeping the row selector', () => {
expect(movedSettingsTabRedirect('?tab=plugins')).toBe('/skills?tab=plugins')
expect(movedSettingsTabRedirect('?tab=plugins&plugin=demo%2Fplugin')).toBe(
'/skills?tab=plugins&plugin=demo%2Fplugin'
)
expect(movedSettingsTabRedirect('?tab=mcp&server=github')).toBe('/skills?tab=mcp&server=github')
})

it('leaves live Settings tabs alone', () => {
expect(movedSettingsTabRedirect('?tab=providers&pview=keys')).toBeNull()
expect(movedSettingsTabRedirect('')).toBeNull()
})
})
23 changes: 23 additions & 0 deletions apps/desktop/src/app/settings/moved-tabs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { SKILLS_ROUTE } from '../routes'

// Settings tabs that now live in Capabilities → the row-selector param each
// carries (`?server=` for MCP, `?plugin=` for Plugins). Old bookmarks and
// palette links keep resolving to the same row on the new page.
const MOVED_TO_CAPABILITIES: Record<string, string> = { mcp: 'server', plugins: 'plugin' }

/** The Capabilities URL an old `/settings?tab=<moved>` query should land on,
* or null when the tab still belongs to Settings. */
export function movedSettingsTabRedirect(search: string): null | string {
const params = new URLSearchParams(search)
const tab = params.get('tab')
const rowParam = tab ? MOVED_TO_CAPABILITIES[tab] : undefined

if (!tab || rowParam === undefined) {
return null
}

const row = params.get(rowParam)
const suffix = row ? `&${rowParam}=${encodeURIComponent(row)}` : ''

return `${SKILLS_ROUTE}?tab=${tab}${suffix}`
}
14 changes: 8 additions & 6 deletions apps/desktop/src/app/settings/plugin-install-modal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-libra
import { MemoryRouter } from 'react-router'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

const { requestGateway } = vi.hoisted(() => ({ requestGateway: vi.fn() }))
// The host tab lists installed plugins on mount; only an `install` action counts as installing.
const { requestGateway } = vi.hoisted(() => ({ requestGateway: vi.fn(async () => ({ plugins: [] })) }))
vi.mock('@/app/gateway/hooks/use-gateway-request', () => ({
useGatewayRequest: () => ({ requestGateway })
}))
Expand All @@ -21,17 +22,18 @@ import {
import { $activeGatewayProfile } from '@/store/profile'
import { $connection, $gatewayState } from '@/store/session'

import { PluginsTab } from '../skills/plugins-tab'

import { PluginInstallModal } from './plugin-install-modal'
import { PluginsSettings } from './plugins-settings'

const probePluginRepo = vi.fn()
const installDesktopPlugin = vi.fn()

const renderFlow = () =>
render(
<MemoryRouter initialEntries={['/settings?tab=plugins']}>
<MemoryRouter initialEntries={['/skills?tab=plugins']}>
<QueryClientProvider client={queryClient}>
<PluginsSettings />
<PluginsTab profile={null} />
<PluginInstallModal />
</QueryClientProvider>
</MemoryRouter>
Expand Down Expand Up @@ -79,7 +81,7 @@ describe('Install from Git entry flow', () => {
)
).toBeTruthy()
expect(screen.getByText("Installs into this app's local desktop-plugins folder")).toBeTruthy()
expect(requestGateway).not.toHaveBeenCalled()
expect(requestGateway).not.toHaveBeenCalledWith('plugins.manage', expect.objectContaining({ action: 'install' }))
expect(installDesktopPlugin).not.toHaveBeenCalled()
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
expect($pluginInstallRequest.get()).toBeNull()
Expand All @@ -106,7 +108,7 @@ describe('Install from Git entry flow', () => {
const boxes = screen.getAllByRole('checkbox')
expect(boxes.map(box => box.getAttribute('aria-checked'))).toEqual(['false', 'true'])
expect(probePluginRepo).toHaveBeenCalledTimes(1)
expect(requestGateway).not.toHaveBeenCalled()
expect(requestGateway).not.toHaveBeenCalledWith('plugins.manage', expect.objectContaining({ action: 'install' }))
expect(installDesktopPlugin).not.toHaveBeenCalled()
})
})
129 changes: 0 additions & 129 deletions apps/desktop/src/app/settings/plugins-settings.test.tsx

This file was deleted.

5 changes: 0 additions & 5 deletions apps/desktop/src/app/settings/settings-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export interface SettingsSearchTarget {
field?: string
key?: string
keysView?: CredentialSettingsView
plugin?: string
providerView?: 'accounts' | 'custom-endpoints' | 'keys'
setting?: string
view: SettingsView
Expand Down Expand Up @@ -221,9 +220,5 @@ export function settingsSearchTargetQuery(target: SettingsSearchTarget): string
params.set('key', target.key)
}

if (target.plugin) {
params.set('plugin', target.plugin)
}

return params.toString()
}
1 change: 0 additions & 1 deletion apps/desktop/src/app/settings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export type SettingsView =
| 'keybinds'
| 'keys'
| 'notifications'
| 'plugins'
| 'providers'
| 'sessions'
| `config:${string}`
Expand Down
Loading
Loading