mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 13:26:40 +00:00
fix(desktop): Plugins-page Desktop switch wires the kanban toolset
The Plugins page's Desktop-relevant toggle was UI-only: flipping it never
told the backend, so kanban stayed active regardless. The switch now also
calls setToolsetEnabled('kanban', on, profile) — the same
PUT /api/tools/toolsets/kanban route the Toolsets tab uses — invalidates
the toolsets query cache, and confirms with a toast (en/fr/de/es).
Fixes #96969
Co-authored-by: previous contributors' direction via #101582
This commit is contained in:
1 parent
0ebaf162cc
commit
cd30fdadf6
8 files changed
+66
-1
No files matched your search
@@ -83,6 +83,15 @@ vi.mock('@/contrib/runtime-loader', async importOriginal => ({
|
||||
uninstallDiskPlugin: (id: string) => uninstallDiskPlugin(id)
|
||||
}))
|
||||
|
||||
// #96969: a Desktop switch whose feature also ships an agent toolset must
|
||||
// reach the same PUT /api/tools/toolsets/{name} the Toolsets tab uses.
|
||||
const setToolsetEnabled = vi.fn(async () => ({ enabled: true, name: 'kanban', ok: true }))
|
||||
|
||||
vi.mock('@/api/toolsets', async importOriginal => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
setToolsetEnabled: (...args: unknown[]) => setToolsetEnabled(...(args as Parameters<typeof setToolsetEnabled>))
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
$pluginRecords.set({})
|
||||
$agentPlugins.set([])
|
||||
@@ -93,6 +102,7 @@ beforeEach(() => {
|
||||
requestGateway.mockReset()
|
||||
requestGateway.mockImplementation(async () => ({ plugins: $agentPlugins.get() }))
|
||||
setEnvVar.mockClear()
|
||||
setToolsetEnabled.mockClear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useStore } from '@nanostores/react'
|
||||
import { memo, type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { setEnvVar } from '@/api/config'
|
||||
import { setToolsetEnabled } from '@/api/toolsets'
|
||||
import { useGatewayRequest } from '@/app/gateway/hooks/use-gateway-request'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Codicon } from '@/components/ui/codicon'
|
||||
@@ -11,8 +12,10 @@ import { $pluginRecords, type PluginRecord, setPluginEnabled } from '@/contrib/p
|
||||
import { discoverRuntimePlugins, uninstallDiskPlugin } from '@/contrib/runtime-loader'
|
||||
import type { ProfileScope } from '@/hermes'
|
||||
import { useI18n } from '@/i18n'
|
||||
import { DESKTOP_PLUGIN_TOOLSETS } from '@/lib/desktop-toolsets'
|
||||
import { triggerHaptic } from '@/lib/haptics'
|
||||
import { FolderOpen, Loader2, Monitor, Package, RefreshCw, Trash2 } from '@/lib/icons'
|
||||
import { queryClient } from '@/lib/query-client'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
$agentPluginBusy,
|
||||
@@ -41,6 +44,7 @@ import { useDeepLinkHighlight } from '../../settings/use-deep-link-highlight'
|
||||
import { CatalogAlert } from '../catalog/catalog-alert'
|
||||
import { CatalogBrowser } from '../catalog/catalog-browser'
|
||||
import { type CatalogEntry, parseCatalog } from '../catalog/catalog-data'
|
||||
import { TOOLSETS_QUERY_KEY } from '../toolsets/toolsets-data'
|
||||
|
||||
import { mergePluginPackages, type PackageKind, type PluginPackage } from './plugin-packages'
|
||||
import { PluginSettingsForm } from './plugin-settings-form'
|
||||
@@ -358,7 +362,33 @@ function PackageRow({
|
||||
checked={desktopOn}
|
||||
onCheckedChange={on => {
|
||||
triggerHaptic('selection')
|
||||
void setPluginEnabled(desktop.id, on)
|
||||
|
||||
void (async () => {
|
||||
await setPluginEnabled(desktop.id, on)
|
||||
// #96969: when the feature's agent-side tools live in a
|
||||
// toolset (the built-in Kanban board has no agent-plugin
|
||||
// half), flip that opt-in with the panel through the same
|
||||
// PUT /api/tools/toolsets/{name} the Toolsets tab uses —
|
||||
// scoped to the profile this page shows, since the toolset
|
||||
// is per-profile config. Refresh the Toolsets cache so its
|
||||
// row repaints, and say what happened either way.
|
||||
const toolset = DESKTOP_PLUGIN_TOOLSETS[desktop.id]
|
||||
|
||||
if (!toolset) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await setToolsetEnabled(toolset, on, profile)
|
||||
void queryClient.invalidateQueries({ queryKey: TOOLSETS_QUERY_KEY })
|
||||
notify({
|
||||
kind: 'success',
|
||||
message: on ? p.toolsetOn(pkg.name, scopeLabel) : p.toolsetOff(pkg.name, scopeLabel)
|
||||
})
|
||||
} catch (err) {
|
||||
notifyError(err, p.toolsetToggleFailed(pkg.name))
|
||||
}
|
||||
})()
|
||||
}}
|
||||
/>
|
||||
) : pkg.desktopMissing ? (
|
||||
|
||||
@@ -2791,6 +2791,9 @@ export const deOverrides = {
|
||||
emptyHint: 'Durchsuchen Sie unten den Katalog und installieren Sie ein geprüftes Plugin mit einem Klick.',
|
||||
loadFailed: 'Agent-Plugins konnten nicht geladen werden',
|
||||
toggleFailed: name => `${name} konnte nicht umgeschaltet werden`,
|
||||
toolsetOn: (name: string, profile: string) => `${name}-Agent-Tools für ${profile} aktiviert`,
|
||||
toolsetOff: (name: string, profile: string) => `${name}-Agent-Tools für ${profile} deaktiviert`,
|
||||
toolsetToggleFailed: (name: string) => `Die ${name}-Agent-Tools konnten nicht umgeschaltet werden`,
|
||||
legacyBackend:
|
||||
'Dieses Backend ist älter als schlüsseladressierte Plugin-Schalter — aktualisieren Sie Hermes, um es hier zu verwalten.',
|
||||
portableBadge: 'tragbar',
|
||||
|
||||
@@ -2487,6 +2487,9 @@ export const en: Translations = {
|
||||
emptyHint: 'Browse the catalog below and install a reviewed plugin with one click.',
|
||||
loadFailed: 'Could not load agent plugins',
|
||||
toggleFailed: (name: string) => `Could not toggle ${name}`,
|
||||
toolsetOn: (name: string, profile: string) => `${name} agent tools enabled for ${profile}`,
|
||||
toolsetOff: (name: string, profile: string) => `${name} agent tools disabled for ${profile}`,
|
||||
toolsetToggleFailed: (name: string) => `Could not toggle the ${name} agent tools`,
|
||||
legacyBackend: 'This backend predates key-addressed plugin toggles — update Hermes to manage it here.',
|
||||
portableBadge: 'portable',
|
||||
serverStates: {
|
||||
|
||||
@@ -2782,6 +2782,9 @@ export const esOverrides = {
|
||||
emptyHint: 'Explora el catálogo de abajo e instala un plugin revisado con un clic.',
|
||||
loadFailed: 'No se pudieron cargar los plugins del agente',
|
||||
toggleFailed: (name: string) => `No se pudo cambiar ${name}`,
|
||||
toolsetOn: (name: string, profile: string) => `Herramientas de agente de ${name} activadas para ${profile}`,
|
||||
toolsetOff: (name: string, profile: string) => `Herramientas de agente de ${name} desactivadas para ${profile}`,
|
||||
toolsetToggleFailed: (name: string) => `No se pudieron cambiar las herramientas de agente de ${name}`,
|
||||
legacyBackend:
|
||||
'Este backend es anterior a los interruptores de plugins por clave: actualiza Hermes para gestionarlo aquí.',
|
||||
portableBadge: 'portátil',
|
||||
|
||||
@@ -2800,6 +2800,9 @@ export const frOverrides = {
|
||||
emptyHint: 'Parcourez le catalogue ci-dessous pour installer un plugin vérifié en un clic.',
|
||||
loadFailed: "Impossible de charger les plugins de l'agent",
|
||||
toggleFailed: name => `Impossible de modifier l'état de ${name}`,
|
||||
toolsetOn: (name: string, profile: string) => `Outils agent de ${name} activés pour ${profile}`,
|
||||
toolsetOff: (name: string, profile: string) => `Outils agent de ${name} désactivés pour ${profile}`,
|
||||
toolsetToggleFailed: (name: string) => `Impossible de modifier les outils agent de ${name}`,
|
||||
legacyBackend: 'Ce backend est trop ancien pour gérer les plugins depuis cet écran ; mettez Hermes à jour.',
|
||||
portableBadge: 'portable',
|
||||
serverStates: {
|
||||
|
||||
@@ -2109,6 +2109,9 @@ export interface Translations {
|
||||
emptyHint: string
|
||||
loadFailed: string
|
||||
toggleFailed: (name: string) => string
|
||||
toolsetOn: (name: string, profile: string) => string
|
||||
toolsetOff: (name: string, profile: string) => string
|
||||
toolsetToggleFailed: (name: string) => string
|
||||
legacyBackend: string
|
||||
portableBadge: string
|
||||
serverStates: {
|
||||
|
||||
@@ -22,3 +22,13 @@ const DESKTOP_HIDDEN_TOOLSETS = new Set([
|
||||
export function isDesktopToolsetVisible(name: string): boolean {
|
||||
return !DESKTOP_HIDDEN_TOOLSETS.has(name)
|
||||
}
|
||||
|
||||
/** Desktop plugin id → the agent toolset carrying the same feature's
|
||||
* agent-side tools. The Plugins page's Desktop switch toggles this app's UI
|
||||
* panel; for these entries it also flips the toolset opt-in through the same
|
||||
* `PUT /api/tools/toolsets/{name}` the Toolsets tab uses, so the panel is
|
||||
* never UI-only (#96969). The kanban board is the built-in case: it has no
|
||||
* agent-plugin package half, its agent tools live in the `kanban` toolset. */
|
||||
export const DESKTOP_PLUGIN_TOOLSETS: Readonly<Record<string, string>> = {
|
||||
kanban: 'kanban'
|
||||
}
|
||||
Reference in new issue
Block a user