diff --git a/frontend/e2e/plugin-manager-layout.spec.js b/frontend/e2e/plugin-manager-layout.spec.js index 69106b3..99b238d 100644 --- a/frontend/e2e/plugin-manager-layout.spec.js +++ b/frontend/e2e/plugin-manager-layout.spec.js @@ -73,6 +73,21 @@ test.describe('E: Plugin Manager layout', () => { expect(buttonStyle.borderRadius).toBe('6px'); }); + test('plugin manager summarizes plugin health and elevated permissions before the list', async ({ page }) => { + await openPluginManager(page); + + const health = page.locator('[data-plugin-manager-summary="health"]'); + await expect(health).toBeVisible(); + await expect(health.locator('[data-plugin-status-summary="loaded"]')).toContainText('7'); + await expect(health.locator('[data-plugin-status-summary="failed"]')).toContainText('0'); + await expect(health.locator('[data-plugin-status-summary="disabled"]')).toContainText('0'); + + const risk = page.locator('[data-plugin-manager-summary="risk"]'); + await expect(risk).toBeVisible(); + await expect(risk.locator('[data-plugin-risk-summary="elevated-permissions"]')).toContainText('4'); + await expect(risk).toContainText('elevated permissions'); + }); + test('workspace selection keeps exactly one active node', async ({ page }) => { const selected = page.locator('.wt-node.selected .wt-label'); await expect(selected).toHaveCount(1); diff --git a/frontend/src/lib/plugin-manager/PluginManager.svelte b/frontend/src/lib/plugin-manager/PluginManager.svelte index 8aba0db..2651d85 100644 --- a/frontend/src/lib/plugin-manager/PluginManager.svelte +++ b/frontend/src/lib/plugin-manager/PluginManager.svelte @@ -227,6 +227,25 @@ $: totalPlugins = plugins.length; $: totalCaps = capabilities.length; $: totalPerms = permissions.length; + $: statusSummary = computeStatusSummary(plugins); + $: elevatedPermissionPluginCount = computeElevatedPermissionPluginCount(plugins, permissions); + + function computeStatusSummary(pluginRows) { + return pluginRows.reduce((summary, plugin) => { + const status = plugin?.status || 'unknown'; + if (status === 'loaded') summary.loaded += 1; + else if (status === 'degraded') summary.degraded += 1; + else if (status === 'failed' || status === 'incompatible' || status === 'missing-required-capability') summary.failed += 1; + else if (status === 'disabled' || plugin?.enabled === false) summary.disabled += 1; + else summary.other += 1; + return summary; + }, { loaded: 0, degraded: 0, failed: 0, disabled: 0, other: 0 }); + } + + function computeElevatedPermissionPluginCount(pluginRows, permissionRows) { + const dangerous = new Set((permissionRows || []).filter(permission => permission.dangerous).map(permission => permission.name)); + return (pluginRows || []).filter(plugin => (plugin?.manifest?.permissions || []).some(permission => dangerous.has(permission))).length; + } function closeSettings() { settingsPanel = null; @@ -280,6 +299,25 @@ {totalPerms} permissions known +