diff --git a/desktop/src/__tests__/pluginsSettings.test.tsx b/desktop/src/__tests__/pluginsSettings.test.tsx index 6c174e0f..64d52c43 100644 --- a/desktop/src/__tests__/pluginsSettings.test.tsx +++ b/desktop/src/__tests__/pluginsSettings.test.tsx @@ -661,4 +661,52 @@ describe('Settings > Plugins tab', () => { expect(screen.getByRole('button', { name: /codex-rescue/i })).toBeDisabled() expect(screen.getByRole('button', { name: /gpt-5-4-prompting/i })).toBeDisabled() }) + + // The store records a failed post-change runtime reload in `refreshWarning` + // and still resolves, so the action itself reads as a success. Without the + // warning surfacing, "disabled the plugin but the runtime never reloaded" + // looks identical to a clean success. + it('warns when a plugin change succeeds but the runtime reload fails', async () => { + usePluginStore.setState({ + selectedPlugin: { + id: 'github@claude-plugins-official', + name: 'github', + marketplace: 'claude-plugins-official', + scope: 'user', + enabled: true, + hasErrors: false, + isBuiltin: false, + description: 'GitHub integration', + componentCounts: { + commands: 0, agents: 0, skills: 0, hooks: 0, mcpServers: 0, lspServers: 0, + }, + capabilities: { + commands: [], agents: [], skills: [], hooks: [], mcpServers: [], lspServers: [], + }, + commandEntries: [], + agentEntries: [], + hookEntries: [], + skillEntries: [], + mcpServerEntries: [], + errors: [], + }, + disablePlugin: async () => { + usePluginStore.setState({ refreshWarning: 'runtime refresh failed' }) + return 'Plugin disabled' + }, + }) + + render() + switchToPluginsTab() + + fireEvent.click(screen.getByRole('button', { name: 'Disable' })) + + await waitFor(() => { + const toasts = useUIStore.getState().toasts + expect( + toasts.some(t => t.type === 'warning' && t.message.includes('runtime refresh failed')), + `expected a warning toast, got ${JSON.stringify(toasts)}`, + ).toBe(true) + }) + }) }) diff --git a/desktop/src/components/plugins/PluginDetail.tsx b/desktop/src/components/plugins/PluginDetail.tsx index 552633be..4b41fd4e 100644 --- a/desktop/src/components/plugins/PluginDetail.tsx +++ b/desktop/src/components/plugins/PluginDetail.tsx @@ -68,7 +68,20 @@ export function PluginDetail() { setActionKey(key) try { const message = await fn() - addToast({ type: 'success', message }) + // The store applies the change, then reloads the runtime, and resolves + // either way -- a failed reload only lands in `refreshWarning`. Reporting + // a plain success here would make "applied but never reloaded" look + // identical to a clean run. Read it after the call: the store clears it + // when the mutation starts, so anything present belongs to this action. + const refreshWarning = usePluginStore.getState().refreshWarning + if (refreshWarning) { + addToast({ + type: 'warning', + message: t('settings.plugins.reloadWarning', { message: refreshWarning }), + }) + } else { + addToast({ type: 'success', message }) + } } catch (err) { addToast({ type: 'error', diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index ea7427c0..664ce414 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -1098,6 +1098,7 @@ export const en = { 'settings.plugins.applyHint': 'Enable, disable, and update changes become active after applying them to the current desktop runtime.', 'settings.plugins.lastReload': 'Last apply: {enabled} active plugins, {skills} plugin skills, {errors} errors.', 'settings.plugins.reloadToast': 'Applied plugin changes: {enabled} active plugins, {skills} skills, {errors} errors.', + 'settings.plugins.reloadWarning': 'Plugin change applied, but reloading the runtime failed: {message}', 'settings.plugins.marketplacesTitle': 'Known marketplaces', 'settings.plugins.marketplacesHint': 'Read-only summary of marketplace sources already configured for this machine.', 'settings.plugins.marketplaceAutoUpdateOn': 'Auto-update on', diff --git a/desktop/src/i18n/locales/jp.ts b/desktop/src/i18n/locales/jp.ts index a570edc6..478173e0 100644 --- a/desktop/src/i18n/locales/jp.ts +++ b/desktop/src/i18n/locales/jp.ts @@ -1100,6 +1100,7 @@ export const jp: Record = { 'settings.plugins.applyHint': '有効化、無効化、更新の変更は、現在のデスクトップランタイムに適用した後で有効になります。', 'settings.plugins.lastReload': '前回の適用: アクティブなプラグイン {enabled} 個、プラグインスキル {skills} 個、エラー {errors} 件。', 'settings.plugins.reloadToast': 'プラグインの変更を適用しました: アクティブなプラグイン {enabled} 個、スキル {skills} 個、エラー {errors} 件。', + 'settings.plugins.reloadWarning': 'プラグインの変更は適用されましたが、ランタイムの再読み込みに失敗しました: {message}', 'settings.plugins.marketplacesTitle': '既知のマーケットプレイス', 'settings.plugins.marketplacesHint': 'このマシンに既に設定済みのマーケットプレイスソースの読み取り専用の概要です。', 'settings.plugins.marketplaceAutoUpdateOn': '自動更新オン', diff --git a/desktop/src/i18n/locales/kr.ts b/desktop/src/i18n/locales/kr.ts index 225607d9..eb51848e 100644 --- a/desktop/src/i18n/locales/kr.ts +++ b/desktop/src/i18n/locales/kr.ts @@ -1100,6 +1100,7 @@ export const kr: Record = { 'settings.plugins.applyHint': '사용, 사용 안 함, 업데이트 변경 사항은 현재 데스크톱 런타임에 적용한 후 적용됩니다.', 'settings.plugins.lastReload': '마지막 적용: 활성 플러그인 {enabled}개, 플러그인 스킬 {skills}개, 오류 {errors}건.', 'settings.plugins.reloadToast': '플러그인 변경 사항을 적용했습니다: 활성 플러그인 {enabled}개, 스킬 {skills}개, 오류 {errors}건.', + 'settings.plugins.reloadWarning': '플러그인 변경 사항은 적용되었지만 런타임 다시 불러오기에 실패했습니다: {message}', 'settings.plugins.marketplacesTitle': '알려진 마켓플레이스', 'settings.plugins.marketplacesHint': '이 컴퓨터에 이미 구성된 마켓플레이스 소스의 읽기 전용 요약입니다.', 'settings.plugins.marketplaceAutoUpdateOn': '자동 업데이트 켜짐', diff --git a/desktop/src/i18n/locales/zh-TW.ts b/desktop/src/i18n/locales/zh-TW.ts index 76d865bb..dd31b029 100644 --- a/desktop/src/i18n/locales/zh-TW.ts +++ b/desktop/src/i18n/locales/zh-TW.ts @@ -1100,6 +1100,7 @@ export const zh: Record = { 'settings.plugins.applyHint': '啟用、禁用或更新後,需要把外掛變更重新應用到當前桌面端執行時。', 'settings.plugins.lastReload': '最近一次應用:{enabled} 個活躍外掛,{skills} 個外掛技能,{errors} 個錯誤。', 'settings.plugins.reloadToast': '外掛變更已應用:{enabled} 個活躍外掛,{skills} 個技能,{errors} 個錯誤。', + 'settings.plugins.reloadWarning': '外掛變更已應用,但執行階段重新載入失敗:{message}', 'settings.plugins.marketplacesTitle': '已知外掛市場', 'settings.plugins.marketplacesHint': '這裡只展示當前機器已經配置好的 marketplace 來源摘要。', 'settings.plugins.marketplaceAutoUpdateOn': '自動更新開', diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index 4f910dfd..5c5fabde 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -1100,6 +1100,7 @@ export const zh: Record = { 'settings.plugins.applyHint': '启用、禁用或更新后,需要把插件变更重新应用到当前桌面端运行时。', 'settings.plugins.lastReload': '最近一次应用:{enabled} 个活跃插件,{skills} 个插件技能,{errors} 个错误。', 'settings.plugins.reloadToast': '插件变更已应用:{enabled} 个活跃插件,{skills} 个技能,{errors} 个错误。', + 'settings.plugins.reloadWarning': '插件变更已应用,但运行时重载失败:{message}', 'settings.plugins.marketplacesTitle': '已知插件市场', 'settings.plugins.marketplacesHint': '这里只展示当前机器已经配置好的 marketplace 来源摘要。', 'settings.plugins.marketplaceAutoUpdateOn': '自动更新开',